mirror of
https://github.com/certbot/certbot.git
synced 2026-07-30 09:54:33 +02:00
Merge branch 'master' into nginx-utf8
This commit is contained in:
+33
-4
@@ -2,7 +2,32 @@
|
||||
|
||||
Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## 1.3.0 - master
|
||||
## 1.4.0 - master
|
||||
|
||||
### Added
|
||||
|
||||
* Added serial number of certificate to the output of `certbot certificates`
|
||||
* Expose two new environment variables in the authenticator and cleanup scripts used by
|
||||
the `manual` plugin: `CERTBOT_REMAINING_CHALLENGES` is equal to the number of challenges
|
||||
remaining after the current challenge, `CERTBOT_ALL_DOMAINS` is a comma-separated list
|
||||
of all domains challenged for the current certificate.
|
||||
* Added TLS-ALPN-01 challenge support in the `acme` library. Support of this
|
||||
challenge in the Certbot client is planned to be added in a future release.
|
||||
|
||||
### Changed
|
||||
|
||||
*
|
||||
|
||||
### Fixed
|
||||
|
||||
* When using an RFC 8555 compliant endpoint, the `acme` library no longer sends the
|
||||
`resource` field in any requests or the `type` field when responding to challenges.
|
||||
* Fix nginx plugin crash when non-ASCII configuration file is being read (instead,
|
||||
the user will be warned that UTF-8 must be used).
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
## 1.3.0 - 2020-03-03
|
||||
|
||||
### Added
|
||||
|
||||
@@ -10,14 +35,19 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
determine the OCSP status of certificates.
|
||||
* Don't verify the existing certificate in HTTP01Response.simple_verify, for
|
||||
compatibility with the real-world ACME challenge checks.
|
||||
* Added support for `$hostname` in nginx `server_name` directive
|
||||
|
||||
### Changed
|
||||
|
||||
* certbot._internal.cli is now a package split in submodules instead of a whole module.
|
||||
* Certbot will now renew certificates early if they have been revoked according
|
||||
to OCSP.
|
||||
* Fix acme module warnings when response Content-Type includes params (e.g. charset).
|
||||
* Fixed issue where webroot plugin would incorrectly raise `Read-only file system`
|
||||
error when creating challenge directories (issue #7165).
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix nginx plugin crash when non-ASCII configuration file is being read
|
||||
* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
@@ -26,7 +56,6 @@ More details about these changes can be found on our GitHub repo.
|
||||
### Added
|
||||
|
||||
* Added support for Cloudflare's limited-scope API Tokens
|
||||
* Added support for `$hostname` in nginx `server_name` directive
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
+1
-5
@@ -71,16 +71,12 @@ ACME spec: http://ietf-wg-acme.github.io/acme/
|
||||
|
||||
ACME working area in github: https://github.com/ietf-wg-acme/acme
|
||||
|
||||
|build-status| |coverage| |container|
|
||||
|build-status| |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
|
||||
|
||||
.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status
|
||||
:target: https://quay.io/repository/letsencrypt/letsencrypt
|
||||
:alt: Docker Repository on Quay.io
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Certbot client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '1.3.0.dev0'
|
||||
__version__ = '1.4.0.dev0'
|
||||
|
||||
@@ -276,12 +276,15 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False):
|
||||
status = "VALID: {0} days".format(diff.days)
|
||||
|
||||
valid_string = "{0} ({1})".format(cert.target_expiry, status)
|
||||
serial = format(crypto_util.get_serial_from_cert(cert.cert_path), 'x')
|
||||
certinfo.append(" Certificate Name: {0}\n"
|
||||
" Domains: {1}\n"
|
||||
" Expiry Date: {2}\n"
|
||||
" Certificate Path: {3}\n"
|
||||
" Private Key Path: {4}".format(
|
||||
" Serial Number: {1}\n"
|
||||
" Domains: {2}\n"
|
||||
" Expiry Date: {3}\n"
|
||||
" Certificate Path: {4}\n"
|
||||
" Private Key Path: {5}".format(
|
||||
cert.lineagename,
|
||||
serial,
|
||||
" ".join(cert.names()),
|
||||
valid_string,
|
||||
cert.fullchain,
|
||||
|
||||
@@ -394,7 +394,7 @@ def _find_domains_or_certname(config, installer, question=None):
|
||||
:param installer: Installer object
|
||||
:type installer: interfaces.IInstaller
|
||||
|
||||
:param `str` question: Overriding dialog question to ask the user if asked
|
||||
:param `str` question: Overriding default question to ask the user if asked
|
||||
to choose from domain names.
|
||||
|
||||
:returns: Two-part tuple of domains and certname
|
||||
|
||||
@@ -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 <root>")
|
||||
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
|
||||
@@ -35,7 +35,11 @@ class Authenticator(common.Plugin):
|
||||
'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.')
|
||||
'$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth script.'
|
||||
'For both authenticator and cleanup script, on HTTP-01 and DNS-01 challenges,'
|
||||
'$CERTBOT_REMAINING_CHALLENGES will be equal to the number of challenges that '
|
||||
'remain after the current one, and $CERTBOT_ALL_DOMAINS contains a comma-separated '
|
||||
'list of all domains that are challenged for the current certificate.')
|
||||
_DNS_INSTRUCTIONS = """\
|
||||
Please deploy a DNS TXT record under the name
|
||||
{domain} with the following value:
|
||||
@@ -109,14 +113,13 @@ permitted by DNS standards.)
|
||||
|
||||
def perform(self, achalls): # pylint: disable=missing-function-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)
|
||||
if self.conf('auth-hook'):
|
||||
self._perform_achall_with_script(achall, achalls)
|
||||
else:
|
||||
self._perform_achall_manually(achall)
|
||||
responses.append(achall.response(achall.account_key))
|
||||
return responses
|
||||
|
||||
@@ -134,9 +137,11 @@ permitted by DNS standards.)
|
||||
else:
|
||||
raise errors.PluginError('Must agree to IP logging to proceed')
|
||||
|
||||
def _perform_achall_with_script(self, achall):
|
||||
def _perform_achall_with_script(self, achall, achalls):
|
||||
env = dict(CERTBOT_DOMAIN=achall.domain,
|
||||
CERTBOT_VALIDATION=achall.validation(achall.account_key))
|
||||
CERTBOT_VALIDATION=achall.validation(achall.account_key),
|
||||
CERTBOT_ALL_DOMAINS=','.join(one_achall.domain for one_achall in achalls),
|
||||
CERTBOT_REMAINING_CHALLENGES=str(len(achalls) - achalls.index(achall) - 1))
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
env['CERTBOT_TOKEN'] = achall.chall.encode('token')
|
||||
else:
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"""Webroot plugin."""
|
||||
import argparse
|
||||
import collections
|
||||
import errno
|
||||
import json
|
||||
import logging
|
||||
|
||||
@@ -71,7 +70,7 @@ to serve all files under specified web root ({0})."""
|
||||
super(Authenticator, self).__init__(*args, **kwargs)
|
||||
self.full_roots = {} # type: Dict[str, str]
|
||||
self.performed = collections.defaultdict(set) \
|
||||
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
|
||||
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
|
||||
# stack of dirs successfully created by this authenticator
|
||||
self._created_dirs = [] # type: List[str]
|
||||
|
||||
@@ -137,7 +136,7 @@ to serve all files under specified web root ({0})."""
|
||||
"webroot when using the webroot plugin.")
|
||||
return None if index == 0 else known_webroots[index - 1] # code == display_util.OK
|
||||
|
||||
def _prompt_for_new_webroot(self, domain, allowraise=False):
|
||||
def _prompt_for_new_webroot(self, domain, allowraise=False): # pylint: no-self-use
|
||||
code, webroot = ops.validated_directory(
|
||||
_validate_webroot,
|
||||
"Input the webroot for {0}:".format(domain),
|
||||
@@ -170,6 +169,10 @@ to serve all files under specified web root ({0})."""
|
||||
# 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):
|
||||
if os.path.isdir(prefix):
|
||||
# Don't try to create directory if it already exists, as some filesystems
|
||||
# won't reliably raise EEXIST or EISDIR if directory exists.
|
||||
continue
|
||||
try:
|
||||
# Set owner as parent directory if possible, apply mode for Linux/Windows.
|
||||
# For Linux, this is coupled with the "umask" call above because
|
||||
@@ -184,14 +187,13 @@ to serve all files under specified web root ({0})."""
|
||||
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))
|
||||
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):
|
||||
def _get_validation_path(self, root_path, achall): # pylint: no-self-use
|
||||
return os.path.join(root_path, achall.chall.encode("token"))
|
||||
|
||||
def _perform_single(self, achall):
|
||||
|
||||
@@ -15,6 +15,7 @@ import certbot
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import ocsp
|
||||
from certbot import util
|
||||
from certbot._internal import cli
|
||||
from certbot._internal import constants
|
||||
@@ -882,27 +883,33 @@ class RenewableCert(interfaces.RenewableCert):
|
||||
with open(target) as f:
|
||||
return crypto_util.get_names_from_cert(f.read())
|
||||
|
||||
def ocsp_revoked(self, version=None):
|
||||
# pylint: disable=unused-argument
|
||||
def ocsp_revoked(self, version):
|
||||
"""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.
|
||||
Also returns True if the cert version is declared as revoked
|
||||
according to OCSP. If OCSP status could not be determined, False
|
||||
is returned.
|
||||
|
||||
:param int version: the desired version number
|
||||
|
||||
:returns: whether the certificate is or will be revoked
|
||||
:returns: True if the certificate is revoked, otherwise, False
|
||||
: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
|
||||
cert_path = self.version("cert", version)
|
||||
chain_path = self.version("chain", version)
|
||||
# While the RevocationChecker should return False if it failed to
|
||||
# determine the OCSP status, let's ensure we don't crash Certbot by
|
||||
# catching all exceptions here.
|
||||
try:
|
||||
return ocsp.RevocationChecker().ocsp_revoked_by_paths(cert_path,
|
||||
chain_path)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.warning(
|
||||
"An error occurred determining the OCSP status of %s.",
|
||||
cert_path)
|
||||
logger.debug(str(e))
|
||||
return False
|
||||
|
||||
def autorenewal_is_enabled(self):
|
||||
"""Is automatic renewal enabled for this cert?
|
||||
|
||||
@@ -491,3 +491,17 @@ def cert_and_chain_from_fullchain(fullchain_pem):
|
||||
crypto.load_certificate(crypto.FILETYPE_PEM, fullchain_pem)).decode()
|
||||
chain = fullchain_pem[len(cert):].lstrip()
|
||||
return (cert, chain)
|
||||
|
||||
def get_serial_from_cert(cert_path):
|
||||
"""Retrieve the serial number of a certificate from certificate path
|
||||
|
||||
:param str cert_path: path to a cert in PEM format
|
||||
|
||||
:returns: serial number of the certificate
|
||||
:rtype: int
|
||||
"""
|
||||
# pylint: disable=redefined-outer-name
|
||||
with open(cert_path) as f:
|
||||
x509 = crypto.load_certificate(crypto.FILETYPE_PEM,
|
||||
f.read())
|
||||
return x509.get_serial_number()
|
||||
|
||||
@@ -107,7 +107,7 @@ def choose_names(installer, question=None):
|
||||
:param installer: An installer object
|
||||
:type installer: :class:`certbot.interfaces.IInstaller`
|
||||
|
||||
:param `str` question: Overriding dialog question to ask the user if asked
|
||||
:param `str` question: Overriding default question to ask the user if asked
|
||||
to choose from domain names.
|
||||
|
||||
:returns: List of selected names
|
||||
|
||||
+12
-1
@@ -68,8 +68,19 @@ class RevocationChecker(object):
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
cert_path, chain_path = cert.cert_path, cert.chain_path
|
||||
return self.ocsp_revoked_by_paths(cert.cert_path, cert.chain_path)
|
||||
|
||||
def ocsp_revoked_by_paths(self, cert_path, chain_path):
|
||||
# type: (str, str) -> bool
|
||||
"""Performs the OCSP revocation check
|
||||
|
||||
:param str cert_path: Certificate filepath
|
||||
:param str chain_path: Certificate chain filepath
|
||||
|
||||
:returns: True if revoked; False if valid or the check failed or cert is expired.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if self.broken:
|
||||
return False
|
||||
|
||||
|
||||
@@ -25,11 +25,9 @@ from certbot._internal import lock
|
||||
from certbot.compat import filesystem
|
||||
from certbot.compat import os
|
||||
|
||||
if sys.platform.startswith('linux'):
|
||||
_USE_DISTRO = sys.platform.startswith('linux')
|
||||
if _USE_DISTRO:
|
||||
import distro
|
||||
_USE_DISTRO = True
|
||||
else:
|
||||
_USE_DISTRO = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -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.2.0 (certbot(-auto);
|
||||
"". (default: CertbotACMEClient/1.3.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,
|
||||
|
||||
+16
-38
@@ -280,6 +280,7 @@ pritunl_ N Y Install certificates in pritunl distributed OpenVPN
|
||||
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
|
||||
dns-clouddns_ Y N DNS Authentication using CloudDNS API
|
||||
================== ==== ==== ===============================================================
|
||||
|
||||
.. _haproxy: https://github.com/greenhost/certbot-haproxy
|
||||
@@ -291,6 +292,7 @@ dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server
|
||||
.. _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
|
||||
.. _dns-clouddns: https://github.com/vshosting/certbot-dns-clouddns
|
||||
|
||||
If you're interested, you can also :ref:`write your own plugin <dev-plugin>`.
|
||||
|
||||
@@ -485,43 +487,6 @@ If you want your hook to run only after a successful renewal, use
|
||||
|
||||
``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
|
||||
@@ -686,6 +651,17 @@ your (web) server configuration directly to those files (or create
|
||||
symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated
|
||||
with the latest necessary files.
|
||||
|
||||
For historical reasons, the containing directories are created with
|
||||
permissions of ``0700`` meaning that certificates are accessible only
|
||||
to servers that run as the root user. **If you will never downgrade
|
||||
to an older version of Certbot**, then you can safely fix this using
|
||||
``chmod 0755 /etc/letsencrypt/{live,archive}``.
|
||||
|
||||
For servers that drop root privileges before attempting to read the
|
||||
private key file, you will also need to use ``chgrp`` and ``chmod
|
||||
0640`` to allow the server to read
|
||||
``/etc/letsencrypt/live/$domain/privkey.pem``.
|
||||
|
||||
.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys``
|
||||
contain all previous keys and certificates, while
|
||||
``/etc/letsencrypt/live`` symlinks to the latest versions.
|
||||
@@ -762,8 +738,10 @@ 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_VALIDATION``: The validation string
|
||||
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only)
|
||||
- ``CERTBOT_REMAINING_CHALLENGES``: Number of challenges remaining after the current challenge
|
||||
- ``CERTBOT_ALL_DOMAINS``: A comma-separated list of all domains challenged for the current certificate
|
||||
|
||||
Additionally for cleanup:
|
||||
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# Remember to update setup.py to match the package versions below.
|
||||
acme[dev]==0.40.0
|
||||
-e acme[dev]
|
||||
|
||||
+1
-1
@@ -36,7 +36,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',
|
||||
'acme>=1.4.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.
|
||||
|
||||
@@ -200,9 +200,11 @@ class CertificatesTest(BaseCertManagerTest):
|
||||
self.assertTrue(mock_utility.called)
|
||||
shutil.rmtree(empty_tempdir)
|
||||
|
||||
@mock.patch('certbot.crypto_util.get_serial_from_cert')
|
||||
@mock.patch('certbot._internal.cert_manager.ocsp.RevocationChecker.ocsp_revoked')
|
||||
def test_report_human_readable(self, mock_revoked):
|
||||
def test_report_human_readable(self, mock_revoked, mock_serial):
|
||||
mock_revoked.return_value = None
|
||||
mock_serial.return_value = 1234567890
|
||||
from certbot._internal import cert_manager
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
@@ -30,15 +30,23 @@ class TestReadFile(TempDirTestCase):
|
||||
# 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'))
|
||||
# The read-only filesystem introduced with macOS Catalina can break
|
||||
# code using relative paths below. See
|
||||
# https://bugs.python.org/issue38295 for another example of this.
|
||||
# Eliminating any possible symlinks in self.tempdir before passing
|
||||
# it to os.path.relpath solves the problem. This is done by calling
|
||||
# filesystem.realpath which removes any symlinks in the path on
|
||||
# POSIX systems.
|
||||
real_path = filesystem.realpath(os.path.join(self.tempdir, 'foo'))
|
||||
relative_path = os.path.relpath(real_path)
|
||||
self.assertRaises(
|
||||
argparse.ArgumentTypeError, cli.read_file, rel_test_path)
|
||||
argparse.ArgumentTypeError, cli.read_file, relative_path)
|
||||
|
||||
test_contents = b'bar\n'
|
||||
with open(rel_test_path, 'wb') as f:
|
||||
with open(relative_path, 'wb') as f:
|
||||
f.write(test_contents)
|
||||
|
||||
path, contents = cli.read_file(rel_test_path)
|
||||
path, contents = cli.read_file(relative_path)
|
||||
self.assertEqual(path, os.path.abspath(path))
|
||||
self.assertEqual(contents, test_contents)
|
||||
finally:
|
||||
@@ -142,7 +150,6 @@ class ParseTest(unittest.TestCase):
|
||||
self.assertTrue("how a certificate is deployed" in out)
|
||||
self.assertTrue("--webroot-path" in out)
|
||||
self.assertTrue("--text" not in out)
|
||||
self.assertTrue("--dialog" not in out)
|
||||
self.assertTrue("%s" not in out)
|
||||
self.assertTrue("{0}" not in out)
|
||||
self.assertTrue("--renew-hook" not in out)
|
||||
@@ -203,7 +210,6 @@ class ParseTest(unittest.TestCase):
|
||||
self.assertTrue("how a certificate is deployed" in out)
|
||||
self.assertTrue("--webroot-path" in out)
|
||||
self.assertTrue("--text" not in out)
|
||||
self.assertTrue("--dialog" not in out)
|
||||
self.assertTrue("%s" not in out)
|
||||
self.assertTrue("{0}" not in out)
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
"""Tests for certbot._internal.notify."""
|
||||
import socket
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
|
||||
class NotifyTests(unittest.TestCase):
|
||||
"""Tests for the notifier."""
|
||||
|
||||
@mock.patch("certbot._internal.notify.smtplib.LMTP")
|
||||
def test_smtp_success(self, mock_lmtp):
|
||||
from certbot._internal.notify import notify
|
||||
lmtp_obj = mock.MagicMock()
|
||||
mock_lmtp.return_value = lmtp_obj
|
||||
self.assertTrue(notify("Goose", "auntrhody@example.com",
|
||||
"The old grey goose is dead."))
|
||||
self.assertEqual(lmtp_obj.connect.call_count, 1)
|
||||
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
|
||||
|
||||
@mock.patch("certbot._internal.notify.smtplib.LMTP")
|
||||
@mock.patch("certbot._internal.notify.subprocess.Popen")
|
||||
def test_smtp_failure(self, mock_popen, mock_lmtp):
|
||||
from certbot._internal.notify import notify
|
||||
lmtp_obj = mock.MagicMock()
|
||||
mock_lmtp.return_value = lmtp_obj
|
||||
lmtp_obj.sendmail.side_effect = socket.error(17)
|
||||
proc = mock.MagicMock()
|
||||
mock_popen.return_value = proc
|
||||
self.assertTrue(notify("Goose", "auntrhody@example.com",
|
||||
"The old grey goose is dead."))
|
||||
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
|
||||
self.assertEqual(proc.communicate.call_count, 1)
|
||||
|
||||
@mock.patch("certbot._internal.notify.smtplib.LMTP")
|
||||
@mock.patch("certbot._internal.notify.subprocess.Popen")
|
||||
def test_everything_fails(self, mock_popen, mock_lmtp):
|
||||
from certbot._internal.notify import notify
|
||||
lmtp_obj = mock.MagicMock()
|
||||
mock_lmtp.return_value = lmtp_obj
|
||||
lmtp_obj.sendmail.side_effect = socket.error(17)
|
||||
proc = mock.MagicMock()
|
||||
mock_popen.return_value = proc
|
||||
proc.communicate.side_effect = OSError("What we have here is a "
|
||||
"failure to communicate.")
|
||||
self.assertFalse(notify("Goose", "auntrhody@example.com",
|
||||
"The old grey goose is dead."))
|
||||
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
|
||||
self.assertEqual(proc.communicate.call_count, 1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
@@ -177,7 +177,7 @@ class InstallerTest(test_util.ConfigTestCase):
|
||||
|
||||
|
||||
class AddrTest(unittest.TestCase):
|
||||
"""Tests for certbot._internal.client.plugins.common.Addr."""
|
||||
"""Tests for certbot.plugins.common.Addr."""
|
||||
|
||||
def setUp(self):
|
||||
from certbot.plugins.common import Addr
|
||||
|
||||
@@ -72,16 +72,23 @@ 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;'
|
||||
'from certbot.compat 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\'));"'
|
||||
'print(os.environ.get(\'CERTBOT_VALIDATION\', \'novalidation\'));'
|
||||
'print(os.environ.get(\'CERTBOT_ALL_DOMAINS\'));'
|
||||
'print(os.environ.get(\'CERTBOT_REMAINING_CHALLENGES\'));"'
|
||||
.format(sys.executable))
|
||||
dns_expected = '{0}\n{1}\n{2}'.format(
|
||||
dns_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
|
||||
self.dns_achall.domain, 'notoken',
|
||||
self.dns_achall.validation(self.dns_achall.account_key))
|
||||
http_expected = '{0}\n{1}\n{2}'.format(
|
||||
self.dns_achall.validation(self.dns_achall.account_key),
|
||||
','.join(achall.domain for achall in self.achalls),
|
||||
len(self.achalls) - self.achalls.index(self.dns_achall) - 1)
|
||||
http_expected = '{0}\n{1}\n{2}\n{3}\n{4}'.format(
|
||||
self.http_achall.domain, self.http_achall.chall.encode('token'),
|
||||
self.http_achall.validation(self.http_achall.account_key))
|
||||
self.http_achall.validation(self.http_achall.account_key),
|
||||
','.join(achall.domain for achall in self.achalls),
|
||||
len(self.achalls) - self.achalls.index(self.http_achall) - 1)
|
||||
|
||||
self.assertEqual(
|
||||
self.auth.perform(self.achalls),
|
||||
|
||||
@@ -672,10 +672,35 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
errors.CertStorageError,
|
||||
self.test_rc._update_link_to, "elephant", 17)
|
||||
|
||||
def test_ocsp_revoked(self):
|
||||
# XXX: This is currently hardcoded to False due to a lack of an
|
||||
# OCSP server to test against.
|
||||
self.assertFalse(self.test_rc.ocsp_revoked())
|
||||
@mock.patch("certbot.ocsp.RevocationChecker.ocsp_revoked_by_paths")
|
||||
def test_ocsp_revoked(self, mock_checker):
|
||||
# Write out test files
|
||||
for kind in ALL_FOUR:
|
||||
self._write_out_kind(kind, 1)
|
||||
version = self.test_rc.latest_common_version()
|
||||
expected_cert_path = self.test_rc.version("cert", version)
|
||||
expected_chain_path = self.test_rc.version("chain", version)
|
||||
|
||||
# Test with cert revoked
|
||||
mock_checker.return_value = True
|
||||
self.assertTrue(self.test_rc.ocsp_revoked(version))
|
||||
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
|
||||
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
|
||||
|
||||
# Test with cert not revoked
|
||||
mock_checker.return_value = False
|
||||
self.assertFalse(self.test_rc.ocsp_revoked(version))
|
||||
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
|
||||
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
|
||||
|
||||
# Test with error
|
||||
mock_checker.side_effect = ValueError
|
||||
with mock.patch("certbot._internal.storage.logger.warning") as logger:
|
||||
self.assertFalse(self.test_rc.ocsp_revoked(version))
|
||||
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
|
||||
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
|
||||
log_msg = logger.call_args[0][0]
|
||||
self.assertIn("An error occurred determining the OCSP status", log_msg)
|
||||
|
||||
def test_add_time_interval(self):
|
||||
from certbot._internal import storage
|
||||
|
||||
Reference in New Issue
Block a user