diff --git a/certbot-apache/certbot_apache/_internal/apache_util.py b/certbot-apache/certbot_apache/_internal/apache_util.py index 862685027..93612f424 100644 --- a/certbot-apache/certbot_apache/_internal/apache_util.py +++ b/certbot-apache/certbot_apache/_internal/apache_util.py @@ -225,7 +225,8 @@ def _get_runtime_cfg(command): command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, + env=util.env_no_snap_for_external_calls()) stdout, stderr = proc.communicate() except (OSError, ValueError): diff --git a/certbot-dns-cloudflare/snap/snapcraft.yaml b/certbot-dns-cloudflare/snap/snapcraft.yaml new file mode 100644 index 000000000..97d1dab42 --- /dev/null +++ b/certbot-dns-cloudflare/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-cloudflare +summary: Cloudflare DNS Authenticator plugin for Certbot +description: Cloudflare DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-cloudflare + +parts: + certbot-dns-cloudflare: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-cloudxns/snap/snapcraft.yaml b/certbot-dns-cloudxns/snap/snapcraft.yaml new file mode 100644 index 000000000..fb60590eb --- /dev/null +++ b/certbot-dns-cloudxns/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-cloudxns +summary: CloudXNS DNS Authenticator plugin for Certbot +description: CloudXNS DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-cloudxns + +parts: + certbot-dns-cloudxns: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-digitalocean/snap/snapcraft.yaml b/certbot-dns-digitalocean/snap/snapcraft.yaml new file mode 100644 index 000000000..40d05311f --- /dev/null +++ b/certbot-dns-digitalocean/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-digitalocean +summary: DigitalOcean DNS Authenticator plugin for Certbot +description: DigitalOcean DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-digitalocean + +parts: + certbot-dns-digitalocean: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-dnsimple/snap/snapcraft.yaml b/certbot-dns-dnsimple/snap/snapcraft.yaml index 64be45436..2d1d53e02 100644 --- a/certbot-dns-dnsimple/snap/snapcraft.yaml +++ b/certbot-dns-dnsimple/snap/snapcraft.yaml @@ -1,9 +1,9 @@ name: certbot-dns-dnsimple summary: DNSimple DNS Authenticator plugin for Certbot -description: TBC +description: DNSimple DNS Authenticator plugin for Certbot confinement: strict grade: devel -base: core18 +base: core20 adopt-info: certbot-dns-dnsimple parts: @@ -11,7 +11,6 @@ parts: plugin: python source: . constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] - python-version: python3 override-pull: | snapcraftctl pull snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` @@ -23,4 +22,4 @@ slots: interface: content content: certbot-1 read: - - $SNAP/lib/python3.6/site-packages + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-dnsmadeeasy/snap/snapcraft.yaml b/certbot-dns-dnsmadeeasy/snap/snapcraft.yaml new file mode 100644 index 000000000..da6ba93be --- /dev/null +++ b/certbot-dns-dnsmadeeasy/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-dnsmadeeasy +summary: DNS Made Easy DNS Authenticator plugin for Certbot +description: DNS Made Easy DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-dnsmadeeasy + +parts: + certbot-dns-dnsmadeeasy: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-gehirn/snap/snapcraft.yaml b/certbot-dns-gehirn/snap/snapcraft.yaml new file mode 100644 index 000000000..be664e597 --- /dev/null +++ b/certbot-dns-gehirn/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-gehirn +summary: Gehirn Infrastructure Service DNS Authenticator plugin for Certbot +description: Gehirn Infrastructure Service DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-gehirn + +parts: + certbot-dns-gehirn: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-google/snap/snapcraft.yaml b/certbot-dns-google/snap/snapcraft.yaml new file mode 100644 index 000000000..ead90696c --- /dev/null +++ b/certbot-dns-google/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-google +summary: Google Cloud DNS Authenticator plugin for Certbot +description: Google Cloud DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-google + +parts: + certbot-dns-google: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-linode/snap/snapcraft.yaml b/certbot-dns-linode/snap/snapcraft.yaml new file mode 100644 index 000000000..3895270dc --- /dev/null +++ b/certbot-dns-linode/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-linode +summary: Linode DNS Authenticator plugin for Certbot +description: Linode DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-linode + +parts: + certbot-dns-linode: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-luadns/snap/snapcraft.yaml b/certbot-dns-luadns/snap/snapcraft.yaml new file mode 100644 index 000000000..701fd76ff --- /dev/null +++ b/certbot-dns-luadns/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-luadns +summary: LuaDNS Authenticator plugin for Certbot +description: LuaDNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-luadns + +parts: + certbot-dns-luadns: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-nsone/snap/snapcraft.yaml b/certbot-dns-nsone/snap/snapcraft.yaml new file mode 100644 index 000000000..602a8dd03 --- /dev/null +++ b/certbot-dns-nsone/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-nsone +summary: NS1 DNS Authenticator plugin for Certbot +description: NS1 DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-nsone + +parts: + certbot-dns-nsone: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-ovh/snap/snapcraft.yaml b/certbot-dns-ovh/snap/snapcraft.yaml new file mode 100644 index 000000000..2949e7951 --- /dev/null +++ b/certbot-dns-ovh/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-ovh +summary: OVH DNS Authenticator plugin for Certbot +description: OVH DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-ovh + +parts: + certbot-dns-ovh: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-rfc2136/snap/snapcraft.yaml b/certbot-dns-rfc2136/snap/snapcraft.yaml new file mode 100644 index 000000000..762976f52 --- /dev/null +++ b/certbot-dns-rfc2136/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-rfc2136 +summary: RFC 2136 DNS Authenticator plugin for Certbot +description: RFC 2136 DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-rfc2136 + +parts: + certbot-dns-rfc2136: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-route53/snap/snapcraft.yaml b/certbot-dns-route53/snap/snapcraft.yaml new file mode 100644 index 000000000..a9f23730d --- /dev/null +++ b/certbot-dns-route53/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-route53 +summary: Route53 DNS Authenticator plugin for Certbot +description: Route53 DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-route53 + +parts: + certbot-dns-route53: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-dns-sakuracloud/snap/snapcraft.yaml b/certbot-dns-sakuracloud/snap/snapcraft.yaml new file mode 100644 index 000000000..bc433c8aa --- /dev/null +++ b/certbot-dns-sakuracloud/snap/snapcraft.yaml @@ -0,0 +1,25 @@ +name: certbot-dns-sakuracloud +summary: Sakura Cloud DNS Authenticator plugin for Certbot +description: Sakura Cloud DNS Authenticator plugin for Certbot +confinement: strict +grade: devel +base: core20 +adopt-info: certbot-dns-sakuracloud + +parts: + certbot-dns-sakuracloud: + plugin: python + source: . + constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version `grep ^version $SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - $SNAP/lib/python3.8/site-packages diff --git a/certbot-nginx/certbot_nginx/_internal/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py index a903c12bf..a70572c33 100644 --- a/certbot-nginx/certbot_nginx/_internal/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -939,7 +939,8 @@ class NginxConfigurator(common.Installer): [self.conf('ctl'), "-c", self.nginx_conf, "-V"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, + env=util.env_no_snap_for_external_calls()) text = proc.communicate()[1] # nginx prints output to stderr except (OSError, ValueError) as error: logger.debug(str(error), exc_info=True) @@ -1169,7 +1170,8 @@ def nginx_restart(nginx_ctl, nginx_conf): """ try: - proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"]) + proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf, "-s", "reload"], + env=util.env_no_snap_for_external_calls()) proc.communicate() if proc.returncode != 0: @@ -1179,7 +1181,7 @@ def nginx_restart(nginx_ctl, nginx_conf): with tempfile.TemporaryFile() as out: with tempfile.TemporaryFile() as err: nginx_proc = subprocess.Popen([nginx_ctl, "-c", nginx_conf], - stdout=out, stderr=err) + stdout=out, stderr=err, env=util.env_no_snap_for_external_calls()) nginx_proc.communicate() if nginx_proc.returncode != 0: # Enter recovery routine... diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index e6d9f26fd..aa0aaa0c8 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==1.4.0 -certbot[dev]==1.4.0 +-e certbot[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 073a1a4d2..48e399edb 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -12,7 +12,7 @@ version = '1.6.0.dev0' # acme/certbot version. install_requires = [ 'acme>=1.4.0', - 'certbot>=1.4.0', + 'certbot>=1.6.0.dev0', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support 'setuptools', diff --git a/certbot.wrapper b/certbot.wrapper index 199191d5f..c779e9939 100755 --- a/certbot.wrapper +++ b/certbot.wrapper @@ -11,9 +11,6 @@ join() { fi } -paths=$(for plugin_snap in $(snap connections certbot|sed -n '2,$p'|awk '$1=="content[certbot-1]"{print $3}'|cut -d: -f1); do echo /snap/$plugin_snap/current/lib/python3.6/site-packages; done) -export PYTHONPATH=$(join : $PYTHONPATH $paths) -if [ -z "$PYTHONPATH" ]; then - unset PYTHONPATH -fi +paths=$(for plugin_snap in $(snap connections certbot|sed -n '2,$p'|awk '$1=="content[certbot-1]"{print $3}'|cut -d: -f1); do echo /snap/$plugin_snap/current/lib/python3.8/site-packages; done) +export CERTBOT_PLUGIN_PATH=$(join : $paths) exec certbot "$@" diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 815ad66ca..57e273fe1 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -15,6 +15,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Changed * Allow session tickets to be disabled in Apache when mod_ssl is statically linked. +* Generalize UI warning message on renewal rate limits * Certbot behaves similarly on Windows to on UNIX systems regarding umask, and the umask `022` is applied by default: all files/directories are not writable by anyone other than the user running Certbot and the system/admin users. @@ -27,6 +28,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). * Don't use `StrictVersion`, but `LooseVersion` to check version requirements with setuptools, to fix some packaging issues with libraries respecting PEP404 for version string, with doesn't match `StrictVersion` requirements. +* Certbot output doesn't refer to SSL Labs due to confusing scoring behavior. +* Fix paths when calling to programs outside of the Certbot Snap, fixing the apache and nginx + plugins on, e.g., CentOS 7. More details about these changes can be found on our GitHub repo. diff --git a/certbot/certbot/_internal/hooks.py b/certbot/certbot/_internal/hooks.py index f26c5c76b..5526b21c4 100644 --- a/certbot/certbot/_internal/hooks.py +++ b/certbot/certbot/_internal/hooks.py @@ -228,7 +228,7 @@ def _run_hook(cmd_name, shell_cmd): :type shell_cmd: `list` of `str` or `str` :returns: stderr if there was any""" - err, _ = misc.execute_command(cmd_name, shell_cmd) + err, _ = misc.execute_command(cmd_name, shell_cmd, env=util.env_no_snap_for_external_calls()) return err diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py index cd00297a9..30f4dd0a2 100644 --- a/certbot/certbot/_internal/main.py +++ b/certbot/certbot/_internal/main.py @@ -209,7 +209,7 @@ def _handle_identical_cert_request(config, lineage): elif config.verb == "certonly": keep_opt = "Keep the existing certificate for now" choices = [keep_opt, - "Renew & replace the cert (limit ~5 per 7 days)"] + "Renew & replace the cert (may be subject to CA rate limits)"] display = zope.component.getUtility(interfaces.IDisplay) response = display.menu(question, choices, diff --git a/certbot/certbot/_internal/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py index f1d89f06a..4cce895d8 100644 --- a/certbot/certbot/_internal/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -2,6 +2,7 @@ import collections import itertools import logging +import sys import pkg_resources import six @@ -12,6 +13,7 @@ from acme.magic_typing import Dict from certbot import errors from certbot import interfaces from certbot._internal import constants +from certbot.compat import os try: # Python 3.3+ @@ -198,6 +200,12 @@ class PluginsRegistry(Mapping): def find_all(cls): """Find plugins using setuptools entry points.""" plugins = {} # type: Dict[str, PluginEntryPoint] + plugin_paths_string = os.getenv('CERTBOT_PLUGIN_PATH') + plugin_paths = plugin_paths_string.split(':') if plugin_paths_string else [] + # XXX should ensure this only happens once + sys.path.extend(plugin_paths) + for plugin_path in plugin_paths: + pkg_resources.working_set.add_entry(plugin_path) entry_points = itertools.chain( pkg_resources.iter_entry_points( constants.SETUPTOOLS_PLUGINS_ENTRY_POINT), diff --git a/certbot/certbot/_internal/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py index 430059445..48bb62632 100644 --- a/certbot/certbot/_internal/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -8,6 +8,7 @@ from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces from certbot import reverter +from certbot import util from certbot._internal import hooks from certbot.compat import misc from certbot.compat import os @@ -187,4 +188,5 @@ permitted by DNS standards.) self.reverter.recovery_routine() def _execute_hook(self, hook_name): - return misc.execute_command(self.option_name(hook_name), self.conf(hook_name)) + return misc.execute_command(self.option_name(hook_name), self.conf(hook_name), + env=util.env_no_snap_for_external_calls()) diff --git a/certbot/certbot/compat/misc.py b/certbot/certbot/compat/misc.py index 49a0814f4..f4ea4a5cc 100644 --- a/certbot/certbot/compat/misc.py +++ b/certbot/certbot/compat/misc.py @@ -12,7 +12,7 @@ import sys from certbot import errors from certbot.compat import os -from acme.magic_typing import Tuple +from acme.magic_typing import Tuple, Optional try: from win32com.shell import shell as shellwin32 @@ -116,8 +116,8 @@ def underscores_for_unsupported_characters_in_path(path): return drive + tail.replace(':', '_') -def execute_command(cmd_name, shell_cmd): - # type: (str, str) -> Tuple[str, str] +def execute_command(cmd_name, shell_cmd, env=None): + # type: (str, str, Optional[dict]) -> Tuple[str, str] """ Run a command: - on Linux command will be run by the standard shell selected with Popen(shell=True) @@ -125,6 +125,7 @@ def execute_command(cmd_name, shell_cmd): :param str cmd_name: the user facing name of the hook being run :param str shell_cmd: shell command to execute + :param dict env: environ to pass into Popen :returns: `tuple` (`str` stderr, `str` stdout) """ @@ -132,11 +133,12 @@ def execute_command(cmd_name, shell_cmd): if POSIX_MODE: cmd = subprocess.Popen(shell_cmd, shell=True, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, universal_newlines=True) + stderr=subprocess.PIPE, universal_newlines=True, + env=env) else: line = ['powershell.exe', '-Command', shell_cmd] cmd = subprocess.Popen(line, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, env=env) # universal_newlines causes Popen.communicate() # to return str objects instead of bytes in Python 3 diff --git a/certbot/certbot/display/ops.py b/certbot/certbot/display/ops.py index 2c3503eab..1c4ee47d4 100644 --- a/certbot/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -241,11 +241,8 @@ def success_installation(domains): """ z_util(interfaces.IDisplay).notification( - "Congratulations! You have successfully enabled {0}{1}{1}" - "You should test your configuration at:{1}{2}".format( - _gen_https_names(domains), - os.linesep, - os.linesep.join(_gen_ssl_lab_urls(domains))), + "Congratulations! You have successfully enabled {0}".format( + _gen_https_names(domains)), pause=False) @@ -258,13 +255,12 @@ def success_renewal(domains): z_util(interfaces.IDisplay).notification( "Your existing certificate has been successfully renewed, and the " "new certificate has been installed.{1}{1}" - "The new certificate covers the following domains: {0}{1}{1}" - "You should test your configuration at:{1}{2}".format( + "The new certificate covers the following domains: {0}".format( _gen_https_names(domains), - os.linesep, - os.linesep.join(_gen_ssl_lab_urls(domains))), + os.linesep), pause=False) + def success_revocation(cert_path): """Display a box confirming a certificate has been revoked. diff --git a/certbot/certbot/ocsp.py b/certbot/certbot/ocsp.py index 51ada012a..75ce9e2ff 100644 --- a/certbot/certbot/ocsp.py +++ b/certbot/certbot/ocsp.py @@ -51,7 +51,8 @@ class RevocationChecker(object): # New versions of openssl want -header var=val, old ones want -header var val test_host_format = Popen(["openssl", "ocsp", "-header", "var", "val"], - stdout=PIPE, stderr=PIPE, universal_newlines=True) + stdout=PIPE, stderr=PIPE, universal_newlines=True, + env=util.env_no_snap_for_external_calls()) _out, err = test_host_format.communicate() if "Missing =" in err: self.host_args = lambda host: ["Host=" + host] diff --git a/certbot/certbot/util.py b/certbot/certbot/util.py index e69b11543..33e2a3b32 100644 --- a/certbot/certbot/util.py +++ b/certbot/certbot/util.py @@ -61,6 +61,31 @@ _INITIAL_PID = os.getpid() _LOCKS = OrderedDict() # type: OrderedDict[str, lock.LockFile] +def env_no_snap_for_external_calls(): + """ + When Certbot is run inside a Snap, certain environment variables + are modified. But Certbot sometimes calls out to external programs, + since it uses classic confinement. When we do that, we must modify + the env to remove our modifications so it will use the system's + libraries, since they may be incompatible with the versions of + libraries included in the Snap. For example, apachectl, Nginx, and + anything run from inside a hook should call this function and pass + the results into the ``env`` argument of ``subprocess.Popen``. + + :returns: A modified copy of os.environ ready to pass to Popen + :rtype: dict + + """ + env = os.environ.copy() + # Avoid accidentally modifying env + if 'SNAP' not in env or 'CERTBOT_SNAPPED' not in env: + return env + for path_name in ('PATH', 'LD_LIBRARY_PATH'): + if path_name in env: + env[path_name] = ':'.join(x for x in env[path_name].split(':') if env['SNAP'] not in x) + return env + + def run_script(params, log=logger.error): """Run the script with the given params. @@ -72,7 +97,8 @@ def run_script(params, log=logger.error): proc = subprocess.Popen(params, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, + env=env_no_snap_for_external_calls()) except (OSError, ValueError): msg = "Unable to run the command: %s" % " ".join(params) @@ -377,12 +403,14 @@ def get_python_os_info(pretty=False): ["/usr/bin/sw_vers", "-productVersion"], stdout=subprocess.PIPE, universal_newlines=True, + env=env_no_snap_for_external_calls(), ) except OSError: proc = subprocess.Popen( ["sw_vers", "-productVersion"], stdout=subprocess.PIPE, universal_newlines=True, + env=env_no_snap_for_external_calls(), ) os_ver = proc.communicate()[0].rstrip('\n') elif os_type.startswith('freebsd'): diff --git a/certbot/docs/ciphers.rst b/certbot/docs/ciphers.rst index e3d9cf54e..1ddd17970 100644 --- a/certbot/docs/ciphers.rst +++ b/certbot/docs/ciphers.rst @@ -110,8 +110,7 @@ to most-backwards compatible). The client will follow the Mozilla defaults for the *Intermediate* configuration by default, at least with regards to ciphersuites and TLS versions. Mozilla's web site describes which client software will be compatible with each configuration. You can also use -the Qualys SSL Labs site, which Certbot will suggest -when installing a certificate, to test your server and see whether it +the Qualys SSL Labs site to test your server and see whether it will be compatible with particular software versions. The Let's Encrypt project expects to follow the Mozilla recommendations diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index b8fd02461..06a641216 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -122,7 +122,7 @@ class PreHookTest(HookTest): def _test_nonrenew_common(self): mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with("pre-hook", self.config.pre_hook) + mock_execute.assert_called_once_with("pre-hook", self.config.pre_hook, env=mock.ANY) self._test_no_executions_common() def test_no_hooks(self): @@ -138,21 +138,21 @@ class PreHookTest(HookTest): def test_renew_disabled_dir_hooks(self): self.config.directory_hooks = False mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with("pre-hook", self.config.pre_hook) + mock_execute.assert_called_once_with("pre-hook", self.config.pre_hook, env=mock.ANY) self._test_no_executions_common() def test_renew_no_overlap(self): self.config.verb = "renew" mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_any_call("pre-hook", self.dir_hook) - mock_execute.assert_called_with("pre-hook", self.config.pre_hook) + mock_execute.assert_any_call("pre-hook", self.dir_hook, env=mock.ANY) + mock_execute.assert_called_with("pre-hook", self.config.pre_hook, env=mock.ANY) self._test_no_executions_common() def test_renew_with_overlap(self): self.config.pre_hook = self.dir_hook self.config.verb = "renew" mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with("pre-hook", self.dir_hook) + mock_execute.assert_called_once_with("pre-hook", self.dir_hook, env=mock.ANY) self._test_no_executions_common() def _test_no_executions_common(self): @@ -194,7 +194,7 @@ class PostHookTest(HookTest): for verb in ("certonly", "run",): self.config.verb = verb mock_execute = self._call_with_mock_execute(self.config) - mock_execute.assert_called_once_with("post-hook", self.config.post_hook) + mock_execute.assert_called_once_with("post-hook", self.config.post_hook, env=mock.ANY) self.assertFalse(self._get_eventually()) def test_cert_only_and_run_without_hook(self): @@ -283,7 +283,7 @@ class RunSavedPostHooksTest(HookTest): def test_single(self): self.eventually = ["foo"] mock_execute = self._call_with_mock_execute_and_eventually() - mock_execute.assert_called_once_with("post-hook", self.eventually[0]) + mock_execute.assert_called_once_with("post-hook", self.eventually[0], env=mock.ANY) class RenewalHookTest(HookTest): @@ -361,7 +361,7 @@ class DeployHookTest(RenewalHookTest): self.config.deploy_hook = "foo" mock_execute = self._call_with_mock_execute( self.config, domains, lineage) - mock_execute.assert_called_once_with("deploy-hook", self.config.deploy_hook) + mock_execute.assert_called_once_with("deploy-hook", self.config.deploy_hook, env=mock.ANY) class RenewHookTest(RenewalHookTest): @@ -385,7 +385,7 @@ class RenewHookTest(RenewalHookTest): self.config.directory_hooks = False mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - mock_execute.assert_called_once_with("deploy-hook", self.config.renew_hook) + mock_execute.assert_called_once_with("deploy-hook", self.config.renew_hook, env=mock.ANY) @mock.patch("certbot._internal.hooks.logger") def test_dry_run(self, mock_logger): @@ -409,13 +409,13 @@ class RenewHookTest(RenewalHookTest): self.config.renew_hook = self.dir_hook mock_execute = self._call_with_mock_execute( self.config, ["example.net", "example.org"], "/foo/bar") - mock_execute.assert_called_once_with("deploy-hook", self.dir_hook) + mock_execute.assert_called_once_with("deploy-hook", self.dir_hook, env=mock.ANY) def test_no_overlap(self): mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") - mock_execute.assert_any_call("deploy-hook", self.dir_hook) - mock_execute.assert_called_with("deploy-hook", self.config.renew_hook) + mock_execute.assert_any_call("deploy-hook", self.dir_hook, env=mock.ANY) + mock_execute.assert_called_with("deploy-hook", self.config.renew_hook, env=mock.ANY) class ListHooksTest(test_util.TempDirTestCase): diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 6dd0f964c..d28d7df6f 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -17,6 +17,40 @@ from certbot.compat import os import certbot.tests.util as test_util +class EnvNoSnapForExternalCallsTest(unittest.TestCase): + """Tests for certbot.util.env_no_snap_for_external_calls.""" + @classmethod + def _call(cls): + from certbot.util import env_no_snap_for_external_calls + return env_no_snap_for_external_calls() + + def test_removed(self): + original_path = os.environ['PATH'] + env_copy_dict = os.environ.copy() + env_copy_dict['PATH'] = 'RANDOM_NONSENSE_GARBAGE/blah/blah:' + original_path + env_copy_dict['SNAP'] = 'RANDOM_NONSENSE_GARBAGE' + env_copy_dict['CERTBOT_SNAPPED'] = 'True' + with mock.patch('certbot.compat.os.environ.copy', return_value=env_copy_dict): + self.assertEqual(self._call()['PATH'], original_path) + + def test_noop(self): + env_copy_dict_unmodified = os.environ.copy() + env_copy_dict_unmodified['PATH'] = 'RANDOM_NONSENSE_GARBAGE/blah/blah:' \ + + env_copy_dict_unmodified['PATH'] + env_copy_dict = env_copy_dict_unmodified.copy() + with mock.patch('certbot.compat.os.environ.copy', return_value=env_copy_dict): + # contains neither necessary key + env_copy_dict.pop('SNAP', None) + env_copy_dict.pop('CERTBOT_SNAPPED', None) + self.assertEqual(self._call()['PATH'], env_copy_dict_unmodified['PATH']) + # contains only one necessary key + env_copy_dict['SNAP'] = 'RANDOM_NONSENSE_GARBAGE' + self.assertEqual(self._call()['PATH'], env_copy_dict_unmodified['PATH']) + del env_copy_dict['SNAP'] + env_copy_dict['CERTBOT_SNAPPED'] = 'True' + self.assertEqual(self._call()['PATH'], env_copy_dict_unmodified['PATH']) + + class RunScriptTest(unittest.TestCase): """Tests for certbot.util.run_script.""" @classmethod diff --git a/snap/local/README.md b/snap/local/README.md index ea509b229..a631decb8 100644 --- a/snap/local/README.md +++ b/snap/local/README.md @@ -66,23 +66,22 @@ These steps need to be done once to set up your VM and do not need to be run aga 5. Add your current user to the lxd group and update your shell to have the new assignment by running `sudo usermod -a -G lxd ${USER} && newgrp lxd`. 6. Install snapcraft with `sudo snap install --classic snapcraft`. 7. `cd ~` (or any other directory where you want our source files to be) - 8. Run `git clone git://github.com/certbot/certbot -b snap-plugin` + 8. Run `git clone git://github.com/certbot/certbot` 9. `cd certbot` ### Build the Snaps These are the steps to build and install the snaps. If you have run these steps before, you may want to run the commands in the section below to clean things up before building the snap again. - 1. Run `tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt | grep -v python-augeas > snap-constraints.txt` (this is a workaround for https://github.com/certbot/certbot/issues/7957). - 2. Run `snapcraft --use-lxd`. - 3. Install the generated snap with `sudo snap install --dangerous --classic certbot_*_amd64.snap`. You can transfer the snap to a different machine to run it there instead if you prefer. - 4. Run `tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt > certbot-dns-dnsimple/snap-constraints.txt`. - 5. `cd certbot-dns-dnsimple` - 6. `snapcraft --use-lxd` - 7. Run `sudo snap set certbot trust-plugin-with-root=ok`. - 8. Install the generated snap with `sudo snap install --dangerous certbot-dns-dnsimple_*_amd64.snap`. Again, you can transfer the snap to a different machine to run it there instead if you prefer. - 9. Connect the plugin with `sudo snap connect certbot:plugin certbot-dns-dnsimple`. - 10. Now you can run Certbot as normal. For example, `certbot plugins` should display the DNSimple plugin as installed. + 1. Run `snapcraft --use-lxd`. + 2. Install the generated snap with `sudo snap install --dangerous --classic certbot_*_amd64.snap`. You can transfer the snap to a different machine to run it there instead if you prefer. + 3. Run `tools/merge_requirements.py tools/dev_constraints.txt <(tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt) > certbot-dns-dnsimple/snap-constraints.txt` (this is a workaround for https://github.com/certbot/certbot/issues/8100). + 4. `cd certbot-dns-dnsimple` + 5. `snapcraft --use-lxd` + 6. Run `sudo snap set certbot trust-plugin-with-root=ok`. + 7. Install the generated snap with `sudo snap install --dangerous certbot-dns-dnsimple_*_amd64.snap`. Again, you can transfer the snap to a different machine to run it there instead if you prefer. + 8. Connect the plugin with `sudo snap connect certbot:plugin certbot-dns-dnsimple`. + 9. Now you can run Certbot as normal. For example, `certbot plugins` should display the DNSimple plugin as installed. ### Reset the Environment diff --git a/snap/local/build.sh b/snap/local/build.sh index 2a0f64b92..c296246e2 100755 --- a/snap/local/build.sh +++ b/snap/local/build.sh @@ -21,9 +21,6 @@ source "${DIR}/common.sh" RegisterQemuHandlers ResolveArch "${SNAP_ARCH}" -tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt \ - | grep -v python-augeas > snap-constraints.txt - pushd "${DIR}/packages" "${CERTBOT_DIR}/tools/simple_http_server.py" 8080 >/dev/null 2>&1 & HTTP_SERVER_PID="$!" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ea81e2b61..f3efea88c 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -25,6 +25,7 @@ apps: PATH: "$SNAP/bin:$SNAP/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" AUGEAS_LENS_LIB: "$SNAP/usr/share/augeas/lenses/dist" LD_LIBRARY_PATH: "$SNAP/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH" + CERTBOT_SNAPPED: "True" renew: command: certbot.wrapper -q renew daemon: oneshot @@ -32,6 +33,7 @@ apps: PATH: "$SNAP/bin:$SNAP/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games" AUGEAS_LENS_LIB: $SNAP/usr/share/augeas/lenses/dist LD_LIBRARY_PATH: "$SNAP/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH" + CERTBOT_SNAPPED: "True" # Run approximately twice a day with randomization timer: 00:00~24:00/2 @@ -49,8 +51,13 @@ parts: - ./certbot-nginx stage: - -usr/lib/python3.8/sitecustomize.py # maybe unnecessary - # Prefer cffi - - -lib/python3.8/site-packages/augeas.py + # Old versions of this file used to unstage + # lib/python3.8/site-packages/augeas.py to avoid conflicts between + # python-augeas 0.5.0 which was pinned in snap-constraints.txt and + # Robie's python-augeas fork which creates an auto-generated cffi file at + # the same path. Since we've combined things in one part and removed the + # python-augeas pinning, unstaging this file had a different, unintended + # effect so we now stage the file to keep the auto-generated cffi file. stage-packages: - libaugeas0 # added to stage python: @@ -69,7 +76,9 @@ parts: build-packages: [libffi-dev, libssl-dev, git, libaugeas-dev, python3-dev] override-pull: | snapcraftctl pull - snapcraftctl set-version `cd $SNAPCRAFT_PART_SRC/certbot && git describe|sed s/^v//` + cd $SNAPCRAFT_PART_SRC + python3 tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt | grep -v python-augeas > snap-constraints.txt + snapcraftctl set-version `git describe|sed s/^v//` wrappers: plugin: dump diff --git a/tests/letstest/requirements.txt b/tests/letstest/requirements.txt index 840e3e5d5..d30c507cf 100644 --- a/tests/letstest/requirements.txt +++ b/tests/letstest/requirements.txt @@ -1,19 +1,23 @@ +awscli==1.18.88 bcrypt==3.1.7 -boto3==1.12.7 -botocore==1.15.7 +boto3==1.14.11 +botocore==1.17.11 cffi==1.14.0 +colorama==0.4.3 cryptography==2.8 docutils==0.15.2 enum34==1.1.9 -Fabric==2.5.0 +fabric==2.5.0 +invoke==1.4.1 ipaddress==1.0.23 -Invoke==1.4.1 jmespath==0.9.5 paramiko==2.7.1 +pyasn1==0.4.8 pycparser==2.19 PyNaCl==1.3.0 python-dateutil==2.8.1 PyYAML==5.3 +rsa==3.4.2 s3transfer==0.3.3 six==1.14.0 urllib3==1.25.8 diff --git a/tools/generate_dnsplugins_snapcraft.sh b/tools/generate_dnsplugins_snapcraft.sh new file mode 100755 index 000000000..2e367f89b --- /dev/null +++ b/tools/generate_dnsplugins_snapcraft.sh @@ -0,0 +1,39 @@ +#!/bin/bash +# Generate the snapcraft.yaml file for all DNS plugins +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +CERTBOT_DIR="$(dirname "${DIR}")" + +for PLUGIN_PATH in "${CERTBOT_DIR}"/certbot-dns-*; do + PLUGIN=$(basename "${PLUGIN_PATH}") + DESCRIPTION=$(grep description "${PLUGIN_PATH}/setup.py" | sed -E 's|\s+description="(.*)",|\1|g') + mkdir -p "${PLUGIN_PATH}/snap" + cat < "${PLUGIN_PATH}/snap/snapcraft.yaml" +name: ${PLUGIN} +summary: ${DESCRIPTION} +description: ${DESCRIPTION} +confinement: strict +grade: devel +base: core20 +adopt-info: ${PLUGIN} + +parts: + ${PLUGIN}: + plugin: python + source: . + constraints: [\$SNAPCRAFT_PART_SRC/snap-constraints.txt] + override-pull: | + snapcraftctl pull + snapcraftctl set-version \`grep ^version \$SNAPCRAFT_PART_SRC/setup.py | cut -f2 -d= | tr -d "'[:space:]"\` + build-environment: + - EXCLUDE_CERTBOT_DEPS: "True" + +slots: + certbot: + interface: content + content: certbot-1 + read: + - \$SNAP/lib/python3.8/site-packages +EOF +done