Compare commits

...

23 Commits

Author SHA1 Message Date
Erica Portnoy
e8079a815a Use verbose=3 in sample dev cli.ini now that count is allowed in config files 2021-06-09 17:47:37 -07:00
alexzorin
d7b26c1bb2 cli: dont use argv[0] in user-facing messages (#8857) 2021-06-09 14:31:15 -07:00
Michel Le Bihan
78261dbae2 Fix typo of fulfill in dns_rfc2136 plugin (#8886) 2021-06-06 09:55:24 +10:00
Jonathan Griffin
2ed4e0a17e Fixed typo in common.py (#8881)
Fixed typo:

exterally -> externally
2021-06-03 13:42:56 -07:00
Brad Warren
c372dd8aee Remove local-oldest-requirements files (#8863)
This is part of https://github.com/certbot/certbot/issues/8787. I got a +1 from our packagers at major distros in https://github.com/certbot/certbot/issues/8761.

* remove local-oldest-requirements files

* fix tests

* fix some oldest tests

* list packages on one line in tox.ini

* add changelog entry
2021-06-01 14:46:06 -07:00
Brad Warren
01772280c0 Merge pull request #8879 from certbot/candidate-1.16.0
Release 1.16.0
2021-06-01 14:13:44 -07:00
Erica Portnoy
814d8d1aba Bump version to 1.17.0 2021-06-01 10:52:31 -07:00
Erica Portnoy
a190480517 Add contents to certbot/CHANGELOG.md for next version 2021-06-01 10:52:31 -07:00
Erica Portnoy
7e8f22e136 Release 1.16.0 2021-06-01 10:52:23 -07:00
Erica Portnoy
965a403699 Update changelog for 1.16.0 release 2021-06-01 10:49:17 -07:00
Brad Warren
968cc5801b delete eggs before running poetry (#8865) 2021-05-31 09:03:25 +02:00
Brad Warren
492b578662 Update coverage and pytest (#8875)
* unpin pytest and update pinnings

* ignore external mock warnings

* fix assertion

* fix test_revoke_mutual_exclusive_flags

* fix output count

* capture stdout and stderr separately

* undouble counts

* rename variable

* don't use capture_output

* fix leaky test

* update coverage
2021-05-31 09:01:01 +02:00
ohemorange
e946479b9f Use shortlink for renewal setup instructions (#8874) 2021-05-28 14:50:59 -07:00
Adrien Ferrand
f88105a952 Deprecate usage of IConfig as a singleton in Certbot (#8869)
* Deprecate usage of IConfig as a singleton in Certbot

* Fix local oldest requirements

* Add changelog

* Add tests for certbot.crypto_util.init_save_* functions

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-05-28 12:17:56 -07:00
alexzorin
3380694fa8 windows: fix colors and bold text not rendering (#8872)
Fixes #8848.
2021-05-28 10:36:51 -07:00
ohemorange
18631b99ef Add instructions for setting up a cronjob in the docs (#8870)
* Add instructions for setting up a cronjob in the docs

* Be more specific about where the cron entry will be created

Co-authored-by: alexzorin <alex@zorin.id.au>

* Correct &amp;s to &s

Co-authored-by: alexzorin <alex@zorin.id.au>

* Correct other &amp; to &

Co-authored-by: alexzorin <alex@zorin.id.au>

* De-weasel the double-scheduled-task comment

Co-authored-by: alexzorin <alex@zorin.id.au>

* Have users create directory hooks instead of command line hooks

* Use sudo in command

Co-authored-by: alexzorin <alex@zorin.id.au>

* tell windows users to ignore these instructions instead of telling them they won't work

* Use the same commands that we have in the general instructions

Co-authored-by: alexzorin <alex@zorin.id.au>
2021-05-28 16:27:56 +10:00
Brad Warren
55d461392a Remove unused tools (#8862)
* remove unused tools

* remove deactivate.py
2021-05-28 06:47:44 +10:00
Arthur Lutz
a7a9a8480b [docs/using] Add mention of CentOS as supported by apache plugin (#8871) 2021-05-27 10:45:11 -07:00
Brad Warren
3640b8546e remove ancient comment (#8861) 2021-05-27 22:05:26 +10:00
Brad Warren
1f94c7db20 remove ancient .gitignore (#8864) 2021-05-27 21:52:46 +10:00
alexzorin
a02223a97f cli: later printing of renewal and install retry advice (#8860)
* later printing of renewal and install retry advice

Move printing of advice for automated renewal, and retrying installation
in case of failure, towards the end of `run` and `certonly`.

Also adds some renewal advice for the --csr case (no autorenewal).

* update renewal advice for preconfigured-renewal

* rewrite in terms of "NEXT STEPS" for run/certonly

* fix lint

* re-add "Could not install certificate"

* update --csr renewal advice

* rewrite non-preconfigured-renewal renewal advice
2021-05-26 15:16:12 -07:00
ohemorange
2e31b1ca41 Remove no names found in configuration files because it sounds like an error but actually it is fine (#8866)
* Remove no names found in configuration files because it sounds like an error but actually it is fine

* fix test

* Pose question more grammatically and specifically, and remove extra space

* fix lint

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2021-05-27 07:54:04 +10:00
alexzorin
7ce86f588b windows: always run with --preconfigured-renewal (#8867)
Adds a Pynsist extra_preamble in the Certbot entry_point for the
Windows installer, ensuring the flag is always set.
2021-05-26 15:45:40 +02:00
78 changed files with 648 additions and 740 deletions

View File

@@ -2,14 +2,12 @@ steps:
- bash: |
FINAL_STATUS=0
declare -a FAILED_BUILDS
python3 -m venv .venv
source .venv/bin/activate
python tools/pipstrap.py
tools/venv.py
source venv/bin/activate
for doc_path in */docs
do
echo ""
echo "##[group]Building $doc_path"
tools/pip_install_editable.py $doc_path/..[docs]
if ! sphinx-build -W --keep-going -b html $doc_path $doc_path/_build/html; then
FINAL_STATUS=1
FAILED_BUILDS[${#FAILED_BUILDS[@]}]="${doc_path%/docs}"

View File

@@ -3,9 +3,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'cryptography>=2.1.4',
# formerly known as acme.jose:

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==1.8.0
certbot[dev]==1.10.1

View File

@@ -1,13 +1,14 @@
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=1.8.0',
'certbot>=1.10.1',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
'python-augeas',
'setuptools>=39.0.1',
'zope.component',

View File

@@ -61,7 +61,7 @@ class IntegrationTestsContext:
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
:return: stdout and stderr from certbot execution
"""
command = ['--authenticator', 'standalone', '--installer', 'null']
command.extend(args)

View File

@@ -78,9 +78,9 @@ def test_registration_override(context):
def test_prepare_plugins(context):
"""Test that plugins are correctly instantiated and displayed."""
output = context.certbot(['plugins', '--init', '--prepare'])
stdout, _ = context.certbot(['plugins', '--init', '--prepare'])
assert 'webroot' in output
assert 'webroot' in stdout
def test_http_01(context):
@@ -407,9 +407,9 @@ def test_invalid_domain_with_dns_challenge(context):
'--manual-cleanup-hook', context.manual_dns_cleanup_hook
])
output = context.certbot(['certificates'])
stdout, _ = context.certbot(['certificates'])
assert context.get_domain('fail-dns1') not in output
assert context.get_domain('fail-dns1') not in stdout
def test_reuse_key(context):
@@ -614,11 +614,11 @@ def test_revoke_and_unregister(context):
context.certbot(['unregister'])
output = context.certbot(['certificates'])
stdout, _ = context.certbot(['certificates'])
assert cert1 not in output
assert cert2 not in output
assert cert3 in output
assert cert1 not in stdout
assert cert2 not in stdout
assert cert3 in stdout
def test_revoke_mutual_exclusive_flags(context):
@@ -630,7 +630,7 @@ def test_revoke_mutual_exclusive_flags(context):
'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
assert 'Exactly one of --cert-path or --cert-name must be specified' in error.value.stderr
def test_revoke_multiple_lineages(context):
@@ -685,12 +685,12 @@ def test_wildcard_certificates(context):
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])
stdout, _ = 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')))
assert stdout.count('TEST_CERT') == 2, ('Did not find two test certs as expected ({0})'
.format(stdout.count('TEST_CERT')))
assert stdout.count('EXPIRED') == 2, ('Did not find two expired certs as expected ({0})'
.format(stdout.count('EXPIRED')))
def test_ocsp_status_live(context):
@@ -699,20 +699,20 @@ def test_ocsp_status_live(context):
# OSCP 1: Check live certificate OCSP status (VALID)
context.certbot(['--domains', cert])
output = context.certbot(['certificates'])
stdout, _ = 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)
assert stdout.count('VALID') == 1, 'Expected {0} to be VALID'.format(cert)
assert stdout.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'])
# 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'])
stdout, _ = 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)
assert stdout.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert)
assert stdout.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
def test_ocsp_renew(context):

View File

@@ -17,7 +17,7 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
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.
This command captures both stdout and stderr 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
@@ -25,13 +25,19 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
: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
:return: stdout and stderr as strings
:rtype: `tuple` of `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)
proc = subprocess.run(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False, universal_newlines=True,
cwd=workspace, env=env)
print('--> Certbot log output was:')
print(proc.stderr)
proc.check_returncode()
return proc.stdout, proc.stderr
def _prepare_environ(workspace):

View File

@@ -48,7 +48,6 @@ class Proxy(configurators_common.Proxy):
setattr(self.le_config, "nginx_" + k, constants.os_constant(k))
conf = configuration.NamespaceConfig(self.le_config)
zope.component.provideUtility(conf)
self._configurator = configurator.NginxConfigurator(
config=conf, name="nginx")
self._configurator.prepare()

View File

@@ -3,7 +3,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
install_requires = [
'certbot',

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'cloudflare>=1.5.1',
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.29.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'python-digitalocean>=1.11', # 1.15.0 or newer is recommended for TTL support
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.29.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'setuptools>=39.0.1',
'zope.interface',
@@ -15,8 +13,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,9 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -15,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'google-api-python-client>=1.5.5',
'oauth2client>=4.0',
@@ -19,8 +17,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.29.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,9 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -15,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -27,7 +27,7 @@ DEFAULT_NETWORK_TIMEOUT = 45
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.
This Authenticator uses RFC 2136 Dynamic Updates to fulfill a dns-01 challenge.
"""
ALGORITHMS = {

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dnspython',
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.29.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,62 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
# Sphinx documentation
docs/_build/
# PyBuilder
target/
#Ipython Notebook
.ipynb_checkpoints

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==1.1.0

View File

@@ -4,10 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'boto3',
'setuptools>=39.0.1',
@@ -16,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.29.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.31.0
certbot[dev]==1.1.0

View File

@@ -4,9 +4,8 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
@@ -15,8 +14,11 @@ install_requires = [
if not os.environ.get('SNAP_BUILD'):
install_requires.extend([
'acme>=0.31.0',
'certbot>=1.1.0',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
])
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Unset SNAP_BUILD when building wheels '

View File

@@ -678,8 +678,9 @@ class NginxConfigurator(common.Installer):
"""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")
le_key = crypto_util.generate_key(
key_size=1024, key_dir=tmp_dir, keyname="key.pem",
strict_permissions=self.config.strict_permissions)
key = OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM, le_key.pem)
cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()])

View File

@@ -1,3 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==1.8.0
certbot[dev]==1.10.1

View File

@@ -1,13 +1,14 @@
from setuptools import find_packages
from setuptools import setup
version = '1.16.0.dev0'
version = '1.17.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=1.8.0',
'certbot>=1.10.1',
# We specify the minimum acme and certbot version as the current plugin
# version for simplicity. See
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
'PyOpenSSL>=17.3.0',
'pyparsing>=2.2.0',
'setuptools>=39.0.1',

View File

@@ -9,7 +9,6 @@ try:
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import pkg_resources
import zope.component
from certbot import util
from certbot.compat import os
@@ -79,9 +78,6 @@ class NginxTest(test_util.ConfigTestCase):
openssl_version=openssl_version)
config.prepare()
# Provide general config utility.
zope.component.provideUtility(self.configuration)
return config

1
certbot/.gitignore vendored
View File

@@ -1 +0,0 @@
*.crt

View File

@@ -2,7 +2,28 @@
Certbot adheres to [Semantic Versioning](https://semver.org/).
## 1.16.0 - master
## 1.17.0 - master
### Added
*
### 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.
### Fixed
*
More details about these changes can be found on our GitHub repo.
## 1.16.0 - 2021-06-01
### Added
@@ -19,11 +40,16 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
and clarify output. If you would like to see more verbose output, use
the -v or -vv flags. UX improvements are an iterative process and
the Certbot team welcomes constructive feedback.
* Functions `certbot.crypto_util.init_save_key` and `certbot.crypto_util.init_save_csr`,
whose behaviors rely on the global Certbot `config` singleton, are deprecated and will
be removed in a future release. Please use `certbot.crypto_util.generate_key` and
`certbot.crypto_util.generate_csr` instead.
### Fixed
* Fix TypeError due to incompatibility with lexicon >= v3.6.0
* Installers (e.g. nginx, Apache) were being restarted unnecessarily after dry-run renewals.
* Colors and bold text should properly render in all supported versions of Windows.
More details about these changes can be found on our GitHub repo.

View File

@@ -1,3 +1,3 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
__version__ = '1.16.0.dev0'
__version__ = '1.17.0.dev0'

View File

@@ -44,11 +44,12 @@ class AuthHandler:
self.account = account
self.pref_challs = pref_challs
def handle_authorizations(self, orderr, best_effort=False, max_retries=30):
def handle_authorizations(self, orderr, config, 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 interfaces.IConfig config: current Certbot configuration
: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
@@ -72,7 +73,6 @@ class AuthHandler:
resps = self.auth.perform(achalls)
# If debug is on, wait for user input before starting the verification process.
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. '

View File

@@ -334,7 +334,7 @@ class Client:
key = None
key_size = self.config.rsa_key_size
elliptic_curve = None
elliptic_curve = "secp256r1"
# key-type defaults to a list, but we are only handling 1 currently
if isinstance(self.config.key_type, list):
@@ -362,13 +362,15 @@ class Client:
data=acme_crypto_util.make_csr(
key.pem, domains, self.config.must_staple))
else:
key = key or crypto_util.init_save_key(
key = key or crypto_util.generate_key(
key_size=key_size,
key_dir=self.config.key_dir,
key_type=self.config.key_type,
elliptic_curve=elliptic_curve,
strict_permissions=self.config.strict_permissions,
)
csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)
csr = crypto_util.generate_csr(key, domains, self.config.csr_dir,
self.config.must_staple, self.config.strict_permissions)
orderr = self._get_order_and_authorizations(csr.data, self.config.allow_subset_of_names)
authzr = orderr.authorizations
@@ -420,7 +422,7 @@ class Client:
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)
authzr = self.auth_handler.handle_authorizations(orderr, self.config, best_effort)
return orderr.update(authorizations=authzr)
def obtain_and_enroll_certificate(self, domains, certname):
@@ -516,11 +518,9 @@ class Client:
return abs_cert_path, abs_chain_path, abs_fullchain_path
def deploy_certificate(self, cert_name, domains, privkey_path,
cert_path, chain_path, fullchain_path):
def deploy_certificate(self, domains, privkey_path, cert_path, chain_path, fullchain_path):
"""Install certificate
:param str cert_name: name of the certificate lineage (optional)
: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)
@@ -536,11 +536,7 @@ class Client:
display_util.notify("Deploying certificate")
msg = f"Failed to install the certificate (installer: {self.config.installer})."
if cert_name:
msg += (" Try again after fixing errors by running:\n\n"
f" {cli.cli_constants.cli_command} install --cert-name {cert_name}\n")
msg = "Could not install certificate"
with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
for dom in domains:
self.installer.deploy_cert(
@@ -616,9 +612,7 @@ class Client:
"""
msg = ("We were unable to set up enhancement %s for your server, "
"however, we successfully installed your certificate."
% (enhancement))
msg = f"Could not set up {enhancement} enhancement"
with error_handler.ErrorHandler(self._recovery_routine_with_msg, msg):
for dom in domains:
try:

View File

@@ -202,7 +202,7 @@ def _handle_subset_cert_request(config: configuration.NamespaceConfig,
"--duplicate option.{br}{br}"
"For example:{br}{br}{1} --duplicate {2}".format(
existing,
sys.argv[0], " ".join(sys.argv[1:]),
cli.cli_command, " ".join(sys.argv[1:]),
br=os.linesep
))
raise errors.Error(USER_CANCELLED)
@@ -469,6 +469,67 @@ def _find_domains_or_certname(config, installer, question=None):
return domains, certname
def _report_next_steps(config: interfaces.IConfig, installer_err: Optional[errors.Error],
lineage: Optional[storage.RenewableCert],
new_or_renewed_cert: bool = True) -> None:
"""Displays post-run/certonly advice to the user about renewal and installation.
The output varies by runtime configuration and any errors encountered during installation.
:param config: Configuration object
:type config: interfaces.IConfig
:param installer_err: The installer/enhancement error encountered, if any.
:type error: Optional[errors.Error]
:param lineage: The resulting certificate lineage from the issuance, if any.
:type lineage: Optional[storage.RenewableCert]
:param bool new_or_renewed_cert: Whether the verb execution resulted in a certificate
being saved (created or renewed).
"""
steps: List[str] = []
# If the installation or enhancement raised an error, show advice on trying again
if installer_err:
steps.append(
"The certificate was saved, but could not be installed (installer: "
f"{config.installer}). After fixing the error shown below, try installing it again "
f"by running:\n {cli.cli_command} install --cert-name "
f"{_cert_name_from_config_or_lineage(config, lineage)}"
)
# If a certificate was obtained or renewed, show applicable renewal advice
if new_or_renewed_cert:
if config.csr:
steps.append(
"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 not config.preconfigured_renewal:
steps.append(
"The certificate will need to be renewed before it expires. Certbot can "
"automatically renew the certificate in the background, but you may need "
"to take steps to enable that functionality. "
"See https://certbot.org/renewal-setup for instructions.")
if not steps:
return
# TODO: refactor ANSI escapes during https://github.com/certbot/certbot/issues/8848
(bold_on, bold_off) = [c if sys.stdout.isatty() and not config.quiet else '' \
for c in (util.ANSI_SGR_BOLD, util.ANSI_SGR_RESET)]
print(bold_on, '\n', 'NEXT STEPS:', bold_off, sep='')
for step in steps:
display_util.notify(f"- {step}")
# If there was an installer error, segregate the error output with a trailing newline
if installer_err:
print()
def _report_new_cert(config, cert_path, fullchain_path, key_path=None):
# type: (interfaces.IConfig, Optional[str], Optional[str], Optional[str]) -> None
"""Reports the creation of a new certificate to the user.
@@ -499,18 +560,13 @@ def _report_new_cert(config, cert_path, fullchain_path, key_path=None):
("\nSuccessfully received certificate.\n"
"Certificate is saved at: {cert_path}\n{key_msg}"
"This certificate expires on {expiry}.\n"
"These files will be updated when the certificate renews.\n{renew_msg}{nl}").format(
"These files will be updated when the certificate renews.{renewal_msg}{nl}").format(
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 "",
renew_msg="Certbot will automatically renew this certificate in the background."
if config.preconfigured_renewal else
(f'Run "{cli.cli_constants.cli_command} renew" to renew '
"expiring certificates. "
"We recommend setting up a scheduled task for renewal; see "
"https://certbot.eff.org/docs/using.html#automated-renewals "
"for instructions."),
nl="\n" if config.verb == "run" else "" # visually split output if also deploying
renewal_msg="\nCertbot has set up a scheduled task to automatically renew this "
"certificate in the background." if config.preconfigured_renewal else "",
nl="\n" if config.verb == "run" else "" # Normalize spacing across verbs
)
)
@@ -813,6 +869,21 @@ def update_account(config, unused_plugins):
return None
def _cert_name_from_config_or_lineage(config: interfaces.IConfig,
lineage: Optional[storage.RenewableCert]) -> Optional[str]:
if lineage:
return lineage.lineagename
elif config.certname:
return config.certname
try:
cert_name = cert_manager.cert_path_to_lineage(config)
return cert_name
except errors.Error:
pass
return None
def _install_cert(config, le_client, domains, lineage=None):
"""Install a cert
@@ -835,20 +906,8 @@ def _install_cert(config, le_client, domains, lineage=None):
path_provider = lineage if lineage else config
assert path_provider.cert_path is not None
cert_name: Optional[str] = None
if isinstance(path_provider, storage.RenewableCert):
cert_name = path_provider.lineagename
elif path_provider.certname:
cert_name = path_provider.certname
else:
# Check if the cert path happens to be part of an existing lineage
try:
cert_name = cert_manager.cert_path_to_lineage(config)
except errors.Error:
pass
le_client.deploy_certificate(cert_name, domains, path_provider.key_path,
path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path)
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)
@@ -993,7 +1052,7 @@ def enhance(config, plugins):
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.error(msg, sys.argv[0])
logger.error(msg, cli.cli_command)
raise errors.MisconfigurationError("No enhancements requested, exiting.")
try:
@@ -1216,15 +1275,27 @@ def run(config, plugins):
if should_get_cert:
_report_new_cert(config, cert_path, fullchain_path, key_path)
_install_cert(config, le_client, domains, new_lineage)
# The installer error, if any, is being stored as a value here, in order to first print
# relevant advice in a nice way, before re-raising the error for normal processing.
installer_err: Optional[errors.Error] = None
try:
_install_cert(config, le_client, domains, new_lineage)
if enhancements.are_requested(config) and new_lineage:
enhancements.enable(new_lineage, domains, installer, config)
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)
if lineage is None or not should_get_cert:
display_ops.success_installation(domains)
else:
display_ops.success_renewal(domains)
except errors.Error as e:
installer_err = e
finally:
_report_next_steps(config, installer_err, new_lineage,
new_or_renewed_cert=should_get_cert)
# If the installer did fail, re-raise the error to bail out
if installer_err:
raise installer_err
_suggest_donation_if_appropriate(config)
eff.handle_subscription(config, le_client.account)
@@ -1327,6 +1398,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)
_suggest_donation_if_appropriate(config)
eff.handle_subscription(config, le_client.account)
return
@@ -1345,6 +1417,7 @@ 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)
_suggest_donation_if_appropriate(config)
eff.handle_subscription(config, le_client.account)
@@ -1445,9 +1518,15 @@ def main(cli_args=None):
logger.debug("Arguments: %r", cli_args)
logger.debug("Discovered plugins: %r", plugins)
# Some releases of Windows require escape sequences to be enable explicitly
misc.prepare_virtual_console()
# note: arg parser internally handles --help (and exits afterwards)
args = cli.prepare_and_parse_args(plugins, cli_args)
config = configuration.NamespaceConfig(args)
# This call is done only for retro-compatibility purposes.
# TODO: Remove this call once zope dependencies are removed from Certbot.
zope.component.provideUtility(config)
# On windows, shell without administrative right cannot create symlinks required by certbot.

View File

@@ -450,7 +450,8 @@ def handle_renewal_request(config):
if renewal_candidate is None:
parse_failures.append(renewal_file)
else:
# XXX: ensure that each call here replaces the previous one
# This call is done only for retro-compatibility purposes.
# TODO: Remove this call once zope dependencies are removed from Certbot.
zope.component.provideUtility(lineage_config)
renewal_candidate.ensure_deployed()
from certbot._internal import main

View File

@@ -17,6 +17,8 @@ from certbot.compat import os
try:
from win32com.shell import shell as shellwin32
from win32console import GetStdHandle, STD_OUTPUT_HANDLE
from pywintypes import error as pywinerror
POSIX_MODE = False
except ImportError: # pragma: no cover
POSIX_MODE = True
@@ -39,6 +41,26 @@ def raise_for_non_administrative_windows_rights() -> None:
raise errors.Error('Error, certbot must be run on a shell with administrative rights.')
def prepare_virtual_console() -> None:
"""
On Windows, ensure that Console Virtual Terminal Sequences are enabled.
"""
if POSIX_MODE:
return
# https://docs.microsoft.com/en-us/windows/console/setconsolemode
ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004
# stdout/stderr will be the same console screen buffer, but this could return None or raise
try:
h = GetStdHandle(STD_OUTPUT_HANDLE)
if h:
h.SetConsoleMode(h.GetConsoleMode() | ENABLE_VIRTUAL_TERMINAL_PROCESSING)
except pywinerror:
logger.debug("Failed to set console mode", exc_info=True)
def readline_with_timeout(timeout: float, prompt: str) -> str:
"""
Read user input to return the first line entered, or raise after specified timeout.

View File

@@ -9,7 +9,7 @@ import logging
import re
import warnings
from typing import List
from typing import List, Set
# See https://github.com/pyca/cryptography/issues/4275
from cryptography import x509 # type: ignore
from cryptography.exceptions import InvalidSignature
@@ -38,9 +38,10 @@ logger = logging.getLogger(__name__)
# High level functions
def init_save_key(
key_size, key_dir, key_type="rsa", elliptic_curve="secp256r1", keyname="key-certbot.pem"
):
def generate_key(key_size: int, key_dir: str, key_type: str = "rsa",
elliptic_curve: str = "secp256r1", keyname: str = "key-certbot.pem",
strict_permissions: bool = True) -> util.Key:
"""Initializes and saves a privkey.
Inits key and saves it in PEM format on the filesystem.
@@ -53,6 +54,8 @@ def init_save_key(
:param str key_type: Key Type [rsa, ecdsa]
:param str elliptic_curve: Name of the elliptic curve if key type is ecdsa.
:param str keyname: Filename of key
:param bool strict_permissions: If true and key_dir exists, an exception is raised if
the directory doesn't have 0700 permissions or isn't owned by the current user.
:returns: Key
:rtype: :class:`certbot.util.Key`
@@ -69,9 +72,8 @@ def init_save_key(
logger.error("Encountered error while making key: %s", str(err))
raise err
config = zope.component.getUtility(interfaces.IConfig)
# Save file
util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions)
util.make_or_verify_dir(key_dir, 0o700, strict_permissions)
key_f, key_path = util.unique_file(
os.path.join(key_dir, keyname), 0o600, "wb")
with key_f:
@@ -84,9 +86,77 @@ def init_save_key(
return util.Key(key_path, key_pem)
# TODO: Remove this call once zope dependencies are removed from Certbot.
def init_save_key(key_size, key_dir, key_type="rsa", elliptic_curve="secp256r1",
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.
.. deprecated:: 1.16.0
Use :func:`generate_key` instead.
:param int key_size: key size in bits if key size is rsa.
:param str key_dir: Key save directory.
:param str key_type: Key Type [rsa, ecdsa]
:param str elliptic_curve: Name of the elliptic curve if key type is ecdsa.
:param str keyname: Filename of key
:returns: Key
:rtype: :class:`certbot.util.Key`
:raises ValueError: If unable to generate the key given key_size.
"""
warnings.warn("certbot.crypto_util.init_save_key is deprecated, please use "
"certbot.crypto_util.generate_key instead.", DeprecationWarning)
config = zope.component.getUtility(interfaces.IConfig)
return generate_key(key_size, key_dir, key_type=key_type, elliptic_curve=elliptic_curve,
keyname=keyname, strict_permissions=config.strict_permissions)
def generate_csr(privkey: util.Key, names: Set[str], path: str,
must_staple: bool = False, strict_permissions: bool = True) -> util.CSR:
"""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.
:param bool must_staple: If true, include the TLS Feature extension "OCSP Must Staple"
:param bool strict_permissions: If true and path exists, an exception is raised if
the directory doesn't have 0755 permissions or isn't owned by the current user.
:returns: CSR
:rtype: :class:`certbot.util.CSR`
"""
csr_pem = acme_crypto_util.make_csr(
privkey.pem, names, must_staple=must_staple)
# Save CSR
util.make_or_verify_dir(path, 0o755, 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")
# TODO: Remove this call once zope dependencies are removed from Certbot.
def init_save_csr(privkey, names, path):
"""Initialize a CSR with the given private key.
.. deprecated:: 1.16.0
Use :func:`generate_csr` instead.
:param privkey: Key to include in the CSR
:type privkey: :class:`certbot.util.Key`
@@ -98,20 +168,13 @@ def init_save_csr(privkey, names, path):
:rtype: :class:`certbot.util.CSR`
"""
warnings.warn("certbot.crypto_util.init_save_csr is deprecated, please use "
"certbot.crypto_util.generate_csr instead.", DeprecationWarning)
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")
return generate_csr(privkey, names, path, must_staple=config.must_staple,
strict_permissions=config.strict_permissions)
# WARNING: the csr and private key file are possible attack vectors for TOCTOU

View File

@@ -123,8 +123,7 @@ def choose_names(installer, question=None):
names = get_valid_domains(domains)
if not names:
return _choose_names_manually(
"No names were found in your configuration files. ")
return _choose_names_manually()
code, names = _filter_names(names, question)
if code == display_util.OK and names:
@@ -192,7 +191,8 @@ def _choose_names_manually(prompt_prefix=""):
"""
code, input_ = z_util(interfaces.IDisplay).input(
prompt_prefix +
"Please enter in your domain name(s) (comma and/or space separated) ",
"Please enter the domain name(s) you would like on your certificate "
"(comma and/or space separated)",
cli_flag="--domains", force_interactive=True)
if code == display_util.OK:

View File

@@ -119,7 +119,7 @@ class Plugin:
# This is a fallback hint. Authenticators should implement their own auth_hint that
# addresses the specific mechanics of that authenticator.
challs = " and ".join(sorted({achall.typ for achall in failed_achalls}))
return ("The Certificate Authority couldn't exterally verify that the {name} plugin "
return ("The Certificate Authority couldn't externally verify that the {name} plugin "
"completed the required {challs} challenges. Ensure the plugin is configured "
"correctly and that the changes it makes are accessible from the internet."
.format(name=self.name, challs=challs))

View File

@@ -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:
-2)
-3)
--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.15.0 (certbot;
"". (default: CertbotACMEClient/1.16.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,

View File

@@ -84,7 +84,7 @@ Apache
The Apache plugin currently `supports
<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/_internal/entrypoint.py>`_
modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.
modern OSes based on Debian, Fedora, SUSE, Gentoo, CentOS and Darwin.
This automates both obtaining *and* installing certificates on an Apache
webserver. To specify this plugin on the command line, simply include
``--apache``.
@@ -695,10 +695,50 @@ is done by means of a scheduled task which runs ``certbot renew`` periodically.
If you are unsure whether you need to configure automated renewal:
1. Review the instructions for your system at https://certbot.eff.org/instructions.
They will describe how to set up a scheduled task, if necessary.
2. (Linux/BSD): Check your system's crontab (typically `/etc/crontab` and
`/etc/cron.*/*`) and systemd timers (``systemctl list-timers``).
1. Review the instructions for your system and installation method at
https://certbot.eff.org/instructions. They will describe how to set up a scheduled task,
if necessary. If no step is listed, your system comes with automated renewal pre-installed,
and you should not need to take any additional actions.
2. On Linux and BSD, you can check to see if your installation method has pre-installed a timer
for you. To do so, look for the ``certbot renew`` command in either your system's crontab
(typically `/etc/crontab` or `/etc/cron.*/*`) or systemd timers (``systemctl list-timers``).
3. If you're still not sure, you can configure automated renewal manually by following the steps
in the next section. Certbot has been carefully engineered to handle the case where both manual
automated renewal and pre-installed automated renewal are set up.
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
whether your system has a pre-installed scheduled task for Certbot, it is safe to follow these
instructions to create one.
If you're using Windows, these instructions are not neccessary as Certbot on Windows comes with
a scheduled task for automated renewal pre-installed.
Run the following line, which will add a cron job to `/etc/crontab`:
.. code-block:: shell
SLEEPTIME=$(awk 'BEGIN{srand(); print int(rand()*(3600+1))}'); echo "0 0,12 * * * root sleep $SLEEPTIME && certbot renew -q" | sudo tee -a /etc/crontab > /dev/null
If you needed to stop your webserver to run Certbot, you'll want to
add ``pre`` and ``post`` hooks to stop and start your webserver automatically.
For example, if your webserver is HAProxy, run the following commands to create the hook files
in the appropriate directory:
.. code-block:: shell
sudo sh -c 'printf "#!/bin/sh\nservice haproxy stop\n" > /etc/letsencrypt/renewal-hooks/pre/haproxy.sh'
sudo sh -c 'printf "#!/bin/sh\nservice haproxy start\n" > /etc/letsencrypt/renewal-hooks/post/haproxy.sh'
sudo chmod 755 /etc/letsencrypt/renewal-hooks/pre/haproxy.sh
sudo chmod 755 /etc/letsencrypt/renewal-hooks/post/haproxy.sh
Congratulations, Certbot will now automatically renew your certificates in the background.
If you are interested in learning more about how Certbot renews your certificates, see the
`Renewing certificates`_ section above.
.. _where-certs:

View File

@@ -13,8 +13,6 @@ 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
verbose = 3
authenticator = standalone

View File

@@ -1,2 +0,0 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==1.8.0

View File

@@ -41,7 +41,10 @@ version = meta['version']
# 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>=1.8.0',
# We specify the minimum acme version as the current Certbot version for
# simplicity. See https://github.com/certbot/certbot/issues/8761 for more
# info.
f'acme>={version}',
# 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.

View File

@@ -69,10 +69,9 @@ class HandleAuthorizationsTest(unittest.TestCase):
from certbot._internal.auth_handler import AuthHandler
self.mock_display = mock.Mock()
self.mock_config = mock.Mock(debug_challenges=False)
zope.component.provideUtility(
self.mock_display, interfaces.IDisplay)
zope.component.provideUtility(
mock.Mock(debug_challenges=False), interfaces.IConfig)
self.mock_auth = mock.MagicMock(name="ApacheConfigurator")
@@ -99,7 +98,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
self.mock_net.poll.side_effect = _gen_mock_on_poll(retry=1, wait_value=30)
with mock.patch('certbot._internal.auth_handler.time') as mock_time:
authzr = self.handler.handle_authorizations(mock_order)
authzr = self.handler.handle_authorizations(mock_order, self.mock_config)
self.assertEqual(self.mock_net.answer_challenge.call_count, 1)
@@ -131,7 +130,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
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)
authzr = self.handler.handle_authorizations(mock_order, self.mock_config)
self.assertEqual(self.mock_net.answer_challenge.call_count, 2)
@@ -152,7 +151,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
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)
authzr = self.handler.handle_authorizations(mock_order, self.mock_config)
self.assertEqual(self.mock_net.answer_challenge.call_count, 1)
@@ -176,7 +175,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
mock_order = mock.MagicMock(authorizations=authzrs)
self.mock_net.poll.side_effect = _gen_mock_on_poll()
authzr = self.handler.handle_authorizations(mock_order)
authzr = self.handler.handle_authorizations(mock_order, self.mock_config)
self.assertEqual(self.mock_net.answer_challenge.call_count, 3)
@@ -195,14 +194,13 @@ class HandleAuthorizationsTest(unittest.TestCase):
self._test_name3_http_01_3_common(combos=False)
def test_debug_challenges(self):
zope.component.provideUtility(
mock.Mock(debug_challenges=True), interfaces.IConfig)
config = mock.Mock(debug_challenges=True)
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)]
mock_order = mock.MagicMock(authorizations=authzrs)
self.mock_net.poll.side_effect = _gen_mock_on_poll()
self.handler.handle_authorizations(mock_order)
self.handler.handle_authorizations(mock_order, config)
self.assertEqual(self.mock_net.answer_challenge.call_count, 1)
self.assertEqual(self.mock_display.notification.call_count, 1)
@@ -214,7 +212,8 @@ class HandleAuthorizationsTest(unittest.TestCase):
self.mock_auth.perform.side_effect = errors.AuthorizationError
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
def test_max_retries_exceeded(self):
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)]
@@ -225,12 +224,13 @@ class HandleAuthorizationsTest(unittest.TestCase):
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.handler.handle_authorizations(mock_order, self.mock_config, False, 1)
self.assertIn('All authorizations were not finalized by the CA.', str(error.exception))
def test_no_domains(self):
mock_order = mock.MagicMock(authorizations=[])
self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
def _test_preferred_challenge_choice_common(self, combos):
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos)]
@@ -242,7 +242,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
challenges.DNS01.typ,))
self.mock_net.poll.side_effect = _gen_mock_on_poll()
self.handler.handle_authorizations(mock_order)
self.handler.handle_authorizations(mock_order, self.mock_config)
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
@@ -260,7 +260,8 @@ class HandleAuthorizationsTest(unittest.TestCase):
mock_order = mock.MagicMock(authorizations=authzrs)
self.handler.pref_challs.append(challenges.DNS01.typ)
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
def test_preferred_challenges_not_supported_acme_1(self):
self._test_preferred_challenges_not_supported_common(combos=True)
@@ -273,14 +274,16 @@ class HandleAuthorizationsTest(unittest.TestCase):
authzrs = [gen_dom_authzr(domain="0", challs=[acme_util.DNS01])]
mock_order = mock.MagicMock(authorizations=authzrs)
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
def test_perform_error(self):
self.mock_auth.perform.side_effect = errors.AuthorizationError
authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=True)
mock_order = mock.MagicMock(authorizations=[authzr])
self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
@@ -293,7 +296,8 @@ class HandleAuthorizationsTest(unittest.TestCase):
mock_order = mock.MagicMock(authorizations=authzrs)
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01")
@@ -305,7 +309,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
with test_util.patch_get_utility():
with self.assertRaises(errors.AuthorizationError) as error:
self.handler.handle_authorizations(mock_order, False)
self.handler.handle_authorizations(mock_order, self.mock_config, False)
self.assertIn('Some challenges have failed.', str(error.exception))
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
@@ -330,7 +334,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
with mock.patch('certbot._internal.auth_handler.AuthHandler._report_failed_authzrs') \
as mock_report:
valid_authzr = self.handler.handle_authorizations(mock_order, True)
valid_authzr = self.handler.handle_authorizations(mock_order, self.mock_config, True)
# Because best_effort=True, we did not blow up. Instead ...
self.assertEqual(len(valid_authzr), 1) # ... the valid authzr has been processed
@@ -340,7 +344,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
with test_util.patch_get_utility():
with self.assertRaises(errors.AuthorizationError) as error:
self.handler.handle_authorizations(mock_order, True)
self.handler.handle_authorizations(mock_order, self.mock_config, True)
# Despite best_effort=True, process will fail because no authzr is valid.
self.assertIn('All challenges have failed.', str(error.exception))
@@ -354,7 +358,8 @@ class HandleAuthorizationsTest(unittest.TestCase):
[messages.STATUS_PENDING], False)
mock_order = mock.MagicMock(authorizations=[authzr])
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
errors.AuthorizationError, self.handler.handle_authorizations,
mock_order, self.mock_config)
# With a validated challenge that is not supported by the plugin, we
# expect the challenge to not be solved again and
@@ -364,7 +369,7 @@ class HandleAuthorizationsTest(unittest.TestCase):
[acme_util.DNS01],
[messages.STATUS_VALID], False)
mock_order = mock.MagicMock(authorizations=[authzr])
self.handler.handle_authorizations(mock_order)
self.handler.handle_authorizations(mock_order, self.mock_config)
def test_valid_authzrs_deactivated(self):
"""When we deactivate valid authzrs in an orderr, we expect them to become deactivated

View File

@@ -242,6 +242,7 @@ class ClientTest(ClientTestCommon):
self.config.allow_subset_of_names = False
self.config.dry_run = False
self.config.strict_permissions = True
self.eg_domains = ["example.com", "www.example.com"]
self.eg_order = mock.MagicMock(
authorizations=[None],
@@ -263,6 +264,7 @@ class ClientTest(ClientTestCommon):
if auth_count == 1:
self.client.auth_handler.handle_authorizations.assert_called_once_with(
self.eg_order,
self.config,
self.config.allow_subset_of_names)
else:
self.assertEqual(self.client.auth_handler.handle_authorizations.call_count, auth_count)
@@ -273,16 +275,14 @@ class ClientTest(ClientTestCommon):
@mock.patch("certbot._internal.client.crypto_util")
@mock.patch("certbot._internal.client.logger")
@test_util.patch_get_utility()
def test_obtain_certificate_from_csr(self, unused_mock_get_utility,
mock_logger, mock_crypto_util):
def test_obtain_certificate_from_csr(self, mock_logger, mock_crypto_util):
self._mock_obtain_certificate()
test_csr = util.CSR(form="pem", file=None, data=CSR_SAN)
auth_handler = self.client.auth_handler
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
orderr = self.acme.new_order(test_csr.data)
auth_handler.handle_authorizations(orderr, False)
auth_handler.handle_authorizations(orderr, self.config, False)
self.assertEqual(
(mock.sentinel.cert, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
@@ -310,7 +310,7 @@ class ClientTest(ClientTestCommon):
self.client.obtain_certificate_from_csr(
test_csr,
orderr=None))
auth_handler.handle_authorizations.assert_called_with(self.eg_order, False)
auth_handler.handle_authorizations.assert_called_with(self.eg_order, self.config, False)
# Test for no auth_handler
self.client.auth_handler = None
@@ -323,20 +323,21 @@ class ClientTest(ClientTestCommon):
@mock.patch("certbot._internal.client.crypto_util")
def test_obtain_certificate(self, mock_crypto_util):
csr = util.CSR(form="pem", file=None, data=CSR_SAN)
mock_crypto_util.init_save_csr.return_value = csr
mock_crypto_util.init_save_key.return_value = mock.sentinel.key
mock_crypto_util.generate_csr.return_value = csr
mock_crypto_util.generate_key.return_value = mock.sentinel.key
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
self._test_obtain_certificate_common(mock.sentinel.key, csr)
mock_crypto_util.init_save_key.assert_called_once_with(
mock_crypto_util.generate_key.assert_called_once_with(
key_size=self.config.rsa_key_size,
key_dir=self.config.key_dir,
key_type=self.config.key_type,
elliptic_curve=None, # elliptic curve is not set
elliptic_curve="secp256r1",
strict_permissions=True,
)
mock_crypto_util.init_save_csr.assert_called_once_with(
mock.sentinel.key, self.eg_domains, self.config.csr_dir)
mock_crypto_util.generate_csr.assert_called_once_with(
mock.sentinel.key, self.eg_domains, self.config.csr_dir, False, True)
mock_crypto_util.cert_and_chain_from_fullchain.assert_called_once_with(
self.eg_order.fullchain_pem)
@@ -345,16 +346,16 @@ class ClientTest(ClientTestCommon):
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)
mock_crypto_util.init_save_csr.return_value = csr
mock_crypto_util.init_save_key.return_value = key
mock_crypto_util.generate_csr.return_value = csr
mock_crypto_util.generate_key.return_value = key
self._set_mock_from_fullchain(mock_crypto_util.cert_and_chain_from_fullchain)
authzr = self._authzr_from_domains(["example.com"])
self.config.allow_subset_of_names = True
self._test_obtain_certificate_common(key, csr, authzr_ret=authzr, auth_count=2)
self.assertEqual(mock_crypto_util.init_save_key.call_count, 2)
self.assertEqual(mock_crypto_util.init_save_csr.call_count, 2)
self.assertEqual(mock_crypto_util.generate_key.call_count, 2)
self.assertEqual(mock_crypto_util.generate_csr.call_count, 2)
self.assertEqual(mock_remove.call_count, 2)
self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 1)
@@ -372,13 +373,13 @@ class ClientTest(ClientTestCommon):
mock_crypto.make_key.assert_called_once_with(
bits=self.config.rsa_key_size,
elliptic_curve=None, # not making an elliptic private key
elliptic_curve="secp256r1",
key_type=self.config.key_type,
)
mock_acme_crypto.make_csr.assert_called_once_with(
mock.sentinel.key_pem, self.eg_domains, self.config.must_staple)
mock_crypto.init_save_key.assert_not_called()
mock_crypto.init_save_csr.assert_not_called()
mock_crypto.generate_key.assert_not_called()
mock_crypto.generate_csr.assert_not_called()
self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1)
@mock.patch("certbot._internal.client.logger")
@@ -521,13 +522,12 @@ class ClientTest(ClientTestCommon):
@test_util.patch_get_utility()
def test_deploy_certificate_success(self, mock_util):
self.assertRaises(errors.Error, self.client.deploy_certificate,
"foo.bar", ["foo.bar"], "key", "cert", "chain", "fullchain")
["foo.bar"], "key", "cert", "chain", "fullchain")
installer = mock.MagicMock()
self.client.installer = installer
self.client.deploy_certificate(
"foo.bar", ["foo.bar"], "key", "cert", "chain", "fullchain")
self.client.deploy_certificate(["foo.bar"], "key", "cert", "chain", "fullchain")
installer.deploy_cert.assert_called_once_with(
cert_path=os.path.abspath("cert"),
chain_path=os.path.abspath("chain"),
@@ -546,31 +546,10 @@ class ClientTest(ClientTestCommon):
installer.deploy_cert.side_effect = errors.PluginError
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
"foo.bar", ["foo.bar"], "key", "cert", "chain", "fullchain")
["foo.bar"], "key", "cert", "chain", "fullchain")
installer.recovery_routine.assert_called_once_with()
mock_notify.assert_any_call('Deploying certificate')
mock_notify.assert_any_call(
'Failed to install the certificate (installer: foobar). '
'Try again after fixing errors by running:\n\n certbot install --cert-name foo.bar\n'
)
@mock.patch('certbot._internal.client.display_util.notify')
@test_util.patch_get_utility()
def test_deploy_certificate_failure_no_certname(self, mock_util, mock_notify):
installer = mock.MagicMock()
self.client.installer = installer
self.config.installer = "foobar"
installer.deploy_cert.side_effect = errors.PluginError
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
None, ["foo.bar"], "key", "cert", "chain", "fullchain")
installer.recovery_routine.assert_called_once_with()
mock_notify.assert_any_call('Deploying certificate')
mock_notify.assert_any_call(
'Failed to install the certificate (installer: foobar).'
)
@test_util.patch_get_utility()
@@ -580,7 +559,7 @@ class ClientTest(ClientTestCommon):
installer.save.side_effect = errors.PluginError
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
"foo.bar", ["foo.bar"], "key", "cert", "chain", "fullchain")
["foo.bar"], "key", "cert", "chain", "fullchain")
installer.recovery_routine.assert_called_once_with()
@mock.patch('certbot._internal.client.display_util.notify')
@@ -591,7 +570,7 @@ class ClientTest(ClientTestCommon):
self.client.installer = installer
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
"foo.bar", ["foo.bar"], "key", "cert", "chain", "fullchain")
["foo.bar"], "key", "cert", "chain", "fullchain")
mock_notify.assert_called_with(
'We were unable to install your certificate, however, we successfully restored '
'your server to its prior configuration.')
@@ -607,7 +586,7 @@ class ClientTest(ClientTestCommon):
self.client.installer = installer
self.assertRaises(errors.PluginError, self.client.deploy_certificate,
"foo.bar", ["foo.bar"], "key", "cert", "chain", "fullchain")
["foo.bar"], "key", "cert", "chain", "fullchain")
self.assertEqual(mock_logger.error.call_count, 1)
self.assertIn(
'An error occurred and we failed to restore your config',

View File

@@ -2,15 +2,15 @@
import logging
import unittest
import certbot.util
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock
import OpenSSL
import zope.component
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import filesystem
from certbot.compat import os
@@ -33,8 +33,8 @@ CERT_ISSUER = test_util.load_vector('cert_intermediate_1.pem')
CERT_ALT_ISSUER = test_util.load_vector('cert_intermediate_2.pem')
class InitSaveKeyTest(test_util.TempDirTestCase):
"""Tests for certbot.crypto_util.init_save_key."""
class GenerateKeyTest(test_util.TempDirTestCase):
"""Tests for certbot.crypto_util.generate_key."""
def setUp(self):
super().setUp()
@@ -42,8 +42,6 @@ class InitSaveKeyTest(test_util.TempDirTestCase):
filesystem.mkdir(self.workdir, mode=0o700)
logging.disable(logging.CRITICAL)
zope.component.provideUtility(
mock.Mock(strict_permissions=True), interfaces.IConfig)
def tearDown(self):
super().tearDown()
@@ -52,8 +50,8 @@ class InitSaveKeyTest(test_util.TempDirTestCase):
@classmethod
def _call(cls, key_size, key_dir):
from certbot.crypto_util import init_save_key
return init_save_key(key_size, key_dir, 'key-certbot.pem')
from certbot.crypto_util import generate_key
return generate_key(key_size, key_dir, 'key-certbot.pem', strict_permissions=True)
@mock.patch('certbot.crypto_util.make_key')
def test_success(self, mock_make):
@@ -69,29 +67,57 @@ class InitSaveKeyTest(test_util.TempDirTestCase):
self.assertRaises(ValueError, self._call, 431, self.workdir)
class InitSaveCSRTest(test_util.TempDirTestCase):
"""Tests for certbot.crypto_util.init_save_csr."""
class InitSaveKey(unittest.TestCase):
"""Test for certbot.crypto_util.init_save_key."""
@mock.patch("certbot.crypto_util.generate_key")
@mock.patch("certbot.crypto_util.zope.component")
def test_it(self, mock_zope, mock_generate):
from certbot.crypto_util import init_save_key
def setUp(self):
super().setUp()
mock_zope.getUtility.return_value = mock.MagicMock(strict_permissions=True)
zope.component.provideUtility(
mock.Mock(strict_permissions=True), interfaces.IConfig)
with self.assertWarns(DeprecationWarning):
init_save_key(4096, "/some/path")
mock_generate.assert_called_with(4096, "/some/path", elliptic_curve="secp256r1",
key_type="rsa", keyname="key-certbot.pem",
strict_permissions=True)
class GenerateCSRTest(test_util.TempDirTestCase):
"""Tests for certbot.crypto_util.generate_csr."""
@mock.patch('acme.crypto_util.make_csr')
@mock.patch('certbot.crypto_util.util.make_or_verify_dir')
def test_it(self, unused_mock_verify, mock_csr):
from certbot.crypto_util import init_save_csr
from certbot.crypto_util import generate_csr
mock_csr.return_value = b'csr_pem'
csr = init_save_csr(
mock.Mock(pem='dummy_key'), 'example.com', self.tempdir)
csr = generate_csr(
mock.Mock(pem='dummy_key'), 'example.com', self.tempdir, strict_permissions=True)
self.assertEqual(csr.data, b'csr_pem')
self.assertIn('csr-certbot.pem', csr.file)
class InitSaveCsr(unittest.TestCase):
"""Tests for certbot.crypto_util.init_save_csr."""
@mock.patch("certbot.crypto_util.generate_csr")
@mock.patch("certbot.crypto_util.zope.component")
def test_it(self, mock_zope, mock_generate):
from certbot.crypto_util import init_save_csr
mock_zope.getUtility.return_value = mock.MagicMock(must_staple=True,
strict_permissions=True)
key = certbot.util.Key(file=None, pem=None)
with self.assertWarns(DeprecationWarning):
init_save_csr(key, {"dummy"}, "/some/path")
mock_generate.assert_called_with(key, {"dummy"}, "/some/path",
must_staple=True, strict_permissions=True)
class ValidCSRTest(unittest.TestCase):
"""Tests for certbot.crypto_util.valid_csr."""

View File

@@ -209,7 +209,6 @@ class ChooseNamesTest(unittest.TestCase):
actual_doms = self._call(self.mock_install)
self.assertEqual(mock_util().input.call_count, 1)
self.assertEqual(actual_doms, [domain])
self.assertIn("configuration files", mock_util().input.call_args[0][0])
def test_sort_names_trivial(self):
from certbot.display.ops import _sort_names

View File

@@ -28,8 +28,13 @@ class PreArgParseSetupTest(unittest.TestCase):
@classmethod
def _call(cls, *args, **kwargs): # pylint: disable=unused-argument
from certbot._internal.log import pre_arg_parse_setup
with mock.patch('builtins.open', mock.mock_open()):
return pre_arg_parse_setup()
return pre_arg_parse_setup()
def tearDown(self):
# We need to call logging.shutdown() at the end of this test to
# properly clean up any resources created by pre_arg_parse_setup.
logging.shutdown()
super().tearDown()
@mock.patch('certbot._internal.log.sys')
@mock.patch('certbot._internal.log.pre_arg_parse_except_hook')

View File

@@ -112,6 +112,7 @@ class RunTest(test_util.ConfigTestCase):
mock.patch('certbot._internal.main._report_new_cert'),
mock.patch('certbot._internal.main._find_cert'),
mock.patch('certbot._internal.eff.handle_subscription'),
mock.patch('certbot._internal.main._report_next_steps')
]
self.mock_auth = patches[0].start()
@@ -122,6 +123,7 @@ class RunTest(test_util.ConfigTestCase):
self.mock_report_cert = patches[5].start()
self.mock_find_cert = patches[6].start()
self.mock_subscription = patches[7].start()
self.mock_report_next_steps = patches[8].start()
for patch in patches:
self.addCleanup(patch.stop)
@@ -139,6 +141,8 @@ class RunTest(test_util.ConfigTestCase):
self.mock_find_cert.return_value = True, None
self._call()
self.mock_success_installation.assert_called_once_with([self.domain])
self.mock_report_next_steps.assert_called_once_with(mock.ANY, None, mock.ANY,
new_or_renewed_cert=True)
def test_reinstall_success(self):
self.mock_auth.return_value = mock.Mock()
@@ -161,6 +165,18 @@ class RunTest(test_util.ConfigTestCase):
main.run,
self.config, plugins)
@mock.patch('certbot._internal.main._install_cert')
def test_cert_success_install_error(self, mock_install_cert):
mock_install_cert.side_effect = errors.PluginError("Fake installation error")
self.mock_auth.return_value = mock.Mock()
self.mock_find_cert.return_value = True, None
self.assertRaises(errors.PluginError, self._call)
# Next steps should contain both renewal advice and installation error
self.mock_report_next_steps.assert_called_once_with(
mock.ANY, mock_install_cert.side_effect, mock.ANY, new_or_renewed_cert=True)
# The final success message shouldn't be shown
self.mock_success_installation.assert_not_called()
class CertonlyTest(unittest.TestCase):
"""Tests for certbot._internal.main.certonly."""
@@ -198,13 +214,14 @@ class CertonlyTest(unittest.TestCase):
def _assert_no_pause(self, message, pause=True): # pylint: disable=unused-argument
self.assertIs(pause, False)
@mock.patch('certbot._internal.main._report_next_steps')
@mock.patch('certbot._internal.cert_manager.lineage_for_certname')
@mock.patch('certbot._internal.cert_manager.domains_for_certname')
@mock.patch('certbot._internal.renewal.renew_cert')
@mock.patch('certbot._internal.main._handle_unexpected_key_type_migration')
@mock.patch('certbot._internal.main._report_new_cert')
def test_find_lineage_for_domains_and_certname(self, mock_report_cert,
mock_handle_type, mock_renew_cert, mock_domains, mock_lineage):
mock_handle_type, mock_renew_cert, mock_domains, mock_lineage, mock_report_next_steps):
domains = ['example.com', 'test.org']
mock_domains.return_value = domains
mock_lineage.names.return_value = domains
@@ -216,6 +233,8 @@ class CertonlyTest(unittest.TestCase):
self.assertEqual(mock_renew_cert.call_count, 1)
self.assertEqual(mock_report_cert.call_count, 1)
self.assertEqual(mock_handle_type.call_count, 1)
mock_report_next_steps.assert_called_once_with(
mock.ANY, None, mock.ANY, new_or_renewed_cert=True)
# user confirms updating lineage with new domains
self._call(('certonly --webroot -d example.com -d test.com '
@@ -231,12 +250,13 @@ class CertonlyTest(unittest.TestCase):
self.assertRaises(errors.ConfigurationError, self._call,
'certonly --webroot -d example.com -d test.com --cert-name example.com'.split())
@mock.patch('certbot._internal.main._report_next_steps')
@mock.patch('certbot._internal.cert_manager.domains_for_certname')
@mock.patch('certbot.display.ops.choose_names')
@mock.patch('certbot._internal.cert_manager.lineage_for_certname')
@mock.patch('certbot._internal.main._report_new_cert')
def test_find_lineage_for_domains_new_certname(self, mock_report_cert,
mock_lineage, mock_choose_names, mock_domains_for_certname):
mock_lineage, mock_choose_names, mock_domains_for_certname, unused_mock_report_next_steps):
mock_lineage.return_value = None
# no lineage with this name but we specified domains so create a new cert
@@ -1823,7 +1843,8 @@ class ReportNewCertTest(unittest.TestCase):
'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.\n'
'Certbot will automatically renew this certificate in the background.'
'Certbot has set up a scheduled task to automatically renew this '
'certificate in the background.'
)
def test_report_no_key(self):
@@ -1836,7 +1857,8 @@ class ReportNewCertTest(unittest.TestCase):
'Certificate is saved at: /path/to/fullchain.pem\n'
'This certificate expires on 1970-01-01.\n'
'These files will be updated when the certificate renews.\n'
'Certbot will automatically renew this certificate in the background.'
'Certbot has set up a scheduled task to automatically renew this '
'certificate in the background.'
)
def test_report_no_preconfigured_renewal(self):
@@ -1849,13 +1871,9 @@ class ReportNewCertTest(unittest.TestCase):
'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.\n'
'Run "certbot renew" to renew expiring certificates. We recommend setting up a '
'scheduled task for renewal; see https://certbot.eff.org/docs/using.html#automated'
'-renewals for instructions.'
'These files will be updated when the certificate renews.'
)
def test_csr_report(self):
self._call_csr(mock.Mock(dry_run=False), '/path/to/cert.pem',
'/path/to/chain.pem', '/path/to/fullchain.pem')

View File

@@ -9,5 +9,10 @@
# deprecation warnings and gives time for plugins that don't use the deprecated
# API to propagate, especially for plugins packaged as an external snap, before
# we release breaking changes.
#
# The current warnings being ignored are:
# 1) The warning raised when importing certbot.tests.util and the external mock
# library is installed.
filterwarnings =
error
ignore:The external mock module:PendingDeprecationWarning

View File

@@ -87,14 +87,6 @@ for pkg_dir in $SUBPKGS certbot-compatibility-test
do
sed -i 's/\.dev0//' "$pkg_dir/setup.py"
git add "$pkg_dir/setup.py"
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 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
SetVersion() {

View File

@@ -1,48 +0,0 @@
"""
Given an ACME account key as input, deactivate the account.
This can be useful if you created an account with a non-Certbot client and now
want to deactivate it.
Private key should be in PKCS#8 PEM form.
To provide the URL for the ACME server you want to use, set it in the $DIRECTORY
environment variable, e.g.:
DIRECTORY=https://acme-staging.api.letsencrypt.org/directory python \
deactivate.py private_key.pem
"""
import os
import sys
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import josepy as jose
from acme import client as acme_client
from acme import errors as acme_errors
from acme import messages
DIRECTORY = os.getenv('DIRECTORY', 'http://localhost:4000/directory')
if len(sys.argv) != 2:
print("Usage: python deactivate.py private_key.pem")
sys.exit(1)
data = open(sys.argv[1], "r").read()
key = jose.JWKRSA(key=serialization.load_pem_private_key(
data, None, default_backend()))
net = acme_client.ClientNetwork(key, verify_ssl=False,
user_agent="acme account deactivator")
client = acme_client.Client(DIRECTORY, key=key, net=net)
try:
# We expect this to fail and give us a Conflict response with a Location
# header pointing at the account's URL.
client.register()
except acme_errors.ConflictError as e:
location = e.location
if location is None:
raise "Key was not previously registered (but now is)."
client.deactivate_registration(messages.RegistrationResource(uri=location))

View File

@@ -1,123 +0,0 @@
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
// This program can be used to perform RSA public key signatures given only
// the hash of the file to be signed as input.
// To compile:
// gcc half-sign.c -lssl -lcrypto -o half-sign
// Sign with SHA256
#define HASH_SIZE 32
void usage() {
printf("half-sign <private key file> [binary hash file]\n");
printf("\n");
printf(" Computes and prints a binary RSA signature over data given the SHA256 hash of\n");
printf(" the data as input.\n");
printf("\n");
printf(" <private key file> should be PEM encoded.\n");
printf("\n");
printf(" The input SHA256 hash should be %d bytes in length. If no binary hash file is\n", HASH_SIZE);
printf(" specified, it will be read from stdin.\n");
exit(1);
}
void sign_hashed_data(EVP_PKEY *signing_key, unsigned char *md, size_t mdlen) {
// cribbed from the openssl EVP_PKEY_sign man page
EVP_PKEY_CTX *ctx;
unsigned char *sig;
size_t siglen;
/* NB: assumes signing_key, md and mdlen are already set up
* and that signing_key is an RSA private key
*/
ctx = EVP_PKEY_CTX_new(signing_key, NULL);
if ((!ctx)
|| (EVP_PKEY_sign_init(ctx) <= 0)
|| (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0)
|| (EVP_PKEY_CTX_set_signature_md(ctx, EVP_sha256()) <= 0)) {
fprintf(stderr, "Failure establishing ctx for signature\n");
exit(1);
}
/* Determine buffer length */
if (EVP_PKEY_sign(ctx, NULL, &siglen, md, mdlen) <= 0) {
fprintf(stderr, "Unable to determine buffer length for signature\n");
exit(1);
}
sig = OPENSSL_malloc(siglen);
if (!sig) {
fprintf(stderr, "Malloc failed\n");
exit(1);
}
if (EVP_PKEY_sign(ctx, sig, &siglen, md, mdlen) <= 0) {
fprintf(stderr, "Signature error\n");
exit(1);
}
/* Signature is siglen bytes written to buffer sig */
fwrite(sig, siglen, 1, stdout);
}
EVP_PKEY *read_private_key(char *filename) {
FILE *keyfile;
EVP_PKEY *privkey;
keyfile = fopen(filename, "r");
if (!keyfile) {
fprintf(stderr, "Failed to open private key.pem file %s\n", filename);
exit(1);
}
privkey = PEM_read_PrivateKey(keyfile, NULL, NULL, NULL);
if (!privkey) {
fprintf(stderr, "Failed to read PEM private key from %s\n", filename);
exit(1);
}
if (EVP_PKEY_type(privkey->type) != EVP_PKEY_RSA) {
fprintf(stderr, "%s was a non-RSA key\n", filename);
exit(1);
}
return privkey;
}
int main(int argc, char *argv[]) {
FILE *input;
unsigned char *buffer;
int test;
EVP_PKEY *privkey;
if (argc > 3 || argc < 2)
usage();
if (argc < 3 || strcmp(argv[2],"-") == 0)
input = stdin;
else {
input = fopen(argv[2], "r");
if (!input) usage();
}
privkey = read_private_key(argv[1]);
buffer = malloc(HASH_SIZE);
if (!buffer) {
fprintf(stderr, "Argh, malloc failed\n");
exit(1);
}
if (fread(buffer, HASH_SIZE, 1, input) != 1) {
perror("half-sign: Failed to read SHA256 from input\n");
exit(1);
}
test = fgetc(input);
if (test != EOF && test != '\n') {
fprintf(stderr,"Error, more than %d bytes fed to half-sign\n", HASH_SIZE);
fprintf(stderr,"Last byte was :%d\n" , (int) test);
exit(1);
}
sign_hashed_data(privkey, buffer, HASH_SIZE);
return 0;
}

View File

@@ -74,6 +74,4 @@ parsedatetime==2.4
# Tracking at https://github.com/certbot/certbot/issues/6473
boto3==1.4.7
botocore==1.7.41
# Lexicon oldest constraint is overridden appropriately on relevant DNS provider plugins
# using their local-oldest-requirements.txt
dns-lexicon==3.1.0

View File

@@ -17,6 +17,12 @@ if ! command -v poetry >/dev/null; then
exit 1
fi
# Old eggs can cause outdated dependency information to be used by poetry so we
# delete them before generating the lock file. See
# https://github.com/python-poetry/poetry/issues/4103 for more info.
cd "${REPO_ROOT}"
rm -rf */*.egg-info
cd "${WORK_DIR}"
if [ -f poetry.lock ]; then

View File

@@ -58,11 +58,6 @@ cython = "*"
# needed. This dependency can be removed here once Certbot's support for the
# 3rd party mock library has been dropped.
mock = "*"
# Upgrading coverage, pytest, and some of pytest's plugins causes many test
# failures so let's pin these packages back for now.
coverage = "4.5.4"
pytest = "3.2.5"
pytest-forked = "0.2"
# We were originally pinning back python-augeas for certbot-auto because we
# found the way older versions of the library linked to Augeas were more
# reliable. That's no longer a concern, however, we continue to pin back the

View File

@@ -1,10 +1,7 @@
#!/usr/bin/env python
# pip installs packages using pinned package versions. If CERTBOT_OLDEST is set
# to 1, a combination of tools/oldest_constraints.txt,
# tools/dev_constraints.txt, and local-oldest-requirements.txt contained in the
# top level of the package's directory is used, otherwise,
# tools/requirements.txt is used. If CERTBOT_OLDEST is set, this script must
# be run with `-e <package-name>` and no other arguments.
# to 1, a combination of tools/oldest_constraints.txt and
# tools/dev_constraints.txt is used, otherwise, tools/requirements.txt is used.
from __future__ import absolute_import
from __future__ import print_function
@@ -37,27 +34,13 @@ def find_tools_path():
return os.path.dirname(readlink.main(__file__))
def certbot_oldest_processing(tools_path, args, constraints_path):
if args[0] != '-e' or len(args) != 2:
raise ValueError('When CERTBOT_OLDEST is set, this script must be run '
'with a single -e <path> argument.')
# remove any extras such as [dev]
pkg_dir = re.sub(r'\[\w+\]', '', args[1])
def certbot_oldest_processing(tools_path, constraints_path):
# The order of the files in this list matters as files specified later can
# override the pinnings found in earlier files.
pinning_files = [os.path.join(tools_path, 'dev_constraints.txt'),
os.path.join(tools_path, 'oldest_constraints.txt')]
requirements = os.path.join(pkg_dir, 'local-oldest-requirements.txt')
# packages like acme don't have any local oldest requirements
if os.path.isfile(requirements):
# We add requirements to the end of the list so it can override
# anything that it needs to.
pinning_files.append(requirements)
else:
requirements = None
with open(constraints_path, 'w') as fd:
fd.write(merge_module.main(*pinning_files))
return requirements
def certbot_normal_processing(tools_path, constraints_path):
@@ -91,27 +74,14 @@ def main(args):
else:
# Otherwise, we merge requirements to build the constraints and pin dependencies
constraints_path = os.path.join(working_dir, 'constraints.txt')
requirements = None
if os.environ.get('CERTBOT_OLDEST') == '1':
requirements = certbot_oldest_processing(tools_path, args, constraints_path)
certbot_oldest_processing(tools_path, constraints_path)
else:
certbot_normal_processing(tools_path, constraints_path)
env = os.environ.copy()
env["PIP_CONSTRAINT"] = constraints_path
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('--requirement "{0}"'.format(requirements),
env=env)
# 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))
print(' '.join(args))
pip_install_with_print(' '.join(args), env=env)

View File

@@ -11,16 +11,17 @@ apipkg==1.5; python_version >= "3.6" and python_full_version < "3.0.0" or python
appdirs==1.4.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
appnope==0.1.2
astroid==2.5.6; python_version >= "3.6" and python_version < "4.0"
atomicwrites==1.4.0; python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.6" and python_full_version >= "3.4.0"
attrs==21.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
awscli==1.19.77; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
awscli==1.19.83; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.6.0")
azure-devops==6.0.0b4; python_version >= "3.6"
babel==2.9.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
backcall==0.2.0
bcrypt==3.2.0; python_version >= "3.6"
beautifulsoup4==4.9.3; python_version >= "3.6" and python_version < "4.0"
bleach==3.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
boto3==1.17.77; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
botocore==1.20.77; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
boto3==1.17.83; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
botocore==1.20.83; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
cachecontrol==0.12.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
cached-property==1.5.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
cachetools==4.2.2; python_version >= "3.5" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6")
@@ -34,7 +35,7 @@ cloudflare==2.8.15; python_version >= "3.6"
colorama==0.4.3; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
configargparse==1.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
configobj==5.0.6; python_version >= "3.6"
coverage==4.5.4; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0" and python_version < "4")
coverage==5.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.6"
crashtest==0.3.1; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0")
cryptography==3.4.7; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux" or python_full_version >= "3.5.0" and python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") and sys_platform == "linux"
cython==0.29.23; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
@@ -49,25 +50,26 @@ docker==4.2.2; python_version >= "3.6" and python_full_version < "3.0.0" or pyth
dockerpty==0.4.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
docopt==0.6.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
docutils==0.15.2; (python_version >= "2.6" and python_full_version < "3.0.0") or (python_full_version >= "3.3.0")
execnet==1.8.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
execnet==1.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
fabric==2.6.0; python_version >= "3.6"
filelock==3.0.12; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "4.0"
google-api-core==1.28.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
google-api-python-client==2.5.0; python_version >= "3.6"
google-api-python-client==2.6.0; python_version >= "3.6"
google-auth-httplib2==0.1.0; python_version >= "3.6"
google-auth==1.30.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
google-auth==1.30.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
googleapis-common-protos==1.53.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
html5lib==1.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
httplib2==0.19.1; python_version >= "3.6"
idna==2.10; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version >= "3.6" and python_version < "4.0"
imagesize==1.2.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
importlib-metadata==1.7.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.8" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0")
importlib-metadata==1.7.0; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.8" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0") or python_version < "3.8" and python_version >= "3.6" and python_full_version >= "3.5.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0")
importlib-resources==5.1.4; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.7"
iniconfig==1.1.1; python_version >= "3.6"
invoke==1.5.0; python_version >= "3.6"
ipdb==0.13.7; python_version >= "3.6"
ipdb==0.13.8; python_version >= "3.6"
ipython-genutils==0.2.0; python_version == "3.6"
ipython==7.16.1; python_version == "3.6"
ipython==7.23.1; python_version >= "3.7"
ipython==7.24.0; python_version >= "3.7"
isodate==0.6.0; python_version >= "3.6"
isort==5.8.0; python_version >= "3.6" and python_version < "4.0"
jedi==0.18.0
@@ -100,12 +102,12 @@ pathlib2==2.3.5; python_version >= "3.6"
pexpect==4.8.0
pickleshare==0.7.5
pkginfo==1.7.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
pluggy==0.13.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
ply==3.11; python_version >= "3.6"
poetry-core==1.0.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
poetry==1.1.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
prompt-toolkit==3.0.3
protobuf==3.17.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
protobuf==3.17.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
ptyprocess==0.7.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
py==1.10.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pyasn1-modules==0.2.8; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
@@ -123,10 +125,10 @@ pyparsing==2.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or p
pypiwin32==223; sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6")
pyrfc3339==1.1; python_version >= "3.6"
pyrsistent==0.17.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
pytest-cov==2.6.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
pytest-forked==0.2
pytest-xdist==1.24.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
pytest==3.2.5
pytest-cov==2.12.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pytest-forked==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
pytest-xdist==2.2.1; python_version >= "3.6"
pytest==6.2.4; python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6")
python-augeas==0.5.0
python-dateutil==2.8.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version >= "3.6"
python-digitalocean==1.16.0; python_version >= "3.6"
@@ -154,29 +156,29 @@ sphinx-rtd-theme==0.5.2; python_version >= "3.6"
sphinx==3.5.4; python_version >= "3.5"
sphinxcontrib-applehelp==1.0.2; python_version >= "3.6"
sphinxcontrib-devhelp==1.0.2; python_version >= "3.6"
sphinxcontrib-htmlhelp==1.0.3; python_version >= "3.6"
sphinxcontrib-htmlhelp==2.0.0; python_version >= "3.6"
sphinxcontrib-jsmath==1.0.1; python_version >= "3.6"
sphinxcontrib-qthelp==1.0.3; python_version >= "3.6"
sphinxcontrib-serializinghtml==1.1.4; python_version >= "3.6"
sphinxcontrib-serializinghtml==1.1.5; python_version >= "3.6"
texttable==1.6.3; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
tldextract==3.1.0; python_version >= "3.6" and python_version < "4.0"
toml==0.10.2; python_version == "3.6" and python_full_version < "3.0.0" or python_version > "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version == "3.6" and python_full_version >= "3.5.0" or python_version > "3.6" and python_full_version >= "3.5.0" and python_version < "4.0"
toml==0.10.2; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "4.0" or python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.5.0"
tomlkit==0.7.2; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
tox==3.23.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
tqdm==4.60.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
tqdm==4.61.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0"
traitlets==4.3.3
twine==3.3.0; python_version >= "3.6"
typed-ast==1.4.3; implementation_name == "cpython" and python_version < "3.8" and python_version >= "3.6"
typing-extensions==3.10.0.0; python_version >= "3.6"
uritemplate==3.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
urllib3==1.26.4; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6"
virtualenv==20.4.6; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
urllib3==1.26.5; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.6.0" and python_version < "4" and python_version >= "3.6"
virtualenv==20.4.7; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
wcwidth==0.2.5; python_version == "3.6"
webencodings==0.5.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.5.0"
websocket-client==0.59.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
wrapt==1.12.1; python_version >= "3.6" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_version >= "3.6" and python_full_version >= "3.4.0")
yarg==0.1.9; python_version >= "3.6"
zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version >= "3.6" and python_full_version >= "3.5.0" and python_version < "3.7"
zipp==3.4.1; python_version >= "3.6" and python_full_version < "3.0.0" and python_version < "3.7" or python_version < "3.7" and python_version >= "3.6" and python_full_version >= "3.5.0"
zope.component==5.0.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
zope.event==4.5.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
zope.hookable==5.0.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"

View File

@@ -1,31 +0,0 @@
#!/usr/bin/env python3
"""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.
A message is printed to stdout specifying the address and port being used
by the server.
:param int port: port number to use.
"""
server = HTTPServer(('', port), SimpleHTTPRequestHandler)
print('Serving HTTP on {0} port {1} ...'.format(*server.server_address))
sys.stdout.flush()
server.serve_forever()
if __name__ == '__main__':
kwargs = {}
if len(sys.argv) > 1:
kwargs['port'] = int(sys.argv[1])
serve_forever(**kwargs)

73
tox.ini
View File

@@ -14,51 +14,13 @@ pip_install = python {toxinidir}/tools/pip_install_editable.py
# before the script moves on to the next package. All dependencies are pinned
# to a specific version for increased stability for developers.
install_and_test = python {toxinidir}/tools/install_and_test.py
dns_packages =
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
all_packages =
acme[dev] \
certbot[dev] \
certbot-apache \
{[base]dns_packages} \
certbot-nginx
install_packages =
python {toxinidir}/tools/pip_install_editable.py {[base]all_packages}
source_paths =
acme/acme
certbot/certbot
certbot-ci/certbot_integration_tests
certbot-apache/certbot_apache
certbot-compatibility-test/certbot_compatibility_test
certbot-dns-cloudflare/certbot_dns_cloudflare
certbot-dns-cloudxns/certbot_dns_cloudxns
certbot-dns-digitalocean/certbot_dns_digitalocean
certbot-dns-dnsimple/certbot_dns_dnsimple
certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy
certbot-dns-gehirn/certbot_dns_gehirn
certbot-dns-google/certbot_dns_google
certbot-dns-linode/certbot_dns_linode
certbot-dns-luadns/certbot_dns_luadns
certbot-dns-nsone/certbot_dns_nsone
certbot-dns-ovh/certbot_dns_ovh
certbot-dns-rfc2136/certbot_dns_rfc2136
certbot-dns-route53/certbot_dns_route53
certbot-dns-sakuracloud/certbot_dns_sakuracloud
certbot-nginx/certbot_nginx
tests/lock_test.py
install_packages = python {toxinidir}/tools/pip_install_editable.py {[base]all_packages}
# Packages are listed on one line because tox seems to have inconsistent
# behavior with substitutions that contain line continuations, see
# https://github.com/tox-dev/tox/issues/2069 for more info.
dns_packages = 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
all_packages = acme[dev] certbot[dev] certbot-apache {[base]dns_packages} certbot-nginx
source_paths = acme/acme certbot/certbot certbot-ci/certbot_integration_tests certbot-apache/certbot_apache certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare certbot-dns-cloudxns/certbot_dns_cloudxns certbot-dns-digitalocean/certbot_dns_digitalocean certbot-dns-dnsimple/certbot_dns_dnsimple certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy certbot-dns-gehirn/certbot_dns_gehirn certbot-dns-google/certbot_dns_google certbot-dns-linode/certbot_dns_linode certbot-dns-luadns/certbot_dns_luadns certbot-dns-nsone/certbot_dns_nsone certbot-dns-ovh/certbot_dns_ovh certbot-dns-rfc2136/certbot_dns_rfc2136 certbot-dns-route53/certbot_dns_route53 certbot-dns-sakuracloud/certbot_dns_sakuracloud certbot-nginx/certbot_nginx tests/lock_test.py
[testenv]
passenv =
@@ -100,7 +62,8 @@ setenv =
basepython =
{[testenv:oldest]basepython}
commands =
{[base]install_and_test} certbot-apache
{[base]pip_install} acme[dev] certbot[dev] certbot-apache
pytest certbot-apache
setenv =
{[testenv:oldest]setenv}
@@ -108,7 +71,8 @@ setenv =
basepython =
{[testenv:oldest]basepython}
commands =
{[base]install_and_test} certbot-apache[dev]
{[base]pip_install} acme[dev] certbot[dev] certbot-apache[dev]
pytest certbot-apache
setenv =
{[testenv:oldest]setenv}
@@ -116,7 +80,8 @@ setenv =
basepython =
{[testenv:oldest]basepython}
commands =
{[base]install_and_test} certbot[dev]
{[base]pip_install} acme[dev] certbot[dev]
pytest certbot
setenv =
{[testenv:oldest]setenv}
@@ -124,7 +89,8 @@ setenv =
basepython =
{[testenv:oldest]basepython}
commands =
{[base]install_and_test} {[base]dns_packages}
{[base]pip_install} acme[dev] certbot[dev] {[base]dns_packages}
pytest {[base]dns_packages}
setenv =
{[testenv:oldest]setenv}
@@ -132,7 +98,8 @@ setenv =
basepython =
{[testenv:oldest]basepython}
commands =
{[base]install_and_test} certbot-nginx
{[base]pip_install} acme[dev] certbot[dev] certbot-nginx
pytest certbot-nginx
python tests/lock_test.py
setenv =
{[testenv:oldest]setenv}
@@ -258,8 +225,7 @@ passenv = DOCKER_*
basepython =
{[testenv:oldest]basepython}
commands =
{[base]pip_install} certbot
{[base]pip_install} certbot-ci
{[base]pip_install} acme certbot certbot-ci
pytest certbot-ci/certbot_integration_tests/certbot_tests \
--acme-server={env:ACME_SERVER:pebble}
passenv = DOCKER_*
@@ -269,8 +235,7 @@ setenv = {[testenv:oldest]setenv}
basepython =
{[testenv:oldest]basepython}
commands =
{[base]pip_install} certbot-nginx
{[base]pip_install} certbot-ci
{[base]pip_install} acme certbot certbot-nginx certbot-ci
pytest certbot-ci/certbot_integration_tests/nginx_tests \
--acme-server={env:ACME_SERVER:pebble}
passenv = DOCKER_*

View File

@@ -0,0 +1,12 @@
"""Pynsist extra_preamble for the Certbot entry point.
This preamble ensures that Certbot on Windows always runs with the --preconfigured-renewal
flag set. Since Pynsist creates a Scheduled Task for renewal, we want this flag to be set
so that we can provide the right automated renewal advice to Certbot on Windows users.
"""
import sys
sys.argv += ["--preconfigured-renewal"]

View File

@@ -82,6 +82,7 @@ def _copy_assets(build_path, repo_path):
shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'template.nsi'), build_path)
shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'renew-up.ps1'), build_path)
shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'renew-down.ps1'), build_path)
shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'preamble.py'), build_path)
def _generate_pynsist_config(repo_path, build_path):
@@ -121,6 +122,7 @@ files=run.bat
[Command certbot]
entry_point=certbot.main:main
extra_preamble=preamble.py
'''.format(certbot_version=certbot_version,
installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32',
python_bitness=PYTHON_BITNESS,