mirror of
https://github.com/certbot/certbot.git
synced 2026-06-07 07:42:08 -04:00
Merge branch 'master' into full-azure-pipelines
# Conflicts: # .travis.yml
This commit is contained in:
commit
7d67a92bb5
50 changed files with 570 additions and 255 deletions
|
|
@ -63,8 +63,6 @@ jobs:
|
|||
matrix:
|
||||
amd64:
|
||||
ARCH: amd64
|
||||
i386:
|
||||
ARCH: i386
|
||||
arm64:
|
||||
ARCH: arm64
|
||||
armhf:
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ Authors
|
|||
* [Felix Schwarz](https://github.com/FelixSchwarz)
|
||||
* [Felix Yan](https://github.com/felixonmars)
|
||||
* [Filip Ochnik](https://github.com/filipochnik)
|
||||
* [Florian Klink](https://github.com/flokli)
|
||||
* [Francois Marier](https://github.com/fmarier)
|
||||
* [Frank](https://github.com/Frankkkkk)
|
||||
* [Frederic BLANC](https://github.com/fblanc)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -27,7 +27,7 @@ install_requires = [
|
|||
'six>=1.9.0', # needed for python_2_unicode_compatible
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -19,7 +19,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -26,7 +26,7 @@ install_requires = [
|
|||
# However environment markers are supported only with setuptools >= 36.2.
|
||||
# So this dependency is not added for old Linux distributions with old setuptools,
|
||||
# in order to allow these systems to build certbot from sources.
|
||||
if StrictVersion(setuptools_version) >= StrictVersion('36.2'):
|
||||
if LooseVersion(setuptools_version) >= LooseVersion('36.2'):
|
||||
install_requires.append("pywin32>=224 ; sys_platform == 'win32'")
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -15,7 +15,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ Credentials
|
|||
-----------
|
||||
|
||||
Use of this plugin requires a configuration file containing Cloudflare API
|
||||
credentials, obtained from your Cloudflare
|
||||
`account page <https://dash.cloudflare.com/profile/api-tokens>`_.
|
||||
credentials, obtained from your
|
||||
`Cloudflare dashboard <https://dash.cloudflare.com/?to=/:account/profile/api-tokens>`_.
|
||||
|
||||
Previously, Cloudflare's "Global API Key" was used for authentication, however
|
||||
this key can access the entire Cloudflare API for all domains in your account,
|
||||
|
|
@ -31,11 +31,8 @@ meaning it could cause a lot of damage if leaked.
|
|||
Cloudflare's newer API Tokens can be restricted to specific domains and
|
||||
operations, and are therefore now the recommended authentication option.
|
||||
|
||||
However, due to some shortcomings in Cloudflare's implementation of Tokens,
|
||||
Tokens created for Certbot currently require ``Zone:Zone:Read`` and ``Zone:DNS:Edit``
|
||||
permissions for **all** zones in your account. While this is not ideal, your Token
|
||||
will still have fewer permission than the Global key, so it's still worth doing.
|
||||
Hopefully Cloudflare will improve this in the future.
|
||||
The Token needed by Certbot requires ``Zone:DNS:Edit`` permissions for only the
|
||||
zones you need certificates for.
|
||||
|
||||
Using Cloudflare Tokens also requires at least version 2.3.1 of the ``cloudflare``
|
||||
python module. If the version that automatically installed with this plugin is
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from certbot.plugins import dns_common
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens'
|
||||
ACCOUNT_URL = 'https://dash.cloudflare.com/?to=/:account/profile/api-tokens'
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
|
|
@ -118,7 +118,7 @@ class _CloudflareClient(object):
|
|||
code = int(e)
|
||||
hint = None
|
||||
|
||||
if code == 9109:
|
||||
if code == 1009:
|
||||
hint = 'Does your API token have "Zone:DNS:Edit" permissions?'
|
||||
|
||||
logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e)
|
||||
|
|
@ -210,11 +210,22 @@ class _CloudflareClient(object):
|
|||
logger.debug('Found zone_id of %s for %s using name %s', zone_id, domain, zone_name)
|
||||
return zone_id
|
||||
|
||||
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. '
|
||||
'Please confirm that the domain name has been entered correctly '
|
||||
'and is already associated with the supplied Cloudflare account.{2}'
|
||||
.format(domain, zone_name_guesses, ' The error from Cloudflare was:'
|
||||
' {0} {1}'.format(code, msg) if code is not None else ''))
|
||||
if msg is not None:
|
||||
if 'com.cloudflare.api.account.zone.list' in msg:
|
||||
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: '
|
||||
'{1}. Please confirm that the domain name has been '
|
||||
'entered correctly and your Cloudflare Token has access '
|
||||
'to the domain.'.format(domain, zone_name_guesses))
|
||||
else:
|
||||
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: '
|
||||
'{1}. The error from Cloudflare was: {2} {3}.'
|
||||
.format(domain, zone_name_guesses, code, msg))
|
||||
else:
|
||||
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: '
|
||||
'{1}. Please confirm that the domain name has been '
|
||||
'entered correctly and is already associated with the '
|
||||
'supplied Cloudflare account.'
|
||||
.format(domain, zone_name_guesses))
|
||||
|
||||
def _find_txt_record_id(self, zone_id, record_name, record_content):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class CloudflareClientTest(unittest.TestCase):
|
|||
def test_add_txt_record_error(self):
|
||||
self.cf.zones.get.return_value = [{'id': self.zone_id}]
|
||||
|
||||
self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '')
|
||||
self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(1009, '', '')
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
|
|
@ -175,6 +175,12 @@ class CloudflareClientTest(unittest.TestCase):
|
|||
self.cloudflare_client.add_txt_record,
|
||||
DOMAIN, self.record_name, self.record_content, self.record_ttl)
|
||||
|
||||
self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(0, 'com.cloudflare.api.account.zone.list', '')
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
self.cloudflare_client.add_txt_record,
|
||||
DOMAIN, self.record_name, self.record_content, self.record_ttl)
|
||||
|
||||
def test_del_txt_record(self):
|
||||
self.cf.zones.get.return_value = [{'id': self.zone_id}]
|
||||
self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -19,7 +19,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
|
@ -12,13 +12,20 @@ version = '1.6.0.dev0'
|
|||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
if not os.environ.get('EXCLUDE_CERTBOT_DEPS'):
|
||||
install_requires.extend([
|
||||
'acme>=0.31.0',
|
||||
'certbot>=1.1.0',
|
||||
])
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
raise RuntimeError('Unset EXCLUDE_CERTBOT_DEPS when building wheels '
|
||||
'to include certbot dependencies.')
|
||||
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
26
certbot-dns-dnsimple/snap/snapcraft.yaml
Normal file
26
certbot-dns-dnsimple/snap/snapcraft.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
name: certbot-dns-dnsimple
|
||||
summary: DNSimple DNS Authenticator plugin for Certbot
|
||||
description: TBC
|
||||
confinement: strict
|
||||
grade: devel
|
||||
base: core18
|
||||
adopt-info: certbot-dns-dnsimple
|
||||
|
||||
parts:
|
||||
certbot-dns-dnsimple:
|
||||
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:]"`
|
||||
build-environment:
|
||||
- EXCLUDE_CERTBOT_DEPS: "True"
|
||||
|
||||
slots:
|
||||
certbot:
|
||||
interface: content
|
||||
content: certbot-1
|
||||
read:
|
||||
- $SNAP/lib/python3.6/site-packages
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -17,7 +17,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -21,7 +21,7 @@ install_requires = [
|
|||
'httplib2'
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -17,7 +17,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -18,7 +18,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -17,7 +17,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import sys
|
||||
|
||||
from setuptools import __version__ as setuptools_version
|
||||
|
|
@ -19,7 +19,7 @@ install_requires = [
|
|||
'zope.interface',
|
||||
]
|
||||
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append('mock ; python_version < "3.3"')
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||
|
||||
### Added
|
||||
|
||||
* Certbot snaps are now available for the i386, arm64, and armhf architectures.
|
||||
* Certbot snaps are now available for the arm64 and armhf architectures.
|
||||
* Add minimal code to run Nginx plugin on NetBSD.
|
||||
* Make Certbot snap find externally snapped plugins
|
||||
* Function `certbot.compat.filesystem.umask` is a drop-in replacement for `os.umask`
|
||||
|
|
@ -23,7 +23,10 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
|||
|
||||
### Fixed
|
||||
|
||||
*
|
||||
* Cloudflare API Tokens may now be restricted to individual zones.
|
||||
* 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.
|
||||
|
||||
More details about these changes can be found on our GitHub repo.
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import zope.component
|
|||
|
||||
from acme import fields as acme_fields
|
||||
from acme import messages
|
||||
from acme.client import ClientBase # pylint: disable=unused-import
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
|
|
@ -39,6 +40,8 @@ class Account(object):
|
|||
|
||||
:ivar datetime.datetime creation_dt: Creation date and time (UTC).
|
||||
:ivar str creation_host: FQDN of host, where account has been created.
|
||||
:ivar str register_to_eff: If not None, Certbot will register the provided
|
||||
email during the account registration.
|
||||
|
||||
.. note:: ``creation_dt`` and ``creation_host`` are useful in
|
||||
cross-machine migration scenarios.
|
||||
|
|
@ -46,15 +49,16 @@ class Account(object):
|
|||
"""
|
||||
creation_dt = acme_fields.RFC3339Field("creation_dt")
|
||||
creation_host = jose.Field("creation_host")
|
||||
register_to_eff = jose.Field("register_to_eff", omitempty=True)
|
||||
|
||||
def __init__(self, regr, key, meta=None):
|
||||
self.key = key
|
||||
self.regr = regr
|
||||
self.meta = self.Meta(
|
||||
# pyrfc3339 drops microseconds, make sure __eq__ is sane
|
||||
creation_dt=datetime.datetime.now(
|
||||
tz=pytz.UTC).replace(microsecond=0),
|
||||
creation_host=socket.getfqdn()) if meta is None else meta
|
||||
creation_dt=datetime.datetime.now(tz=pytz.UTC).replace(microsecond=0),
|
||||
creation_host=socket.getfqdn(),
|
||||
register_to_eff=None) if meta is None else meta
|
||||
|
||||
# try MD5, else use MD5 in non-security mode (e.g. for FIPS systems / RHEL)
|
||||
try:
|
||||
|
|
@ -242,15 +246,47 @@ class AccountFileStorage(interfaces.AccountStorage):
|
|||
return self._load_for_server_path(account_id, self.config.server_path)
|
||||
|
||||
def save(self, account, client):
|
||||
self._save(account, client, regr_only=False)
|
||||
# type: (Account, ClientBase) -> None
|
||||
"""Create a new account.
|
||||
|
||||
def save_regr(self, account, acme):
|
||||
"""Save the registration resource.
|
||||
|
||||
:param Account account: account whose regr should be saved
|
||||
:param Account account: account to create
|
||||
:param ClientBase client: ACME client associated to the account
|
||||
|
||||
"""
|
||||
self._save(account, acme, regr_only=True)
|
||||
try:
|
||||
dir_path = self._prepare(account)
|
||||
self._create(account, dir_path)
|
||||
self._update_meta(account, dir_path)
|
||||
self._update_regr(account, client, dir_path)
|
||||
except IOError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
def update_regr(self, account, client):
|
||||
# type: (Account, ClientBase) -> None
|
||||
"""Update the registration resource.
|
||||
|
||||
:param Account account: account to update
|
||||
:param ClientBase client: ACME client associated to the account
|
||||
|
||||
"""
|
||||
try:
|
||||
dir_path = self._prepare(account)
|
||||
self._update_regr(account, client, dir_path)
|
||||
except IOError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
def update_meta(self, account):
|
||||
# type: (Account) -> None
|
||||
"""Update the meta resource.
|
||||
|
||||
:param Account account: account to update
|
||||
|
||||
"""
|
||||
try:
|
||||
dir_path = self._prepare(account)
|
||||
self._update_meta(account, dir_path)
|
||||
except IOError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
|
||||
def delete(self, account_id):
|
||||
"""Delete registration info from disk
|
||||
|
|
@ -318,32 +354,36 @@ class AccountFileStorage(interfaces.AccountStorage):
|
|||
|
||||
return dir_path
|
||||
|
||||
def _save(self, account, acme, regr_only):
|
||||
def _prepare(self, account):
|
||||
# type: (Account) -> str
|
||||
account_dir_path = self._account_dir_path(account.id)
|
||||
util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions)
|
||||
try:
|
||||
with open(self._regr_path(account_dir_path), "w") as regr_file:
|
||||
regr = account.regr
|
||||
# If we have a value for new-authz, save it for forwards
|
||||
# compatibility with older versions of Certbot. If we don't
|
||||
# have a value for new-authz, this is an ACMEv2 directory where
|
||||
# an older version of Certbot won't work anyway.
|
||||
if hasattr(acme.directory, "new-authz"):
|
||||
regr = RegistrationResourceWithNewAuthzrURI(
|
||||
new_authzr_uri=acme.directory.new_authz,
|
||||
body={},
|
||||
uri=regr.uri)
|
||||
else:
|
||||
regr = messages.RegistrationResource(
|
||||
body={},
|
||||
uri=regr.uri)
|
||||
regr_file.write(regr.json_dumps())
|
||||
if not regr_only:
|
||||
with util.safe_open(self._key_path(account_dir_path),
|
||||
"w", chmod=0o400) as key_file:
|
||||
key_file.write(account.key.json_dumps())
|
||||
with open(self._metadata_path(
|
||||
account_dir_path), "w") as metadata_file:
|
||||
metadata_file.write(account.meta.json_dumps())
|
||||
except IOError as error:
|
||||
raise errors.AccountStorageError(error)
|
||||
return account_dir_path
|
||||
|
||||
def _create(self, account, dir_path):
|
||||
# type: (Account, str) -> None
|
||||
with util.safe_open(self._key_path(dir_path), "w", chmod=0o400) as key_file:
|
||||
key_file.write(account.key.json_dumps())
|
||||
|
||||
def _update_regr(self, account, acme, dir_path):
|
||||
# type: (Account, ClientBase, str) -> None
|
||||
with open(self._regr_path(dir_path), "w") as regr_file:
|
||||
regr = account.regr
|
||||
# If we have a value for new-authz, save it for forwards
|
||||
# compatibility with older versions of Certbot. If we don't
|
||||
# have a value for new-authz, this is an ACMEv2 directory where
|
||||
# an older version of Certbot won't work anyway.
|
||||
if hasattr(acme.directory, "new-authz"):
|
||||
regr = RegistrationResourceWithNewAuthzrURI(
|
||||
new_authzr_uri=acme.directory.new_authz,
|
||||
body={},
|
||||
uri=regr.uri)
|
||||
else:
|
||||
regr = messages.RegistrationResource(
|
||||
body={},
|
||||
uri=regr.uri)
|
||||
regr_file.write(regr.json_dumps())
|
||||
|
||||
def _update_meta(self, account, dir_path):
|
||||
with open(self._metadata_path(dir_path), "w") as metadata_file:
|
||||
metadata_file.write(account.meta.json_dumps())
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ def register(config, account_storage, tos_cb=None):
|
|||
account.report_new_account(config)
|
||||
account_storage.save(acc, acme)
|
||||
|
||||
eff.handle_subscription(config)
|
||||
eff.prepare_subscription(config, acc)
|
||||
|
||||
return acc, acme
|
||||
|
||||
|
|
@ -389,6 +389,7 @@ class Client(object):
|
|||
|
||||
authzr = self.auth_handler.handle_authorizations(orderr, best_effort)
|
||||
return orderr.update(authorizations=authzr)
|
||||
|
||||
def obtain_and_enroll_certificate(self, domains, certname):
|
||||
"""Obtain and enroll certificate.
|
||||
|
||||
|
|
|
|||
|
|
@ -4,32 +4,68 @@ import logging
|
|||
import requests
|
||||
import zope.component
|
||||
|
||||
from acme.magic_typing import Optional # pylint: disable=unused-import
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot._internal import constants
|
||||
from certbot._internal.account import Account # pylint: disable=unused-import
|
||||
from certbot._internal.account import AccountFileStorage
|
||||
from certbot.interfaces import IConfig # pylint: disable=unused-import
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_subscription(config):
|
||||
"""High level function to take care of EFF newsletter subscriptions.
|
||||
def prepare_subscription(config, acc):
|
||||
# type: (IConfig, Account) -> None
|
||||
"""High level function to store potential EFF newsletter subscriptions.
|
||||
|
||||
The user may be asked if they want to sign up for the newsletter if
|
||||
they have not already specified.
|
||||
they have not given their explicit approval or refusal using --eff-mail
|
||||
or --no-eff-mail flag.
|
||||
|
||||
:param .IConfig config: Client configuration.
|
||||
Decision about EFF subscription will be stored in the account metadata.
|
||||
|
||||
:param IConfig config: Client configuration.
|
||||
:param Account acc: Current client account.
|
||||
|
||||
"""
|
||||
if config.email is None:
|
||||
if config.eff_email:
|
||||
_report_failure("you didn't provide an e-mail address")
|
||||
if config.eff_email is False:
|
||||
return
|
||||
if config.eff_email is None:
|
||||
config.eff_email = _want_subscription()
|
||||
if config.eff_email:
|
||||
subscribe(config.email)
|
||||
if config.eff_email is True:
|
||||
if config.email is None:
|
||||
_report_failure("you didn't provide an e-mail address")
|
||||
else:
|
||||
acc.meta = acc.meta.update(register_to_eff=config.email)
|
||||
elif config.email and _want_subscription():
|
||||
acc.meta = acc.meta.update(register_to_eff=config.email)
|
||||
|
||||
if acc.meta.register_to_eff:
|
||||
storage = AccountFileStorage(config)
|
||||
storage.update_meta(acc)
|
||||
|
||||
|
||||
def handle_subscription(config, acc):
|
||||
# type: (IConfig, Account) -> None
|
||||
"""High level function to take care of EFF newsletter subscriptions.
|
||||
|
||||
Once subscription is handled, it will not be handled again.
|
||||
|
||||
:param IConfig config: Client configuration.
|
||||
:param Account acc: Current client account.
|
||||
|
||||
"""
|
||||
if config.dry_run:
|
||||
return
|
||||
if acc.meta.register_to_eff:
|
||||
subscribe(acc.meta.register_to_eff)
|
||||
|
||||
acc.meta = acc.meta.update(register_to_eff=None)
|
||||
storage = AccountFileStorage(config)
|
||||
storage.update_meta(acc)
|
||||
|
||||
|
||||
def _want_subscription():
|
||||
# type: () -> bool
|
||||
"""Does the user want to be subscribed to the EFF newsletter?
|
||||
|
||||
:returns: True if we should subscribe the user, otherwise, False
|
||||
|
|
@ -37,16 +73,17 @@ def _want_subscription():
|
|||
|
||||
"""
|
||||
prompt = (
|
||||
'Would you be willing to share your email address with the '
|
||||
"Electronic Frontier Foundation, a founding partner of the Let's "
|
||||
'Encrypt project and the non-profit organization that develops '
|
||||
"Certbot? We'd like to send you email about our work encrypting "
|
||||
'Would you be willing, once your first certificate is successfully issued, '
|
||||
'to share your email address with the Electronic Frontier Foundation, a '
|
||||
"founding partner of the Let's Encrypt project and the non-profit organization "
|
||||
"that develops Certbot? We'd like to send you email about our work encrypting "
|
||||
"the web, EFF news, campaigns, and ways to support digital freedom. ")
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
return display.yesno(prompt, default=False)
|
||||
|
||||
|
||||
def subscribe(email):
|
||||
# type: (str) -> None
|
||||
"""Subscribe the user to the EFF mailing list.
|
||||
|
||||
:param str email: the e-mail address to subscribe
|
||||
|
|
@ -56,11 +93,13 @@ def subscribe(email):
|
|||
data = {'data_type': 'json',
|
||||
'email': email,
|
||||
'form_id': 'eff_supporters_library_subscribe_form'}
|
||||
logger.info('Subscribe to the EFF mailing list (email: %s).', email)
|
||||
logger.debug('Sending POST request to %s:\n%s', url, data)
|
||||
_check_response(requests.post(url, data=data))
|
||||
|
||||
|
||||
def _check_response(response):
|
||||
# type: (requests.Response) -> None
|
||||
"""Check for errors in the server's response.
|
||||
|
||||
If an error occurred, it will be reported to the user.
|
||||
|
|
@ -81,6 +120,7 @@ def _check_response(response):
|
|||
|
||||
|
||||
def _report_failure(reason=None):
|
||||
# type: (Optional[str]) -> None
|
||||
"""Notify the user of failing to sign them up for the newsletter.
|
||||
|
||||
:param reason: a phrase describing what the problem was
|
||||
|
|
|
|||
|
|
@ -721,11 +721,12 @@ def update_account(config, unused_plugins):
|
|||
# the v2 uri. Since it's the same object on disk, put it back to the v1 uri
|
||||
# so that we can also continue to use the account object with acmev1.
|
||||
acc.regr = acc.regr.update(uri=prev_regr_uri)
|
||||
account_storage.save_regr(acc, cb_client.acme)
|
||||
eff.handle_subscription(config)
|
||||
account_storage.update_regr(acc, cb_client.acme)
|
||||
eff.prepare_subscription(config, acc)
|
||||
add_msg("Your e-mail address was updated to {0}.".format(config.email))
|
||||
return None
|
||||
|
||||
|
||||
def _install_cert(config, le_client, domains, lineage=None):
|
||||
"""Install a cert
|
||||
|
||||
|
|
@ -1116,6 +1117,7 @@ def run(config, plugins):
|
|||
display_ops.success_renewal(domains)
|
||||
|
||||
_suggest_donation_if_appropriate(config)
|
||||
eff.handle_subscription(config, le_client.account)
|
||||
return None
|
||||
|
||||
|
||||
|
|
@ -1189,6 +1191,7 @@ def renew_cert(config, plugins, lineage):
|
|||
notify("new certificate deployed with reload of {0} server; fullchain is {1}".format(
|
||||
config.installer, lineage.fullchain), pause=False)
|
||||
|
||||
|
||||
def certonly(config, plugins):
|
||||
"""Authenticate & obtain cert, but do not install it.
|
||||
|
||||
|
|
@ -1220,6 +1223,7 @@ def certonly(config, plugins):
|
|||
cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client)
|
||||
_report_new_cert(config, cert_path, fullchain_path)
|
||||
_suggest_donation_if_appropriate(config)
|
||||
eff.handle_subscription(config, le_client.account)
|
||||
return
|
||||
|
||||
domains, certname = _find_domains_or_certname(config, installer)
|
||||
|
|
@ -1237,6 +1241,8 @@ def certonly(config, plugins):
|
|||
key_path = lineage.key_path if lineage else None
|
||||
_report_new_cert(config, cert_path, fullchain_path, key_path)
|
||||
_suggest_donation_if_appropriate(config)
|
||||
eff.handle_subscription(config, le_client.account)
|
||||
|
||||
|
||||
def renew(config, unused_plugins):
|
||||
"""Renew previously-obtained certificates.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import codecs
|
||||
from distutils.version import StrictVersion
|
||||
from distutils.version import LooseVersion
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
|
@ -61,7 +61,7 @@ install_requires = [
|
|||
# So this dependency is not added for old Linux distributions with old setuptools,
|
||||
# in order to allow these systems to build certbot from sources.
|
||||
pywin32_req = 'pywin32>=227' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py
|
||||
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
|
||||
setuptools_known_environment_markers = (LooseVersion(setuptools_version) >= LooseVersion('36.2'))
|
||||
if setuptools_known_environment_markers:
|
||||
install_requires.append(pywin32_req + " ; sys_platform == 'win32'")
|
||||
elif 'bdist_wheel' in sys.argv[1:]:
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from certbot.compat import misc
|
|||
from certbot.compat import os
|
||||
import certbot.tests.util as test_util
|
||||
|
||||
|
||||
KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem"))
|
||||
|
||||
|
||||
|
|
@ -56,6 +57,32 @@ class AccountTest(unittest.TestCase):
|
|||
self.assertTrue(repr(self.acc).startswith(
|
||||
"<Account(i_am_a_regr, 7adac10320f585ddf118429c0c4af2cd, Meta("))
|
||||
|
||||
|
||||
class MetaTest(unittest.TestCase):
|
||||
"""Tests for certbot._internal.account.Meta."""
|
||||
def test_deserialize_partial(self):
|
||||
from certbot._internal.account import Account
|
||||
meta = Account.Meta.json_loads(
|
||||
'{'
|
||||
' "creation_dt": "2020-06-13T07:46:45Z",'
|
||||
' "creation_host": "hyperion.localdomain"'
|
||||
'}')
|
||||
self.assertIsNotNone(meta.creation_dt)
|
||||
self.assertIsNotNone(meta.creation_host)
|
||||
self.assertIsNone(meta.register_to_eff)
|
||||
|
||||
def test_deserialize_full(self):
|
||||
from certbot._internal.account import Account
|
||||
meta = Account.Meta.json_loads(
|
||||
'{'
|
||||
' "creation_dt": "2020-06-13T07:46:45Z",'
|
||||
' "creation_host": "hyperion.localdomain",'
|
||||
' "register_to_eff": "bar"'
|
||||
'}')
|
||||
self.assertIsNotNone(meta.creation_dt)
|
||||
self.assertIsNotNone(meta.creation_host)
|
||||
self.assertIsNotNone(meta.register_to_eff)
|
||||
|
||||
class ReportNewAccountTest(test_util.ConfigTestCase):
|
||||
"""Tests for certbot._internal.account.report_new_account."""
|
||||
|
||||
|
|
@ -138,15 +165,23 @@ class AccountFileStorageTest(test_util.ConfigTestCase):
|
|||
regr = json.load(f)
|
||||
self.assertTrue("new_authzr_uri" in regr)
|
||||
|
||||
def test_save_regr(self):
|
||||
self.storage.save_regr(self.acc, self.mock_client)
|
||||
def test_update_regr(self):
|
||||
self.storage.update_regr(self.acc, self.mock_client)
|
||||
account_path = os.path.join(self.config.accounts_dir, self.acc.id)
|
||||
self.assertTrue(os.path.exists(account_path))
|
||||
self.assertTrue(os.path.exists(os.path.join(
|
||||
account_path, "regr.json")))
|
||||
for file_name in "meta.json", "private_key.json":
|
||||
self.assertFalse(os.path.exists(
|
||||
os.path.join(account_path, file_name)))
|
||||
self.assertTrue(os.path.exists(os.path.join(account_path, "regr.json")))
|
||||
|
||||
self.assertFalse(os.path.exists(os.path.join(account_path, "meta.json")))
|
||||
self.assertFalse(os.path.exists(os.path.join(account_path, "private_key.json")))
|
||||
|
||||
def test_update_meta(self):
|
||||
self.storage.update_meta(self.acc)
|
||||
account_path = os.path.join(self.config.accounts_dir, self.acc.id)
|
||||
self.assertTrue(os.path.exists(account_path))
|
||||
self.assertTrue(os.path.exists(os.path.join(account_path, "meta.json")))
|
||||
|
||||
self.assertFalse(os.path.exists(os.path.join(account_path, "regr.json")))
|
||||
self.assertFalse(os.path.exists(os.path.join(account_path, "private_key.json")))
|
||||
|
||||
def test_find_all(self):
|
||||
self.storage.save(self.acc, self.mock_client)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from certbot.compat import filesystem
|
|||
from certbot.compat import os
|
||||
import certbot.tests.util as test_util
|
||||
|
||||
|
||||
KEY = test_util.load_vector("rsa512_key.pem")
|
||||
CSR_SAN = test_util.load_vector("csr-san_512.pem")
|
||||
|
||||
|
|
@ -91,15 +92,15 @@ class RegisterTest(test_util.ConfigTestCase):
|
|||
with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
|
||||
mock_client.new_account_and_tos().terms_of_service = "http://tos"
|
||||
mock_client().external_account_required.side_effect = self._false_mock
|
||||
with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle:
|
||||
with mock.patch("certbot._internal.eff.prepare_subscription") as mock_prepare:
|
||||
with mock.patch("certbot._internal.account.report_new_account"):
|
||||
mock_client().new_account_and_tos.side_effect = errors.Error
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
self.assertFalse(mock_handle.called)
|
||||
self.assertFalse(mock_prepare.called)
|
||||
|
||||
mock_client().new_account_and_tos.side_effect = None
|
||||
self._call()
|
||||
self.assertTrue(mock_handle.called)
|
||||
self.assertTrue(mock_prepare.called)
|
||||
|
||||
def test_it(self):
|
||||
with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
|
||||
|
|
@ -117,11 +118,11 @@ class RegisterTest(test_util.ConfigTestCase):
|
|||
mx_err = messages.Error.with_code('invalidContact', detail=msg)
|
||||
with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client:
|
||||
mock_client().external_account_required.side_effect = self._false_mock
|
||||
with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle:
|
||||
with mock.patch("certbot._internal.eff.prepare_subscription") as mock_prepare:
|
||||
mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()]
|
||||
self._call()
|
||||
self.assertEqual(mock_get_email.call_count, 1)
|
||||
self.assertTrue(mock_handle.called)
|
||||
self.assertTrue(mock_prepare.called)
|
||||
|
||||
@mock.patch("certbot._internal.account.report_new_account")
|
||||
def test_email_invalid_noninteractive(self, _rep):
|
||||
|
|
@ -141,7 +142,7 @@ class RegisterTest(test_util.ConfigTestCase):
|
|||
|
||||
@mock.patch("certbot._internal.client.logger")
|
||||
def test_without_email(self, mock_logger):
|
||||
with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle:
|
||||
with mock.patch("certbot._internal.eff.prepare_subscription") as mock_prepare:
|
||||
with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_clnt:
|
||||
mock_clnt().external_account_required.side_effect = self._false_mock
|
||||
with mock.patch("certbot._internal.account.report_new_account"):
|
||||
|
|
@ -150,7 +151,7 @@ class RegisterTest(test_util.ConfigTestCase):
|
|||
self.config.dry_run = False
|
||||
self._call()
|
||||
mock_logger.info.assert_called_once_with(mock.ANY)
|
||||
self.assertTrue(mock_handle.called)
|
||||
self.assertTrue(mock_prepare.called)
|
||||
|
||||
@mock.patch("certbot._internal.account.report_new_account")
|
||||
@mock.patch("certbot._internal.client.display_ops.get_email")
|
||||
|
|
|
|||
|
|
@ -1,82 +1,91 @@
|
|||
"""Tests for certbot._internal.eff."""
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
try:
|
||||
import mock
|
||||
except ImportError: # pragma: no cover
|
||||
except ImportError: # pragma: no cover
|
||||
from unittest import mock
|
||||
import josepy
|
||||
import pytz
|
||||
import requests
|
||||
|
||||
from acme import messages
|
||||
from certbot._internal import account
|
||||
from certbot._internal import constants
|
||||
import certbot.tests.util as test_util
|
||||
|
||||
|
||||
class HandleSubscriptionTest(test_util.ConfigTestCase):
|
||||
"""Tests for certbot._internal.eff.handle_subscription."""
|
||||
_KEY = josepy.JWKRSA.load(test_util.load_vector("rsa512_key.pem"))
|
||||
|
||||
|
||||
class SubscriptionTest(test_util.ConfigTestCase):
|
||||
"""Abstract class for subscription tests."""
|
||||
def setUp(self):
|
||||
super(HandleSubscriptionTest, self).setUp()
|
||||
self.email = 'certbot@example.org'
|
||||
self.config.email = self.email
|
||||
super(SubscriptionTest, self).setUp()
|
||||
self.account = account.Account(
|
||||
regr=messages.RegistrationResource(
|
||||
uri=None, body=messages.Registration(),
|
||||
new_authzr_uri='hi'),
|
||||
key=_KEY,
|
||||
meta=account.Account.Meta(
|
||||
creation_host='test.certbot.org',
|
||||
creation_dt=datetime.datetime(
|
||||
2015, 7, 4, 14, 4, 10, tzinfo=pytz.UTC)))
|
||||
self.config.email = 'certbot@example.org'
|
||||
self.config.eff_email = None
|
||||
|
||||
|
||||
class PrepareSubscriptionTest(SubscriptionTest):
|
||||
"""Tests for certbot._internal.eff.prepare_subscription."""
|
||||
def _call(self):
|
||||
from certbot._internal.eff import handle_subscription
|
||||
return handle_subscription(self.config)
|
||||
from certbot._internal.eff import prepare_subscription
|
||||
prepare_subscription(self.config, self.account)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_failure(self, mock_subscribe, mock_get_utility):
|
||||
def test_failure(self, mock_get_utility):
|
||||
self.config.email = None
|
||||
self.config.eff_email = True
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
self.assertFalse(mock_get_utility().yesno.called)
|
||||
actual = mock_get_utility().add_message.call_args[0][0]
|
||||
expected_part = "because you didn't provide an e-mail address"
|
||||
self.assertTrue(expected_part in actual)
|
||||
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_no_subscribe_with_no_prompt(self, mock_subscribe):
|
||||
self.config.eff_email = False
|
||||
with test_util.patch_get_utility() as mock_get_utility:
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
self._assert_no_get_utility_calls(mock_get_utility)
|
||||
self.assertIsNone(self.account.meta.register_to_eff)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility):
|
||||
def test_will_not_subscribe_with_no_prompt(self, mock_get_utility):
|
||||
self.config.eff_email = False
|
||||
self._call()
|
||||
self._assert_no_get_utility_calls(mock_get_utility)
|
||||
self.assertIsNone(self.account.meta.register_to_eff)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_will_subscribe_with_no_prompt(self, mock_get_utility):
|
||||
self.config.eff_email = True
|
||||
self._call()
|
||||
self._assert_subscribed(mock_subscribe)
|
||||
self._assert_no_get_utility_calls(mock_get_utility)
|
||||
self.assertEqual(self.account.meta.register_to_eff, self.config.email)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_will_not_subscribe_with_prompt(self, mock_get_utility):
|
||||
mock_get_utility().yesno.return_value = False
|
||||
self._call()
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
self._assert_correct_yesno_call(mock_get_utility)
|
||||
self.assertIsNone(self.account.meta.register_to_eff)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_will_subscribe_with_prompt(self, mock_get_utility):
|
||||
mock_get_utility().yesno.return_value = True
|
||||
self._call()
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
self._assert_correct_yesno_call(mock_get_utility)
|
||||
self.assertEqual(self.account.meta.register_to_eff, self.config.email)
|
||||
|
||||
def _assert_no_get_utility_calls(self, mock_get_utility):
|
||||
self.assertFalse(mock_get_utility().yesno.called)
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
|
||||
mock_get_utility().yesno.return_value = True
|
||||
self._call()
|
||||
self._assert_subscribed(mock_subscribe)
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
self._assert_correct_yesno_call(mock_get_utility)
|
||||
|
||||
def _assert_subscribed(self, mock_subscribe):
|
||||
self.assertTrue(mock_subscribe.called)
|
||||
self.assertEqual(mock_subscribe.call_args[0][0], self.email)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
|
||||
mock_get_utility().yesno.return_value = False
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
self._assert_correct_yesno_call(mock_get_utility)
|
||||
|
||||
def _assert_correct_yesno_call(self, mock_get_utility):
|
||||
self.assertTrue(mock_get_utility().yesno.called)
|
||||
call_args, call_kwargs = mock_get_utility().yesno.call_args
|
||||
|
|
@ -86,6 +95,25 @@ class HandleSubscriptionTest(test_util.ConfigTestCase):
|
|||
self.assertFalse(call_kwargs.get('default', True))
|
||||
|
||||
|
||||
class HandleSubscriptionTest(SubscriptionTest):
|
||||
"""Tests for certbot._internal.eff.handle_subscription."""
|
||||
def _call(self):
|
||||
from certbot._internal.eff import handle_subscription
|
||||
handle_subscription(self.config, self.account)
|
||||
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_no_subscribe(self, mock_subscribe):
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
|
||||
@mock.patch('certbot._internal.eff.subscribe')
|
||||
def test_subscribe(self, mock_subscribe):
|
||||
self.account.meta = self.account.meta.update(register_to_eff=self.config.email)
|
||||
self._call()
|
||||
self.assertTrue(mock_subscribe.called)
|
||||
self.assertEqual(mock_subscribe.call_args[0][0], self.config.email)
|
||||
|
||||
|
||||
class SubscribeTest(unittest.TestCase):
|
||||
"""Tests for certbot._internal.eff.subscribe."""
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ from certbot.compat import os
|
|||
from certbot.plugins import enhancements
|
||||
import certbot.tests.util as test_util
|
||||
|
||||
|
||||
CERT_PATH = test_util.vector_path('cert_512.pem')
|
||||
CERT = test_util.vector_path('cert_512.pem')
|
||||
CSR = test_util.vector_path('csr_512.der')
|
||||
|
|
@ -71,7 +72,9 @@ class RunTest(test_util.ConfigTestCase):
|
|||
mock.patch('certbot._internal.main._init_le_client'),
|
||||
mock.patch('certbot._internal.main._suggest_donation_if_appropriate'),
|
||||
mock.patch('certbot._internal.main._report_new_cert'),
|
||||
mock.patch('certbot._internal.main._find_cert')]
|
||||
mock.patch('certbot._internal.main._find_cert'),
|
||||
mock.patch('certbot._internal.eff.handle_subscription'),
|
||||
]
|
||||
|
||||
self.mock_auth = patches[0].start()
|
||||
self.mock_success_installation = patches[1].start()
|
||||
|
|
@ -80,6 +83,7 @@ class RunTest(test_util.ConfigTestCase):
|
|||
self.mock_suggest_donation = patches[4].start()
|
||||
self.mock_report_cert = patches[5].start()
|
||||
self.mock_find_cert = patches[6].start()
|
||||
self.mock_subscription = patches[7].start()
|
||||
for patch in patches:
|
||||
self.addCleanup(patch.stop)
|
||||
|
||||
|
|
@ -137,7 +141,8 @@ class CertonlyTest(unittest.TestCase):
|
|||
|
||||
with mock.patch('certbot._internal.main._init_le_client') as mock_init:
|
||||
with mock.patch('certbot._internal.main._suggest_donation_if_appropriate'):
|
||||
main.certonly(config, plugins)
|
||||
with mock.patch('certbot._internal.eff.handle_subscription'):
|
||||
main.certonly(config, plugins)
|
||||
|
||||
return mock_init() # returns the client
|
||||
|
||||
|
|
@ -589,13 +594,14 @@ class MainTest(test_util.ConfigTestCase):
|
|||
args.extend(['--standalone', '-d', 'eg.is'])
|
||||
self._cli_missing_flag(args, "register before running")
|
||||
|
||||
@mock.patch('certbot._internal.eff.handle_subscription')
|
||||
@mock.patch('certbot._internal.log.post_arg_parse_setup')
|
||||
@mock.patch('certbot._internal.main._report_new_cert')
|
||||
@mock.patch('certbot._internal.main.client.acme_client.Client')
|
||||
@mock.patch('certbot._internal.main._determine_account')
|
||||
@mock.patch('certbot._internal.main.client.Client.obtain_and_enroll_certificate')
|
||||
@mock.patch('certbot._internal.main._get_and_save_cert')
|
||||
def test_user_agent(self, gsc, _obt, det, _client, _, __):
|
||||
def test_user_agent(self, gsc, _obt, det, _client, _, __, ___):
|
||||
# Normally the client is totally mocked out, but here we need more
|
||||
# arguments to automate it...
|
||||
args = ["--standalone", "certonly", "-m", "none@none.com",
|
||||
|
|
@ -695,10 +701,11 @@ class MainTest(test_util.ConfigTestCase):
|
|||
self.assertTrue(mock_getcert.called)
|
||||
self.assertTrue(mock_inst.called)
|
||||
|
||||
@mock.patch('certbot._internal.eff.handle_subscription')
|
||||
@mock.patch('certbot._internal.log.post_arg_parse_setup')
|
||||
@mock.patch('certbot._internal.main._report_new_cert')
|
||||
@mock.patch('certbot.util.exe_exists')
|
||||
def test_configurator_selection(self, mock_exe_exists, _, __):
|
||||
def test_configurator_selection(self, mock_exe_exists, _, __, ___):
|
||||
mock_exe_exists.return_value = True
|
||||
real_plugins = disco.PluginsRegistry.find_all()
|
||||
args = ['--apache', '--authenticator', 'standalone']
|
||||
|
|
@ -929,9 +936,10 @@ class MainTest(test_util.ConfigTestCase):
|
|||
# Asserts we don't suggest donating after a successful dry run
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 1)
|
||||
|
||||
@mock.patch('certbot._internal.eff.handle_subscription')
|
||||
@mock.patch('certbot.crypto_util.notAfter')
|
||||
@test_util.patch_get_utility()
|
||||
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter):
|
||||
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter, mock_subscription):
|
||||
cert_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/foo.bar'))
|
||||
key_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/baz.qux'))
|
||||
date = '1970-01-01'
|
||||
|
|
@ -950,12 +958,15 @@ class MainTest(test_util.ConfigTestCase):
|
|||
self.assertTrue(key_path in cert_msg)
|
||||
self.assertTrue(
|
||||
'donate' in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(mock_subscription.called)
|
||||
|
||||
def test_certonly_new_request_failure(self):
|
||||
@mock.patch('certbot._internal.eff.handle_subscription')
|
||||
def test_certonly_new_request_failure(self, mock_subscription):
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_and_enroll_certificate.return_value = False
|
||||
self.assertRaises(errors.Error,
|
||||
self._certonly_new_request_common, mock_client)
|
||||
self.assertFalse(mock_subscription.called)
|
||||
|
||||
def _test_renewal_common(self, due_for_renewal, extra_args, log_out=None,
|
||||
args=None, should_renew=True, error_expected=False,
|
||||
|
|
@ -995,21 +1006,22 @@ class MainTest(test_util.ConfigTestCase):
|
|||
with mock.patch('certbot._internal.main.renewal.crypto_util') \
|
||||
as mock_crypto_util:
|
||||
mock_crypto_util.notAfter.return_value = expiry_date
|
||||
if not args:
|
||||
args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']
|
||||
if extra_args:
|
||||
args += extra_args
|
||||
try:
|
||||
ret, stdout, _, _ = self._call(args, stdout)
|
||||
if ret:
|
||||
print("Returned", ret)
|
||||
raise AssertionError(ret)
|
||||
assert not error_expected, "renewal should have errored"
|
||||
except: # pylint: disable=bare-except
|
||||
if not error_expected:
|
||||
raise AssertionError(
|
||||
"Unexpected renewal error:\n" +
|
||||
traceback.format_exc())
|
||||
with mock.patch('certbot._internal.eff.handle_subscription'):
|
||||
if not args:
|
||||
args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']
|
||||
if extra_args:
|
||||
args += extra_args
|
||||
try:
|
||||
ret, stdout, _, _ = self._call(args, stdout)
|
||||
if ret:
|
||||
print("Returned", ret)
|
||||
raise AssertionError(ret)
|
||||
assert not error_expected, "renewal should have errored"
|
||||
except: # pylint: disable=bare-except
|
||||
if not error_expected:
|
||||
raise AssertionError(
|
||||
"Unexpected renewal error:\n" +
|
||||
traceback.format_exc())
|
||||
|
||||
if should_renew:
|
||||
if reuse_key:
|
||||
|
|
@ -1310,13 +1322,15 @@ class MainTest(test_util.ConfigTestCase):
|
|||
|
||||
return mock_get_utility
|
||||
|
||||
def test_certonly_csr(self):
|
||||
@mock.patch('certbot._internal.eff.handle_subscription')
|
||||
def test_certonly_csr(self, mock_subscription):
|
||||
mock_get_utility = self._test_certonly_csr_common()
|
||||
cert_msg = mock_get_utility().add_message.call_args_list[0][0][0]
|
||||
self.assertTrue('fullchain.pem' in cert_msg)
|
||||
self.assertFalse('Your key file has been saved at' in cert_msg)
|
||||
self.assertTrue(
|
||||
'donate' in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(mock_subscription.called)
|
||||
|
||||
def test_certonly_csr_dry_run(self):
|
||||
mock_get_utility = self._test_certonly_csr_common(['--dry-run'])
|
||||
|
|
@ -1395,7 +1409,7 @@ class MainTest(test_util.ConfigTestCase):
|
|||
def test_update_account_with_email(self, mock_utility, mock_email):
|
||||
email = "user@example.com"
|
||||
mock_email.return_value = email
|
||||
with mock.patch('certbot._internal.eff.handle_subscription') as mock_handle:
|
||||
with mock.patch('certbot._internal.eff.prepare_subscription') as mock_prepare:
|
||||
with mock.patch('certbot._internal.main._determine_account') as mocked_det:
|
||||
with mock.patch('certbot._internal.main.account') as mocked_account:
|
||||
with mock.patch('certbot._internal.main.client') as mocked_client:
|
||||
|
|
@ -1415,10 +1429,10 @@ class MainTest(test_util.ConfigTestCase):
|
|||
self.assertTrue(
|
||||
cb_client.acme.update_registration.called)
|
||||
# and we saved the updated registration on disk
|
||||
self.assertTrue(mocked_storage.save_regr.called)
|
||||
self.assertTrue(mocked_storage.update_regr.called)
|
||||
self.assertTrue(
|
||||
email in mock_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(mock_handle.called)
|
||||
self.assertTrue(mock_prepare.called)
|
||||
|
||||
@mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins')
|
||||
@mock.patch('certbot._internal.updater._run_updaters')
|
||||
|
|
|
|||
119
snap/local/README.md
Normal file
119
snap/local/README.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# Certbot Plugin Snaps
|
||||
|
||||
This is a proof of concept of how a Certbot snap might support plugin snaps
|
||||
that add functionality to Certbot using its existing plugin API.
|
||||
|
||||
## Architecture
|
||||
|
||||
This is a description of how Certbot plugin functionality is exposed via snaps.
|
||||
For information on Certbot's plugin architecture itself, see the [Certbot
|
||||
documentation on
|
||||
plugins](https://certbot.eff.org/docs/contributing.html#plugin-architecture).
|
||||
|
||||
The Certbot snap itself is a classic snap. Plugin snaps are regular confined
|
||||
snaps, but normally do not provide any "apps" themselves. Plugin snaps export
|
||||
loadable Python modules to the Certbot snap via a snap content interface.
|
||||
|
||||
Certbot itself accepts a `CERTBOT_PLUGIN_PATH` environment variable. This
|
||||
support is currently patched but this is intended to be upstreamed. The
|
||||
variable, if set, should contain a `:`-separated list of paths to add to
|
||||
Certbot's plugin search path.
|
||||
|
||||
The Certbot snap runs Certbot via a wrapper which examines its list of
|
||||
connected interfaces, sets `CERTBOT_PLUGIN_PATH` accordingly, and then `exec`s
|
||||
Certbot itself.
|
||||
|
||||
## Use (Production)
|
||||
|
||||
_Note: this production use example assumes that these snaps are available in
|
||||
stable channels in the Snap Store, which they aren't yet. See below for
|
||||
development instructions._
|
||||
|
||||
To use a Certbot plugin snap, install both the plugin snap and the Certbot snap
|
||||
as usual. Plugin snaps are confined as normal; the Certbot snap is a classic
|
||||
snap and thus needs `--classic` during installation. For example:
|
||||
|
||||
snap install --classic certbot
|
||||
snap set certbot trust-plugin-with-root=ok
|
||||
snap install certbot-dns-dnsimple
|
||||
|
||||
Then connect the plugin snap to the main certbot snap as follows. Note that
|
||||
this connection allows the plugin snap code to run inside the certbot process,
|
||||
which has access to your host system. Only perform this step if you trust the
|
||||
plugin author to have "root" on your system.
|
||||
|
||||
sudo snap connect certbot:plugin certbot-dns-dnsimple
|
||||
|
||||
Now certbot will automatically load and use the plugin when it is run. To check
|
||||
that this has worked, `certbot plugins` should list the plugin.
|
||||
|
||||
You can now operate the plugin as normal.
|
||||
|
||||
## Use (Testing and Development)
|
||||
|
||||
To try this out, you'll need to build the snaps (a patched Certbot snap and a
|
||||
plugin snap) manually.
|
||||
|
||||
### Initial VM Set Up
|
||||
|
||||
These steps need to be done once to set up your VM and do not need to be run again to rebuild the snap.
|
||||
|
||||
1. Start with a Focal VM. You need a full virtual machine using something like DigitalOcean, EC2, or VirtualBox. Docker won't work. Another version of Ubuntu can probably be used, but Focal was used when writing these instructions.
|
||||
2. Set up a user other than root with sudo privileges for use with snapcraft and run all of the following commands with it. A command to do this for a user named certbot looks like `adduser certbot && usermod -aG sudo certbot && su - certbot`.
|
||||
3. Install git and python with `sudo apt update && sudo apt install -y git python`.
|
||||
4. Set up lxd for use with snapcraft by running `sudo snap install lxd && sudo /snap/bin/lxd.migrate -yes && sudo /snap/bin/lxd waitready && sudo /snap/bin/lxd init --auto` (errors here are ok; it may already
|
||||
have been installed on your system).
|
||||
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`
|
||||
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.
|
||||
|
||||
### Reset the Environment
|
||||
|
||||
The instructions below clean up the build environment so it can reliably be used again.
|
||||
|
||||
1. `cd ~/certbot` (or to an alternate path where you put our source files)
|
||||
2. `snapcraft clean --use-lxd`
|
||||
3. `rm certbot_*_amd64.snap`
|
||||
4. `cd certbot-dns-dnsimple`
|
||||
5. `rm certbot-dns-dnsimple_*_amd64.snap`
|
||||
6. `snapcraft clean --use-lxd`
|
||||
7. `cd ..`
|
||||
|
||||
## Publishing Permissions
|
||||
|
||||
There are security implications to permitting anyone to publish, without
|
||||
review, a plugin into the Snap Store which will then run in Certbot's classic
|
||||
snap context, with full access to the host system.
|
||||
|
||||
At a minimum, it is clear that this should happen only with the user's explicit
|
||||
opt-in action.
|
||||
|
||||
As implemented, Certbot will only load plugins connected via the snap interface
|
||||
mechanism, so permission is effectively delegated to what interface connections
|
||||
the snap infrastucture will permit.
|
||||
|
||||
We have approval from the snap team to use this design as long as we make it
|
||||
explicit what a user is agreeing to when they connect a plugin to the
|
||||
Certbot snap. That work was completed in
|
||||
https://github.com/certbot/certbot/issues/8013.
|
||||
|
||||
## Outstanding issues
|
||||
|
||||
[Outstanding items relating to plugin support in Certbot snaps are tracked on GitHub](https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22area%3A+snaps%22).
|
||||
|
|
@ -21,7 +21,8 @@ source "${DIR}/common.sh"
|
|||
RegisterQemuHandlers
|
||||
ResolveArch "${SNAP_ARCH}"
|
||||
|
||||
tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt > snap-constraints.txt
|
||||
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 &
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
# Resolve the Snap architecture to Docker architecture (DOCKER_ARCH variable)
|
||||
# and QEMU architecture (QEMU_ARCH variable).
|
||||
# Usage: ResolveArch [amd64|i386|arm64|armhf]
|
||||
# Usage: ResolveArch [amd64|arm64|armhf]
|
||||
ResolveArch() {
|
||||
local SNAP_ARCH=$1
|
||||
|
||||
|
|
@ -12,10 +12,6 @@ ResolveArch() {
|
|||
DOCKER_ARCH="amd64"
|
||||
QEMU_ARCH="x86_64"
|
||||
;;
|
||||
"i386")
|
||||
DOCKER_ARCH="i386"
|
||||
QEMU_ARCH="i386"
|
||||
;;
|
||||
"arm64")
|
||||
DOCKER_ARCH="arm64v8"
|
||||
QEMU_ARCH="aarch64"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
set -ex
|
||||
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
TARGET_ARCHS="i386 arm64 armhf"
|
||||
TARGET_ARCHS="arm64 armhf"
|
||||
|
||||
rm -rf "${DIR}/packages/"*
|
||||
|
||||
|
|
@ -14,7 +14,8 @@ source "${DIR}/common.sh"
|
|||
|
||||
RegisterQemuHandlers
|
||||
|
||||
tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt > "${DIR}/snap-constraints.txt"
|
||||
tools/strip_hashes.py letsencrypt-auto-source/pieces/dependency-requirements.txt \
|
||||
| grep -v python-augeas > "${DIR}/snap-constraints.txt"
|
||||
for SNAP_ARCH in ${TARGET_ARCHS}; do
|
||||
ResolveArch "${SNAP_ARCH}"
|
||||
DownloadQemuStatic "${QEMU_ARCH}" "${DIR}"
|
||||
|
|
@ -24,7 +25,7 @@ for SNAP_ARCH in ${TARGET_ARCHS}; do
|
|||
-v "${DIR}/qemu-${QEMU_ARCH}-static:/usr/bin/qemu-${QEMU_ARCH}-static" \
|
||||
-v "${DIR}:/workspace" \
|
||||
-w "/workspace" \
|
||||
"${DOCKER_ARCH}/ubuntu:18.04" \
|
||||
"${DOCKER_ARCH}/ubuntu:20.04" \
|
||||
sh -c "\
|
||||
apt-get update \
|
||||
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends python3 python3-venv python3-dev libffi-dev libssl-dev gcc \
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
snap/local/packages/cffi/cffi-1.14.0-cp38-cp38-linux_aarch64.whl
Normal file
BIN
snap/local/packages/cffi/cffi-1.14.0-cp38-cp38-linux_aarch64.whl
Normal file
Binary file not shown.
BIN
snap/local/packages/cffi/cffi-1.14.0-cp38-cp38-linux_armv7l.whl
Normal file
BIN
snap/local/packages/cffi/cffi-1.14.0-cp38-cp38-linux_armv7l.whl
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -15,7 +15,7 @@ description: |
|
|||
- Help you revoke the certificate if that ever becomes necessary.
|
||||
confinement: classic
|
||||
grade: devel
|
||||
base: core18
|
||||
base: core20
|
||||
adopt-info: certbot
|
||||
|
||||
apps:
|
||||
|
|
@ -26,7 +26,7 @@ apps:
|
|||
AUGEAS_LENS_LIB: "$SNAP/usr/share/augeas/lenses/dist"
|
||||
LD_LIBRARY_PATH: "$SNAP/usr/lib/x86_64-linux-gnu/:$LD_LIBRARY_PATH"
|
||||
renew:
|
||||
command: certbot -q renew
|
||||
command: certbot.wrapper -q renew
|
||||
daemon: oneshot
|
||||
environment:
|
||||
PATH: "$SNAP/bin:$SNAP/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games"
|
||||
|
|
@ -35,58 +35,42 @@ apps:
|
|||
# Run approximately twice a day with randomization
|
||||
timer: 00:00~24:00/2
|
||||
|
||||
|
||||
parts:
|
||||
python-augeas:
|
||||
plugin: python
|
||||
source: git://github.com/basak/python-augeas
|
||||
source-branch: snap
|
||||
python-version: python3
|
||||
build-packages: [libaugeas-dev]
|
||||
acme:
|
||||
plugin: python
|
||||
source: .
|
||||
source-subdir: acme
|
||||
constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt]
|
||||
python-version: python3
|
||||
# To build cryptography and cffi if needed
|
||||
build-packages: [libffi-dev, libssl-dev]
|
||||
certbot:
|
||||
plugin: python
|
||||
source: .
|
||||
source-subdir: certbot
|
||||
constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt]
|
||||
python-version: python3
|
||||
after: [acme]
|
||||
python-packages:
|
||||
- git+https://github.com/basak/python-augeas.git@snap
|
||||
- ./acme
|
||||
- ./certbot
|
||||
- ./certbot-apache
|
||||
- ./certbot-nginx
|
||||
stage:
|
||||
- -usr/lib/python3.8/sitecustomize.py # maybe unnecessary
|
||||
# Prefer cffi
|
||||
- -lib/python3.8/site-packages/augeas.py
|
||||
stage-packages:
|
||||
- libaugeas0
|
||||
# added to stage python:
|
||||
- libpython3-stdlib
|
||||
- libpython3.8-stdlib
|
||||
- libpython3.8-minimal
|
||||
- python3-pip
|
||||
- python3-setuptools
|
||||
- python3-wheel
|
||||
- python3-venv
|
||||
- python3-minimal
|
||||
- python3-distutils
|
||||
- python3-pkg-resources
|
||||
- python3.8-minimal
|
||||
# To build cryptography and cffi if needed
|
||||
build-packages: [libffi-dev, libssl-dev, git, libaugeas-dev, python3-dev]
|
||||
override-pull: |
|
||||
snapcraftctl pull
|
||||
snapcraftctl set-version `cd $SNAPCRAFT_PART_SRC && git describe|sed s/^v//`
|
||||
# Workaround for lack of site-packages leading to empty sitecustomize.py
|
||||
stage:
|
||||
- -usr/lib/python3.6/sitecustomize.py
|
||||
certbot-apache:
|
||||
plugin: python
|
||||
source: .
|
||||
source-subdir: certbot-apache
|
||||
constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt]
|
||||
python-version: python3
|
||||
after: [python-augeas, certbot]
|
||||
stage-packages: [libaugeas0]
|
||||
stage:
|
||||
# Prefer cffi
|
||||
- -lib/python3.6/site-packages/augeas.py
|
||||
certbot-nginx:
|
||||
plugin: python
|
||||
source: .
|
||||
source-subdir: certbot-nginx
|
||||
constraints: [$SNAPCRAFT_PART_SRC/snap-constraints.txt]
|
||||
python-version: python3
|
||||
# This is the last step, compile pycache now as there should be no conflicts.
|
||||
override-prime: |
|
||||
snapcraftctl prime
|
||||
./usr/bin/python3 -m compileall -q .
|
||||
# After certbot-apache to not rebuild duplicates (essentially sharing what was already staged,
|
||||
# like zope)
|
||||
after: [certbot-apache]
|
||||
snapcraftctl set-version `cd $SNAPCRAFT_PART_SRC/certbot && git describe|sed s/^v//`
|
||||
|
||||
wrappers:
|
||||
plugin: dump
|
||||
source: .
|
||||
|
|
|
|||
Loading…
Reference in a new issue