mirror of
https://github.com/certbot/certbot.git
synced 2026-06-04 14:26:10 -04:00
Merge branch 'master' into repin-oldest
This commit is contained in:
commit
4c87fff29a
62 changed files with 377 additions and 4097 deletions
|
|
@ -79,6 +79,9 @@ jobs:
|
|||
TOXENV: integration-dns-rfc2136
|
||||
docker-dev:
|
||||
TOXENV: docker_dev
|
||||
le-modification:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
TOXENV: modification
|
||||
macos-farmtest-apache2:
|
||||
# We run one of these test farm tests on macOS to help ensure the
|
||||
# tests continue to work on the platform.
|
||||
|
|
|
|||
|
|
@ -56,11 +56,6 @@ jobs:
|
|||
apache-compat:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
TOXENV: apache_compat
|
||||
# le-modification can be moved to the extended test suite once
|
||||
# https://github.com/certbot/certbot/issues/8742 is resolved.
|
||||
le-modification:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
TOXENV: modification
|
||||
apacheconftest:
|
||||
IMAGE_NAME: ubuntu-18.04
|
||||
PYTHON_VERSION: 3.6
|
||||
|
|
|
|||
|
|
@ -314,6 +314,15 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
|
|||
except requests.exceptions.RequestException as error:
|
||||
logger.error("Unable to reach %s: %s", uri, error)
|
||||
return False
|
||||
# By default, http_response.text will try to guess the encoding to use
|
||||
# when decoding the response to Python unicode strings. This guesswork
|
||||
# is error prone. RFC 8555 specifies that HTTP-01 responses should be
|
||||
# key authorizations with possible trailing whitespace. Since key
|
||||
# authorizations must be composed entirely of the base64url alphabet
|
||||
# plus ".", we tell requests that the response should be ASCII. See
|
||||
# https://datatracker.ietf.org/doc/html/rfc8555#section-8.3 for more
|
||||
# info.
|
||||
http_response.encoding = "ascii"
|
||||
logger.debug("Received %s: %s. Headers: %s", http_response,
|
||||
http_response.text, http_response.headers)
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from typing import List
|
|||
from typing import Set
|
||||
from typing import Text
|
||||
from typing import Union
|
||||
import warnings
|
||||
|
||||
import josepy as jose
|
||||
import OpenSSL
|
||||
|
|
@ -224,6 +225,9 @@ class ClientBase:
|
|||
class Client(ClientBase):
|
||||
"""ACME client for a v1 API.
|
||||
|
||||
.. deprecated:: 1.18.0
|
||||
Use :class:`ClientV2` instead.
|
||||
|
||||
.. todo::
|
||||
Clean up raised error types hierarchy, document, and handle (wrap)
|
||||
instances of `.DeserializationError` raised in `from_json()`.
|
||||
|
|
@ -246,6 +250,8 @@ class Client(ClientBase):
|
|||
URI from which the resource will be downloaded.
|
||||
|
||||
"""
|
||||
warnings.warn("acme.client.Client (ACMEv1) is deprecated, "
|
||||
"use acme.client.ClientV2 instead.", PendingDeprecationWarning)
|
||||
self.key = key
|
||||
if net is None:
|
||||
net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
|
||||
|
|
@ -805,6 +811,9 @@ class BackwardsCompatibleClientV2:
|
|||
"""ACME client wrapper that tends towards V2-style calls, but
|
||||
supports V1 servers.
|
||||
|
||||
.. deprecated:: 1.18.0
|
||||
Use :class:`ClientV2` instead.
|
||||
|
||||
.. note:: While this class handles the majority of the differences
|
||||
between versions of the ACME protocol, if you need to support an
|
||||
ACME server based on version 3 or older of the IETF ACME draft
|
||||
|
|
@ -821,6 +830,8 @@ class BackwardsCompatibleClientV2:
|
|||
"""
|
||||
|
||||
def __init__(self, net, key, server):
|
||||
warnings.warn("acme.client.BackwardsCompatibleClientV2 is deprecated, use "
|
||||
"acme.client.ClientV2 instead.", PendingDeprecationWarning)
|
||||
directory = messages.Directory.from_json(net.get(server).json())
|
||||
self.acme_version = self._acme_version_from_directory(directory)
|
||||
self.client: Union[Client, ClientV2]
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ class Error(jose.JSONObjectWithFields, errors.Error):
|
|||
:rtype: unicode
|
||||
|
||||
"""
|
||||
code = str(self.typ).split(':')[-1]
|
||||
code = str(self.typ).rsplit(':', maxsplit=1)[-1]
|
||||
if code in ERROR_CODES:
|
||||
return code
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import socket
|
|||
import socketserver
|
||||
import threading
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from acme import challenges
|
||||
from acme import crypto_util
|
||||
|
|
@ -66,6 +67,9 @@ class BaseDualNetworkedServers:
|
|||
self.threads: List[threading.Thread] = []
|
||||
self.servers: List[socketserver.BaseServer] = []
|
||||
|
||||
# Preserve socket error for re-raising, if no servers can be started
|
||||
last_socket_err: Optional[socket.error] = None
|
||||
|
||||
# Must try True first.
|
||||
# Ubuntu, for example, will fail to bind to IPv4 if we've already bound
|
||||
# to IPv6. But that's ok, since it will accept IPv4 connections on the IPv6
|
||||
|
|
@ -82,7 +86,8 @@ class BaseDualNetworkedServers:
|
|||
logger.debug(
|
||||
"Successfully bound to %s:%s using %s", new_address[0],
|
||||
new_address[1], "IPv6" if ip_version else "IPv4")
|
||||
except socket.error:
|
||||
except socket.error as e:
|
||||
last_socket_err = e
|
||||
if self.servers:
|
||||
# Already bound using IPv6.
|
||||
logger.debug(
|
||||
|
|
@ -101,7 +106,10 @@ class BaseDualNetworkedServers:
|
|||
# bind to the same port for both servers.
|
||||
port = server.socket.getsockname()[1]
|
||||
if not self.servers:
|
||||
raise socket.error("Could not bind to IPv4 or IPv6.")
|
||||
if last_socket_err:
|
||||
raise last_socket_err
|
||||
else: # pragma: no cover
|
||||
raise socket.error("Could not bind to IPv4 or IPv6.")
|
||||
|
||||
def serve_forever(self):
|
||||
"""Wraps socketserver.TCPServer.serve_forever"""
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'cryptography>=2.1.4',
|
||||
|
|
|
|||
|
|
@ -190,12 +190,18 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
|
|||
|
||||
@mock.patch("socket.socket.bind")
|
||||
def test_fail_to_bind(self, mock_bind):
|
||||
mock_bind.side_effect = socket.error
|
||||
from errno import EADDRINUSE
|
||||
from acme.standalone import BaseDualNetworkedServers
|
||||
self.assertRaises(socket.error, BaseDualNetworkedServers,
|
||||
BaseDualNetworkedServersTest.SingleProtocolServer,
|
||||
('', 0),
|
||||
socketserver.BaseRequestHandler)
|
||||
|
||||
mock_bind.side_effect = socket.error(EADDRINUSE, "Fake addr in use error")
|
||||
|
||||
with self.assertRaises(socket.error) as em:
|
||||
BaseDualNetworkedServers(
|
||||
BaseDualNetworkedServersTest.SingleProtocolServer,
|
||||
('', 0), socketserver.BaseRequestHandler)
|
||||
|
||||
self.assertEqual(em.exception.errno, EADDRINUSE)
|
||||
|
||||
|
||||
def test_ports_equal(self):
|
||||
from acme.standalone import BaseDualNetworkedServers
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ from certbot_apache._internal import override_debian
|
|||
from certbot_apache._internal import override_fedora
|
||||
from certbot_apache._internal import override_gentoo
|
||||
from certbot_apache._internal import override_suse
|
||||
from certbot_apache._internal import override_void
|
||||
|
||||
OVERRIDE_CLASSES = {
|
||||
"arch": override_arch.ArchConfigurator,
|
||||
|
|
@ -35,6 +36,7 @@ OVERRIDE_CLASSES = {
|
|||
"sles": override_suse.OpenSUSEConfigurator,
|
||||
"scientific": override_centos.CentOSConfigurator,
|
||||
"scientific linux": override_centos.CentOSConfigurator,
|
||||
"void": override_void.VoidConfigurator,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -95,10 +95,10 @@ class ApacheHttp01(common.ChallengePerformer):
|
|||
def _mod_config(self):
|
||||
selected_vhosts: List[VirtualHost] = []
|
||||
http_port = str(self.configurator.config.http01_port)
|
||||
|
||||
# Search for VirtualHosts matching by name
|
||||
for chall in self.achalls:
|
||||
# Search for matching VirtualHosts
|
||||
for vh in self._matching_vhosts(chall.domain):
|
||||
selected_vhosts.append(vh)
|
||||
selected_vhosts += self._matching_vhosts(chall.domain)
|
||||
|
||||
# Ensure that we have one or more VirtualHosts that we can continue
|
||||
# with. (one that listens to port configured with --http-01-port)
|
||||
|
|
@ -107,9 +107,13 @@ class ApacheHttp01(common.ChallengePerformer):
|
|||
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)
|
||||
# If there's at least one elgible VirtualHost, also add all unnamed VirtualHosts
|
||||
# because they might match at runtime (#8890)
|
||||
if found:
|
||||
selected_vhosts += self._unnamed_vhosts()
|
||||
# Otherwise, add every Virtualhost which listens on the right port
|
||||
else:
|
||||
selected_vhosts += self._relevant_vhosts()
|
||||
|
||||
# Add the challenge configuration
|
||||
for vh in selected_vhosts:
|
||||
|
|
@ -167,6 +171,10 @@ class ApacheHttp01(common.ChallengePerformer):
|
|||
|
||||
return relevant_vhosts
|
||||
|
||||
def _unnamed_vhosts(self) -> List[VirtualHost]:
|
||||
"""Return all VirtualHost objects with no ServerName"""
|
||||
return [vh for vh in self.configurator.vhosts if vh.name is None]
|
||||
|
||||
def _set_up_challenges(self):
|
||||
if not os.path.isdir(self.challenge_dir):
|
||||
old_umask = filesystem.umask(0o022)
|
||||
|
|
|
|||
|
|
@ -177,8 +177,8 @@ 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:
|
||||
self.variables[k] = defines[k]
|
||||
for k, v in defines.items():
|
||||
self.variables[k] = v
|
||||
|
||||
def not_modssl_ifmodule(self, path):
|
||||
"""Checks if the provided Augeas path has argument !mod_ssl"""
|
||||
|
|
|
|||
|
|
@ -87,5 +87,5 @@ class FedoraParser(parser.ApacheParser):
|
|||
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]
|
||||
for k, v in defines.items():
|
||||
self.variables[k] = v
|
||||
|
|
|
|||
|
|
@ -53,8 +53,8 @@ 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:
|
||||
self.variables[k] = defines[k]
|
||||
for k, v in defines.items():
|
||||
self.variables[k] = v
|
||||
|
||||
def update_modules(self):
|
||||
"""Get loaded modules from httpd process, and add them to DOM"""
|
||||
|
|
|
|||
23
certbot-apache/certbot_apache/_internal/override_void.py
Normal file
23
certbot-apache/certbot_apache/_internal/override_void.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
""" Distribution specific override class for Void Linux """
|
||||
import zope.interface
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot_apache._internal import configurator
|
||||
from certbot_apache._internal.configurator import OsOptions
|
||||
|
||||
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class VoidConfigurator(configurator.ApacheConfigurator):
|
||||
"""Void Linux specific ApacheConfigurator override class"""
|
||||
|
||||
OS_DEFAULTS = OsOptions(
|
||||
server_root="/etc/apache",
|
||||
vhost_root="/etc/apache/extra",
|
||||
vhost_files="*.conf",
|
||||
logs_root="/var/log/httpd",
|
||||
ctl="apachectl",
|
||||
version_cmd=['apachectl', '-v'],
|
||||
restart_cmd=['apachectl', 'graceful'],
|
||||
conftest_cmd=['apachectl', 'configtest'],
|
||||
challenge_location="/etc/apache/extra",
|
||||
)
|
||||
|
|
@ -440,7 +440,11 @@ class ApacheParser:
|
|||
:type args: list or str
|
||||
"""
|
||||
first_dir = aug_conf_path + "/directive[1]"
|
||||
self.aug.insert(first_dir, "directive", True)
|
||||
if self.aug.get(first_dir):
|
||||
self.aug.insert(first_dir, "directive", True)
|
||||
else:
|
||||
self.aug.set(first_dir, "directive")
|
||||
|
||||
self.aug.set(first_dir, dirname)
|
||||
if isinstance(args, list):
|
||||
for i, value in enumerate(args, 1):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# We specify the minimum acme and certbot version as the current plugin
|
||||
|
|
|
|||
|
|
@ -125,6 +125,18 @@ class ApacheHttp01Test(util.ApacheTest):
|
|||
domain="duplicate.example.com", account_key=self.account_key)]
|
||||
self.common_perform_test(achalls, vhosts)
|
||||
|
||||
def test_configure_name_and_blank(self):
|
||||
domain = "certbot.demo"
|
||||
vhosts = [v for v in self.config.vhosts if v.name == domain or v.name is None]
|
||||
achalls = [
|
||||
achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.chall_to_challb(
|
||||
challenges.HTTP01(token=((b'a' * 16))),
|
||||
"pending"),
|
||||
domain=domain, 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)
|
||||
|
|
|
|||
|
|
@ -105,6 +105,11 @@ class BasicParserTest(util.ParserTest):
|
|||
for i, match in enumerate(matches):
|
||||
self.assertEqual(self.parser.aug.get(match), str(i + 1))
|
||||
|
||||
for name in ("empty.conf", "no-directives.conf"):
|
||||
conf = "/files" + os.path.join(self.parser.root, "sites-available", name)
|
||||
self.parser.add_dir_beginning(conf, "AddDirectiveBeginning", "testBegin")
|
||||
self.assertTrue(self.parser.find_dir("AddDirectiveBeginning", "testBegin", conf))
|
||||
|
||||
def test_empty_arg(self):
|
||||
self.assertEqual(None,
|
||||
self.parser.get_arg("/files/whatever/nonexistent"))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<VirtualHost *:80>
|
||||
<Location />
|
||||
Require all denied
|
||||
</Location>
|
||||
</VirtualHost>
|
||||
1988
certbot-auto
1988
certbot-auto
File diff suppressed because it is too large
Load diff
|
|
@ -346,7 +346,8 @@ def test_renew_empty_hook_scripts(context):
|
|||
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()
|
||||
with open(join(hook_dir, 'file'), 'w'):
|
||||
pass
|
||||
context.certbot(['renew'])
|
||||
|
||||
assert_cert_count_for_lineage(context.config_dir, certname, 2)
|
||||
|
|
@ -368,7 +369,8 @@ def test_renew_hook_override(context):
|
|||
assert_hook_execution(context.hook_probe, 'deploy')
|
||||
|
||||
# Now we override all previous hooks during next renew.
|
||||
open(context.hook_probe, 'w').close()
|
||||
with open(context.hook_probe, 'w'):
|
||||
pass
|
||||
context.certbot([
|
||||
'renew', '--cert-name', certname,
|
||||
'--pre-hook', misc.echo('pre_override', context.hook_probe),
|
||||
|
|
@ -387,7 +389,8 @@ def test_renew_hook_override(context):
|
|||
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()
|
||||
with open(context.hook_probe, 'w'):
|
||||
pass
|
||||
context.certbot(['renew', '--cert-name', certname])
|
||||
|
||||
assert_hook_execution(context.hook_probe, 'pre_override')
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class ACMEServer:
|
|||
self._proxy = http_proxy
|
||||
self._workspace = tempfile.mkdtemp()
|
||||
self._processes: List[subprocess.Popen] = []
|
||||
self._stdout = sys.stdout if stdout else open(os.devnull, 'w')
|
||||
self._stdout = sys.stdout if stdout else open(os.devnull, 'w') # pylint: disable=consider-using-with
|
||||
self._dns_server = dns_server
|
||||
self._http_01_port = http_01_port
|
||||
if http_01_port != DEFAULT_HTTP_01_PORT:
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ class DNSServer:
|
|||
|
||||
# Unfortunately the BIND9 image forces everything to stderr with -g and we can't
|
||||
# modify the verbosity.
|
||||
# pylint: disable=consider-using-with
|
||||
self._output = sys.stderr if show_output else open(os.devnull, "w")
|
||||
|
||||
def start(self):
|
||||
|
|
|
|||
|
|
@ -79,11 +79,12 @@ def _get_names(config):
|
|||
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)
|
||||
with open(os.path.join(root, filename)) as f:
|
||||
for line in f:
|
||||
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
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'cloudflare>=1.5.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'python-digitalocean>=1.11', # 1.15.0 or newer is recommended for TTL support
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# This version of lexicon is required to address the problem described in
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'google-api-python-client>=1.5.5',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dnspython',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'boto3',
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import sys
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
|
|
|
|||
|
|
@ -96,8 +96,8 @@ class NginxParser:
|
|||
servers = self._get_raw_servers()
|
||||
|
||||
addr_to_ssl: Dict[Tuple[str, str], bool] = {}
|
||||
for filename in servers:
|
||||
for server, _ in servers[filename]:
|
||||
for server_list in servers.values():
|
||||
for server, _ in server_list:
|
||||
# Parse the server block to save addr info
|
||||
parsed_server = _parse_server_raw(server)
|
||||
for addr in parsed_server['addrs']:
|
||||
|
|
@ -112,8 +112,7 @@ class NginxParser:
|
|||
"""Get a map of unparsed all server blocks
|
||||
"""
|
||||
servers: Dict[str, Union[List, nginxparser.UnspacedList]] = {}
|
||||
for filename in self.parsed:
|
||||
tree = self.parsed[filename]
|
||||
for filename, tree in self.parsed.items():
|
||||
servers[filename] = []
|
||||
srv = servers[filename] # workaround undefined loop var in lambdas
|
||||
|
||||
|
|
@ -141,8 +140,8 @@ class NginxParser:
|
|||
servers = self._get_raw_servers()
|
||||
|
||||
vhosts = []
|
||||
for filename in servers:
|
||||
for server, path in servers[filename]:
|
||||
for filename, server_list in servers.items():
|
||||
for server, path in server_list:
|
||||
# Parse the server block into a VirtualHost object
|
||||
|
||||
parsed_server = _parse_server_raw(server)
|
||||
|
|
@ -240,8 +239,7 @@ class NginxParser:
|
|||
|
||||
"""
|
||||
# Best-effort atomicity is enforced above us by reverter.py
|
||||
for filename in self.parsed:
|
||||
tree = self.parsed[filename]
|
||||
for filename, tree in self.parsed.items():
|
||||
if ext:
|
||||
filename = filename + os.path.extsep + ext
|
||||
if not isinstance(tree, UnspacedList):
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from setuptools import find_packages
|
||||
from setuptools import setup
|
||||
|
||||
version = '1.17.0.dev0'
|
||||
version = '1.18.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
# We specify the minimum acme and certbot version as the current plugin
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## 1.17.0 - master
|
||||
## 1.18.0 - master
|
||||
|
||||
### Added
|
||||
|
||||
|
|
@ -10,12 +10,38 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||
|
||||
### Changed
|
||||
|
||||
* When self-validating HTTP-01 challenges using
|
||||
acme.challenges.HTTP01Response.simple_verify, we now assume that the response
|
||||
is composed of only ASCII characters. Previously we were relying on the
|
||||
default behavior of the requests library which tries to guess the encoding of
|
||||
the response which was error prone.
|
||||
* `acme`: the `.client.Client` and `.client.BackwardsCompatibleClientV2` classes
|
||||
are now deprecated in favor of `.client.ClientV2`.
|
||||
|
||||
### Fixed
|
||||
|
||||
* The Apache authenticator no longer crashes with "Unable to insert label"
|
||||
when encountering a completely empty vhost. This issue affected Certbot 1.17.0.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 1.17.0 - 2021-07-06
|
||||
|
||||
### Added
|
||||
|
||||
* Add Void Linux overrides for certbot-apache.
|
||||
|
||||
### Changed
|
||||
|
||||
* We changed how dependencies are specified between Certbot packages. For this
|
||||
and future releases, higher level Certbot components will require that lower
|
||||
level components are the same version or newer. More specifically, version X
|
||||
of the Certbot package will now always require acme>=X and version Y of a
|
||||
plugin package will always require acme>=Y and certbot=>Y. Specifying
|
||||
dependencies in this way simplifies testing and development.
|
||||
* The Apache authenticator now always configures virtual hosts which do not have
|
||||
an explicit `ServerName`. This should make it work more reliably with the
|
||||
default Apache configuration in Debian-based environments.
|
||||
|
||||
### Fixed
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
"""Certbot client."""
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '1.17.0.dev0'
|
||||
__version__ = '1.18.0.dev0'
|
||||
|
|
|
|||
|
|
@ -311,8 +311,8 @@ class AccountFileStorage(interfaces.AccountStorage):
|
|||
|
||||
# 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
|
||||
for k, v in constants.LE_REUSE_SERVERS.items():
|
||||
reused_servers[v] = k
|
||||
|
||||
# is there a next one up?
|
||||
possible_next_link = True
|
||||
|
|
|
|||
|
|
@ -40,9 +40,9 @@ from certbot._internal.cli.plugins_parsing import _plugins_parsing
|
|||
from certbot._internal.cli.subparsers import _create_subparsers
|
||||
from certbot._internal.cli.verb_help import VERB_HELP
|
||||
from certbot._internal.cli.verb_help import VERB_HELP_MAP
|
||||
from certbot.plugins import enhancements
|
||||
from certbot._internal.plugins import disco as plugins_disco
|
||||
import certbot._internal.plugins.selection as plugin_selection
|
||||
import certbot.plugins.enhancements as enhancements
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import datetime
|
|||
import logging
|
||||
import platform
|
||||
from typing import List, Optional, Union
|
||||
import warnings
|
||||
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
# See https://github.com/pyca/cryptography/issues/4275
|
||||
|
|
@ -32,13 +33,23 @@ from certbot.display import util as display_util
|
|||
|
||||
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)
|
||||
|
||||
with warnings.catch_warnings():
|
||||
# TODO: full removal of ACMEv1 support: https://github.com/certbot/certbot/issues/6844
|
||||
warnings.simplefilter("ignore", PendingDeprecationWarning)
|
||||
|
||||
client = acme_client.BackwardsCompatibleClientV2(net, key, config.server)
|
||||
if client.acme_version == 1:
|
||||
logger.warning(
|
||||
"Certbot is configured to use an ACMEv1 server (%s). ACMEv1 support is deprecated"
|
||||
" and will soon be removed. See https://community.letsencrypt.org/t/143839 for "
|
||||
"more information.", config.server)
|
||||
return client
|
||||
|
||||
|
||||
def determine_user_agent(config):
|
||||
|
|
|
|||
|
|
@ -139,8 +139,8 @@ class ErrorHandler:
|
|||
|
||||
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])
|
||||
for signum, handler in self.prev_handlers.items():
|
||||
signal.signal(signum, handler)
|
||||
self.prev_handlers.clear()
|
||||
|
||||
def _signal_handler(self, signum, unused_frame):
|
||||
|
|
|
|||
|
|
@ -507,6 +507,13 @@ def _report_next_steps(config: interfaces.IConfig, installer_err: Optional[error
|
|||
"Certificates created using --csr will not be renewed automatically by Certbot. "
|
||||
"You will need to renew the certificate before it expires, by running the same "
|
||||
"Certbot command again.")
|
||||
elif _is_interactive_only_auth(config):
|
||||
steps.append(
|
||||
"This certificate will not be renewed automatically. Autorenewal of "
|
||||
"--manual certificates requires the use of an authentication hook script "
|
||||
"(--manual-auth-hook) but one was not provided. To renew this certificate, repeat "
|
||||
f"this same {cli.cli_command} command before the certificate's expiry date."
|
||||
)
|
||||
elif not config.preconfigured_renewal:
|
||||
steps.append(
|
||||
"The certificate will need to be renewed before it expires. Certbot can "
|
||||
|
|
@ -556,6 +563,11 @@ def _report_new_cert(config, cert_path, fullchain_path, key_path=None):
|
|||
|
||||
assert cert_path and fullchain_path, "No certificates saved to report."
|
||||
|
||||
renewal_msg = ""
|
||||
if config.preconfigured_renewal and not _is_interactive_only_auth(config):
|
||||
renewal_msg = ("\nCertbot has set up a scheduled task to automatically renew this "
|
||||
"certificate in the background.")
|
||||
|
||||
display_util.notify(
|
||||
("\nSuccessfully received certificate.\n"
|
||||
"Certificate is saved at: {cert_path}\n{key_msg}"
|
||||
|
|
@ -564,13 +576,22 @@ def _report_new_cert(config, cert_path, fullchain_path, key_path=None):
|
|||
cert_path=fullchain_path,
|
||||
expiry=crypto_util.notAfter(cert_path).date(),
|
||||
key_msg="Key is saved at: {}\n".format(key_path) if key_path else "",
|
||||
renewal_msg="\nCertbot has set up a scheduled task to automatically renew this "
|
||||
"certificate in the background." if config.preconfigured_renewal else "",
|
||||
renewal_msg=renewal_msg,
|
||||
nl="\n" if config.verb == "run" else "" # Normalize spacing across verbs
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _is_interactive_only_auth(config: interfaces.IConfig) -> bool:
|
||||
""" Whether the current authenticator params only support interactive renewal.
|
||||
"""
|
||||
# --manual without --manual-auth-hook can never autorenew
|
||||
if config.authenticator == "manual" and config.manual_auth_hook is None:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _csr_report_new_cert(config: interfaces.IConfig, cert_path: Optional[str],
|
||||
chain_path: Optional[str], fullchain_path: Optional[str]):
|
||||
""" --csr variant of _report_new_cert.
|
||||
|
|
@ -1398,7 +1419,7 @@ def certonly(config, plugins):
|
|||
if config.csr:
|
||||
cert_path, chain_path, fullchain_path = _csr_get_and_save_cert(config, le_client)
|
||||
_csr_report_new_cert(config, cert_path, chain_path, fullchain_path)
|
||||
_report_next_steps(config, None, None)
|
||||
_report_next_steps(config, None, None, new_or_renewed_cert=not config.dry_run)
|
||||
_suggest_donation_if_appropriate(config)
|
||||
eff.handle_subscription(config, le_client.account)
|
||||
return
|
||||
|
|
@ -1417,7 +1438,8 @@ def certonly(config, plugins):
|
|||
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)
|
||||
_report_next_steps(config, None, lineage, new_or_renewed_cert=should_get_cert)
|
||||
_report_next_steps(config, None, lineage,
|
||||
new_or_renewed_cert=should_get_cert and not config.dry_run)
|
||||
_suggest_donation_if_appropriate(config)
|
||||
eff.handle_subscription(config, le_client.account)
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import logging
|
|||
import socket
|
||||
from typing import DefaultDict
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import TYPE_CHECKING
|
||||
|
|
@ -184,6 +185,14 @@ class Authenticator(common.Plugin):
|
|||
if not self.served[servers]:
|
||||
self.servers.stop(port)
|
||||
|
||||
def auth_hint(self, failed_achalls: List[achallenges.AnnotatedChallenge]) -> str:
|
||||
port, addr = self.config.http01_port, self.config.http01_address
|
||||
neat_addr = f"{addr}:{port}" if addr else f"port {port}"
|
||||
return ("The Certificate Authority failed to download the challenge files from "
|
||||
f"the temporary standalone webserver started by Certbot on {neat_addr}. "
|
||||
"Ensure that the listed domains point to this machine and that it can "
|
||||
"accept inbound connections from the internet.")
|
||||
|
||||
|
||||
def _handle_perform_error(error):
|
||||
if error.socket_error.errno == errno.EACCES:
|
||||
|
|
|
|||
|
|
@ -144,7 +144,8 @@ def write_renewal_config(o_filename, n_filename, archive_dir, target, relevant_d
|
|||
logger.debug("Writing new config %s.", n_filename)
|
||||
|
||||
# Ensure that the file exists
|
||||
open(n_filename, 'a').close()
|
||||
with open(n_filename, 'a'):
|
||||
pass
|
||||
|
||||
# Copy permissions from the old version of the file, if it exists.
|
||||
if os.path.exists(o_filename):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ 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
|
||||
from certbot.plugins import enhancements
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
|
|||
|
|
@ -217,9 +217,9 @@ def _choose_names_manually(prompt_prefix=""):
|
|||
retry_message = (
|
||||
"One or more of the entered domain names was not valid:"
|
||||
"{0}{0}").format(os.linesep)
|
||||
for domain in invalid_domains:
|
||||
for invalid_domain, err in invalid_domains.items():
|
||||
retry_message = retry_message + "{1}: {2}{0}".format(
|
||||
os.linesep, domain, invalid_domains[domain])
|
||||
os.linesep, invalid_domain, err)
|
||||
retry_message = retry_message + (
|
||||
"{0}Would you like to re-enter the names?{0}").format(
|
||||
os.linesep)
|
||||
|
|
|
|||
|
|
@ -549,7 +549,8 @@ class IReporter(zope.interface.Interface):
|
|||
class RenewableCert(object, metaclass=abc.ABCMeta):
|
||||
"""Interface to a certificate lineage."""
|
||||
|
||||
@abc.abstractproperty
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def cert_path(self):
|
||||
"""Path to the certificate file.
|
||||
|
||||
|
|
@ -557,7 +558,8 @@ class RenewableCert(object, metaclass=abc.ABCMeta):
|
|||
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def key_path(self):
|
||||
"""Path to the private key file.
|
||||
|
||||
|
|
@ -565,7 +567,8 @@ class RenewableCert(object, metaclass=abc.ABCMeta):
|
|||
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def chain_path(self):
|
||||
"""Path to the certificate chain file.
|
||||
|
||||
|
|
@ -573,7 +576,8 @@ class RenewableCert(object, metaclass=abc.ABCMeta):
|
|||
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def fullchain_path(self):
|
||||
"""Path to the full chain file.
|
||||
|
||||
|
|
@ -583,7 +587,8 @@ class RenewableCert(object, metaclass=abc.ABCMeta):
|
|||
|
||||
"""
|
||||
|
||||
@abc.abstractproperty
|
||||
@property
|
||||
@abc.abstractmethod
|
||||
def lineagename(self):
|
||||
"""Name given to the certificate lineage.
|
||||
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ class DNSAuthenticator(common.Plugin):
|
|||
indicate any issue.
|
||||
"""
|
||||
|
||||
def __validator(filename):
|
||||
def __validator(filename): # pylint: disable=unused-private-member
|
||||
configuration = CredentialsConfiguration(filename, self.dest)
|
||||
|
||||
if required_variables:
|
||||
|
|
@ -199,7 +199,7 @@ class DNSAuthenticator(common.Plugin):
|
|||
:rtype: str
|
||||
"""
|
||||
|
||||
def __validator(i):
|
||||
def __validator(i): # pylint: disable=unused-private-member
|
||||
if not i:
|
||||
raise errors.PluginError('Please enter your {0}.'.format(label))
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ class DNSAuthenticator(common.Plugin):
|
|||
:rtype: str
|
||||
"""
|
||||
|
||||
def __validator(filename):
|
||||
def __validator(filename): # pylint: disable=unused-private-member
|
||||
if not filename:
|
||||
raise errors.PluginError('Please enter a valid path to your {0}.'.format(label))
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ optional arguments:
|
|||
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:
|
||||
-3)
|
||||
0)
|
||||
--max-log-backups MAX_LOG_BACKUPS
|
||||
Specifies the maximum number of backup logs that
|
||||
should be kept by Certbot's built in log rotation.
|
||||
|
|
@ -118,7 +118,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.16.0 (certbot;
|
||||
"". (default: CertbotACMEClient/1.17.0 (certbot;
|
||||
OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY
|
||||
(SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).
|
||||
The flags encoded in the user agent are: --duplicate,
|
||||
|
|
|
|||
|
|
@ -57,10 +57,11 @@ standalone_ Y N | Uses a "standalone" webserver to obtain a certificate.
|
|||
| domain. Doing domain validation in this way is
|
||||
| the only way to obtain wildcard certificates from Let's
|
||||
| Encrypt.
|
||||
manual_ Y N | Helps you obtain a certificate by giving you instructions to http-01_ (80) or
|
||||
| perform domain validation yourself. Additionally allows you dns-01_ (53)
|
||||
| to specify scripts to automate the validation task in a
|
||||
| customized way.
|
||||
manual_ Y N | Obtain a certificate by manually following instructions to http-01_ (80) or
|
||||
| perform domain validation yourself. Certificates created this dns-01_ (53)
|
||||
| way do not support autorenewal.
|
||||
| Autorenewal may be enabled by providing an authentication
|
||||
| hook script to automate the domain validation steps.
|
||||
=========== ==== ==== =============================================================== =============================
|
||||
|
||||
.. |dns_plugs| replace:: :ref:`DNS plugins <dns_plugins>`
|
||||
|
|
@ -229,11 +230,21 @@ For example, for the domain ``example.com``, a zone file entry would look like:
|
|||
|
||||
_acme-challenge.example.com. 300 IN TXT "gfj9Xq...Rg85nM"
|
||||
|
||||
.. _manual-renewal:
|
||||
|
||||
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.
|
||||
**Renewal with the manual plugin**
|
||||
|
||||
Certificates created using ``--manual`` **do not** support automatic renewal unless
|
||||
combined with an `authentication hook script <#hooks>`_ via ``--manual-auth-hook``
|
||||
to automatically set up the required HTTP and/or TXT challenges.
|
||||
|
||||
If you can use one of the other plugins_ which support autorenewal to create
|
||||
your certificate, doing so is highly recommended.
|
||||
|
||||
To manually renew a certificate using ``--manual`` without hooks, repeat the same
|
||||
``certbot --manual`` command you used to create the certificate originally. As this
|
||||
will require you to copy and paste new HTTP files or DNS TXT records, the command
|
||||
cannot be automated with a cron job.
|
||||
|
||||
.. _combination:
|
||||
|
||||
|
|
@ -286,6 +297,10 @@ dns-lightsail_ Y N DNS Authentication using Amazon Lightsail DNS API
|
|||
dns-inwx_ Y Y DNS Authentication for INWX through the XML API
|
||||
dns-azure_ Y N DNS Authentication using Azure DNS
|
||||
dns-godaddy_ Y N DNS Authentication using Godaddy DNS
|
||||
njalla_ Y N DNS Authentication for njalla
|
||||
DuckDNS_ Y N DNS Authentication for DuckDNS
|
||||
Porkbun_ Y N DNS Authentication for Porkbun
|
||||
Infomaniak_ Y N DNS Authentication using Infomaniak Domains API
|
||||
================== ==== ==== ===============================================================
|
||||
|
||||
.. _haproxy: https://github.com/greenhost/certbot-haproxy
|
||||
|
|
@ -302,6 +317,10 @@ dns-godaddy_ Y N DNS Authentication using Godaddy DNS
|
|||
.. _dns-inwx: https://github.com/oGGy990/certbot-dns-inwx/
|
||||
.. _dns-azure: https://github.com/binkhq/certbot-dns-azure
|
||||
.. _dns-godaddy: https://github.com/miigotu/certbot-dns-godaddy
|
||||
.. _njalla: https://github.com/chaptergy/certbot-dns-njalla
|
||||
.. _DuckDNS: https://github.com/infinityofspace/certbot_dns_duckdns
|
||||
.. _Porkbun: https://github.com/infinityofspace/certbot_dns_porkbun
|
||||
.. _Infomaniak: https://github.com/Infomaniak/certbot-dns-infomaniak
|
||||
|
||||
If you're interested, you can also :ref:`write your own plugin <dev-plugin>`.
|
||||
|
||||
|
|
@ -522,6 +541,10 @@ Renewing certificates
|
|||
.. seealso:: Most Certbot installations come with automatic
|
||||
renewal out of the box. See `Automated Renewals`_ for more details.
|
||||
|
||||
.. seealso:: Users of the `Manual`_ plugin should note that ``--manual`` certificates
|
||||
will not renew automatically, unless combined with authentication hook scripts.
|
||||
See `Renewal with the manual plugin <#manual-renewal>`_.
|
||||
|
||||
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
|
||||
|
|
@ -710,7 +733,7 @@ Setting up automated renewal
|
|||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
If you think you may need to set up automated renewal, follow these instructions to set up a
|
||||
scheduled task to automatically renew your certificates in the background. If you are unsure
|
||||
scheduled task to automatically renew your certificates in the background. If you are unsure
|
||||
whether your system has a pre-installed scheduled task for Certbot, it is safe to follow these
|
||||
instructions to create one.
|
||||
|
||||
|
|
|
|||
|
|
@ -271,6 +271,21 @@ class CertonlyTest(unittest.TestCase):
|
|||
self._call(('certonly --webroot --cert-name example.com').split())
|
||||
self.assertIs(mock_choose_names.called, True)
|
||||
|
||||
@mock.patch('certbot._internal.main._report_next_steps')
|
||||
@mock.patch('certbot._internal.main._get_and_save_cert')
|
||||
@mock.patch('certbot._internal.main._csr_get_and_save_cert')
|
||||
@mock.patch('certbot._internal.cert_manager.lineage_for_certname')
|
||||
def test_dryrun_next_steps_no_cert_saved(self, mock_lineage, mock_csr_get_cert,
|
||||
unused_mock_get_cert, mock_report_next_steps):
|
||||
"""certonly --dry-run shouldn't report creation of a certificate in NEXT STEPS."""
|
||||
mock_lineage.return_value = None
|
||||
mock_csr_get_cert.return_value = ("/cert", "/chain", "/fullchain")
|
||||
for flag in (f"--csr {CSR}", "-d example.com"):
|
||||
self._call(f"certonly {flag} --webroot --cert-name example.com --dry-run".split())
|
||||
mock_report_next_steps.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY, new_or_renewed_cert=False)
|
||||
mock_report_next_steps.reset_mock()
|
||||
|
||||
|
||||
class FindDomainsOrCertnameTest(unittest.TestCase):
|
||||
"""Tests for certbot._internal.main._find_domains_or_certname."""
|
||||
|
|
@ -1886,6 +1901,71 @@ class ReportNewCertTest(unittest.TestCase):
|
|||
'This certificate expires on 1970-01-01.'
|
||||
)
|
||||
|
||||
def test_manual_no_hooks_report(self):
|
||||
"""Shouldn't get a message about autorenewal if no --manual-auth-hook"""
|
||||
self._call(mock.Mock(dry_run=False, authenticator='manual', manual_auth_hook=None),
|
||||
'/path/to/cert.pem', '/path/to/fullchain.pem',
|
||||
'/path/to/privkey.pem')
|
||||
|
||||
self.mock_notify.assert_called_with(
|
||||
'\nSuccessfully received certificate.\n'
|
||||
'Certificate is saved at: /path/to/fullchain.pem\n'
|
||||
'Key is saved at: /path/to/privkey.pem\n'
|
||||
'This certificate expires on 1970-01-01.\n'
|
||||
'These files will be updated when the certificate renews.'
|
||||
)
|
||||
|
||||
|
||||
class ReportNextStepsTest(unittest.TestCase):
|
||||
"""Tests for certbot._internal.main._report_next_steps"""
|
||||
|
||||
def setUp(self):
|
||||
self.config = mock.MagicMock(
|
||||
cert_name="example.com", preconfigured_renewal=True,
|
||||
csr=None, authenticator="nginx", manual_auth_hook=None)
|
||||
notify_patch = mock.patch('certbot._internal.main.display_util.notify')
|
||||
self.mock_notify = notify_patch.start()
|
||||
self.addCleanup(notify_patch.stop)
|
||||
self.old_stdout = sys.stdout
|
||||
sys.stdout = io.StringIO()
|
||||
|
||||
def tearDown(self):
|
||||
sys.stdout = self.old_stdout
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot._internal.main import _report_next_steps
|
||||
_report_next_steps(*args, **kwargs)
|
||||
|
||||
def _output(self) -> str:
|
||||
self.mock_notify.assert_called_once()
|
||||
return self.mock_notify.call_args_list[0][0][0]
|
||||
|
||||
def test_report(self):
|
||||
"""No steps for a normal renewal"""
|
||||
self.config.authenticator = "manual"
|
||||
self.config.manual_auth_hook = "/bin/true"
|
||||
self._call(self.config, None, None)
|
||||
self.mock_notify.assert_not_called()
|
||||
|
||||
def test_csr_report(self):
|
||||
"""--csr requires manual renewal"""
|
||||
self.config.csr = "foo.csr"
|
||||
self._call(self.config, None, None)
|
||||
self.assertIn("--csr will not be renewed", self._output())
|
||||
|
||||
def test_manual_no_hook_renewal(self):
|
||||
"""--manual without a hook requires manual renewal"""
|
||||
self.config.authenticator = "manual"
|
||||
self._call(self.config, None, None)
|
||||
self.assertIn("--manual certificates requires", self._output())
|
||||
|
||||
def test_no_preconfigured_renewal(self):
|
||||
"""No --preconfigured-renewal needs manual cron setup"""
|
||||
self.config.preconfigured_renewal = False
|
||||
self._call(self.config, None, None)
|
||||
self.assertIn("https://certbot.org/renewal-setup", self._output())
|
||||
|
||||
|
||||
class UpdateAccountTest(test_util.ConfigTestCase):
|
||||
"""Tests for certbot._internal.main.update_account"""
|
||||
|
|
|
|||
|
|
@ -177,6 +177,13 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
"server1": set(), "server2": set()})
|
||||
self.auth.servers.stop.assert_called_with(2)
|
||||
|
||||
def test_auth_hint(self):
|
||||
self.config.http01_port = "80"
|
||||
self.config.http01_address = None
|
||||
self.assertIn("on port 80", self.auth.auth_hint([]))
|
||||
self.config.http01_address = "127.0.0.1"
|
||||
self.assertIn("on 127.0.0.1:80", self.auth.auth_hint([]))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
1988
letsencrypt-auto
1988
letsencrypt-auto
File diff suppressed because it is too large
Load diff
|
|
@ -1,11 +0,0 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAmBsmUkACgkQTRfJlc2X
|
||||
dfI7Bwf9FkNrf1HEh2G3uk1p+qLMd/s5kcVV2udK2FkRELee5nHlLZx2YmHA/8ID
|
||||
gqsk8EsyRZNMX374nGrPm0syykdEsyVtMJTbHCEr+Ms3l54ZgE3HV6ywnhWSlAFo
|
||||
Za50kdzhodBVTS5AEADbCKLKObVAWwO3fFKtKyv/iY29ykpHK0KSHCKRII3iQU7l
|
||||
dnR6u35Z0wgfEmDxsH27K6uo0YepZaEL70qHHFk93MhCh9Z15rO17gRpsVzz7Z1j
|
||||
YClI6h2K/VOfZtbkoQvoks7s+xd75Kjr3GNH+cznkJx8gNWSZLfkc1XX4Bjdm4GG
|
||||
IWz3Ezy8tFg6PtITb7y+aIg75kWx4w==
|
||||
=zEy4
|
||||
-----END PGP SIGNATURE-----
|
||||
|
|
@ -10,11 +10,6 @@ import os
|
|||
# taken from our v1.14.0 tag which was the last release we intended to make
|
||||
# changes to certbot-auto.
|
||||
#
|
||||
# certbot-auto, letsencrypt-auto, and letsencrypt-auto-source/certbot-auto.asc
|
||||
# can be removed from this dict after coordinating with tech ops to ensure we
|
||||
# get the behavior we want from https://dl.eff.org. See
|
||||
# https://github.com/certbot/certbot/issues/8742 for more info.
|
||||
#
|
||||
# Deleting letsencrypt-auto-source/letsencrypt-auto and
|
||||
# letsencrypt-auto-source/letsencrypt-auto.sig can be done once we're
|
||||
# comfortable breaking any certbot-auto scripts that haven't already updated to
|
||||
|
|
@ -22,14 +17,8 @@ import os
|
|||
# https://opensource.eff.org/eff-open-source/pl/65geri7c4tr6iqunc1rpb3mpna for
|
||||
# more info.
|
||||
EXPECTED_FILES = {
|
||||
'certbot-auto':
|
||||
'b997e3608526650a08e36e682fc3bf0c29903c06fa5ba4cc49308c43832450c2',
|
||||
'letsencrypt-auto':
|
||||
'b997e3608526650a08e36e682fc3bf0c29903c06fa5ba4cc49308c43832450c2',
|
||||
os.path.join('letsencrypt-auto-source', 'letsencrypt-auto'):
|
||||
'b997e3608526650a08e36e682fc3bf0c29903c06fa5ba4cc49308c43832450c2',
|
||||
os.path.join('letsencrypt-auto-source', 'certbot-auto.asc'):
|
||||
'0558ba7bd816732b38c092e8fedb6033dad01f263e290ec6b946263aaf6625a8',
|
||||
os.path.join('letsencrypt-auto-source', 'letsencrypt-auto.sig'):
|
||||
'61c036aabf75da350b0633da1b2bef0260303921ecda993455ea5e6d3af3b2fe',
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue