Merge remote-tracking branch 'origin' into enhance_verb

This commit is contained in:
Joona Hoikkala 2018-04-02 23:04:06 +03:00
commit 080f48156f
81 changed files with 2507 additions and 401 deletions

View file

@ -2,6 +2,110 @@
Certbot adheres to [Semantic Versioning](http://semver.org/).
## 0.22.2 - 2018-03-19
### Fixed
* A type error introduced in 0.22.1 that would occur during challenge cleanup
when a Certbot plugin raises an exception while trying to complete the
challenge was fixed.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
packages with changes other than their version number were:
* certbot
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/53?closed=1
## 0.22.1 - 2018-03-19
### Changed
* The ACME server used with Certbot's --dry-run and --staging flags is now
Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2
features with these flags.
### Fixed
* The HTTP Content-Type header is now set to the correct value during
certificate revocation with new versions of the ACME protocol.
* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank
line to the top of chain.pem and between the certificates in fullchain.pem
for each lineage. These blank lines have been removed.
* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to
work.
* Fixed a regression in acme.client.Client that caused the class to not work
when it was initialized without a ClientNetwork which is done by some of the
other projects using our ACME library.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
packages with changes other than their version number were:
* acme
* certbot
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/51?closed=1
## 0.22.0 - 2018-03-07
### Added
* Support for obtaining wildcard certificates and a newer version of the ACME
protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2
endpoint was added to Certbot and its ACME library. Certbot still works with
older ACME versions and will automatically change the version of the protocol
used based on the version the ACME CA implements.
* The Apache and Nginx plugins are now able to automatically install a wildcard
certificate to multiple virtual hosts that you select from your server
configuration.
* The `certbot install` command now accepts the `--cert-name` flag for
selecting a certificate.
* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library
which automatically handles most of the differences between new and old ACME
versions. `acme.client.ClientV2` is also available for people who only want
to support one version of the protocol or want to handle the differences
between versions themselves.
* certbot-auto now supports the flag --install-only which has the script
install Certbot and its dependencies and exit without invoking Certbot.
* Support for issuing a single certificate for a wildcard and base domain was
added to our Google Cloud DNS plugin. To do this, we now require your API
credentials have additional permissions, however, your credentials will
already have these permissions unless you defined a custom role with fewer
permissions than the standard DNS administrator role provided by Google.
These permissions are also only needed for the case described above so it
will continue to work for existing users. For more information about the
permissions changes, see the documentation in the plugin.
### Changed
* We have broken lockstep between our ACME library, Certbot, and its plugins.
This means that the different components do not need to be the same version
to work together like they did previously. This makes packaging easier
because not every piece of Certbot needs to be repackaged to ship a change to
a subset of its components.
* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot,
Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL
6 based system, it will walk you through the process of installing Certbot
with Python 3 and refuse to upgrade to a newer version of Certbot until you
have done so.
* Certbot's components now work with older versions of setuptools to simplify
packaging for EPEL 7.
### Fixed
* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives
has been resolved.
* A problem where Certbot's Apache plugin would add redundant include
directives for the TLS configuration managed by Certbot has been fixed.
* Certbot's webroot plugin now properly deletes any directories it creates.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/48?closed=1
## 0.21.1 - 2018-01-25
### Fixed

View file

@ -259,11 +259,12 @@ class Client(ClientBase):
"""
# pylint: disable=too-many-arguments
self.key = key
self.net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) if net is None else net
if net is None:
net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
if isinstance(directory, six.string_types):
directory = messages.Directory.from_json(
self.net.get(directory).json())
net.get(directory).json())
super(Client, self).__init__(directory=directory,
net=net, acme_version=1)

View file

@ -299,6 +299,16 @@ class ClientTest(ClientTestBase):
directory=uri, key=KEY, alg=jose.RS256, net=self.net)
self.net.get.assert_called_once_with(uri)
@mock.patch('acme.client.ClientNetwork')
def test_init_without_net(self, mock_net):
mock_net.return_value = mock.sentinel.net
alg = jose.RS256
from acme.client import Client
self.client = Client(
directory=self.directory, key=KEY, alg=alg)
mock_net.called_once_with(KEY, alg=alg, verify_ssl=True)
self.assertEqual(self.client.net, mock.sentinel.net)
def test_register(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -285,8 +285,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
chain_path=None, fullchain_path=None):
"""Deploys certificate to specified virtual host.
Currently tries to find the last directives to deploy the cert in
the VHost associated with the given domain. If it can't find the
Currently tries to find the last directives to deploy the certificate
in the VHost associated with the given domain. If it can't find the
directives, it searches the "included" confs. The function verifies
that it has located the three directives and finally modifies them
to point to the correct destination. After the certificate is
@ -424,14 +424,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
path["chain_path"] = self.parser.find_dir(
"SSLCertificateChainFile", None, vhost.path)
if not path["cert_path"] or not path["cert_key"]:
# Throw some can't find all of the directives error"
# Handle errors when certificate/key directives cannot be found
if not path["cert_path"]:
logger.warning(
"Cannot find a cert or key directive in %s. "
"Cannot find an SSLCertificateFile directive in %s. "
"VirtualHost was not modified", vhost.path)
# Presumably break here so that the virtualhost is not modified
raise errors.PluginError(
"Unable to find cert and/or key directives")
"Unable to find an SSLCertificateFile directive")
elif not path["cert_key"]:
logger.warning(
"Cannot find an SSLCertificateKeyFile directive for "
"certificate in %s. VirtualHost was not modified", vhost.path)
raise errors.PluginError(
"Unable to find an SSLCertificateKeyFile directive for "
"certificate")
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
@ -2133,5 +2139,3 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# to be modified.
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
self.constant("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)

View file

@ -441,13 +441,37 @@ class MultipleVhostsTest(util.ApacheTest):
self.vh_truth[1].path))
def test_deploy_cert_invalid_vhost(self):
"""For test cases where the `ApacheConfigurator` class' `_deploy_cert`
method is called with an invalid vhost parameter. Currently this tests
that a PluginError is appropriately raised when important directives
are missing in an SSL module."""
self.config.parser.modules.add("ssl_module")
mock_find = mock.MagicMock()
mock_find.return_value = []
self.config.parser.find_dir = mock_find
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
def side_effect(*args):
"""Mocks case where an SSLCertificateFile directive can be found
but an SSLCertificateKeyFile directive is missing."""
if "SSLCertificateFile" in args:
return ["example/cert.pem"]
else:
return []
mock_find_dir = mock.MagicMock(return_value=[])
mock_find_dir.side_effect = side_effect
self.config.parser.find_dir = mock_find_dir
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
self.assertRaises(
errors.PluginError, self.config.deploy_cert, "random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
# Remove side_effect to mock case where both SSLCertificateFile
# and SSLCertificateKeyFile directives are missing
self.config.parser.find_dir.side_effect = None
self.assertRaises(
errors.PluginError, self.config.deploy_cert, "random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.21.1"
LE_AUTO_VERSION="0.22.2"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
--no-bootstrap do not install OS dependencies
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
--install-only install certbot, upgrade if needed, and exit
-v, --verbose provide more output
-q, --quiet provide only update/error output;
implies --non-interactive
@ -60,6 +61,8 @@ for arg in "$@" ; do
DEBUG=1;;
--os-packages-only)
OS_PACKAGES_ONLY=1;;
--install-only)
INSTALL_ONLY=1;;
--no-self-upgrade)
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
@ -246,7 +249,7 @@ DeprecationBootstrap() {
fi
}
MIN_PYTHON_VERSION="2.6"
MIN_PYTHON_VERSION="2.7"
MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//')
# Sets LE_PYTHON to Python version string and PYVER to the first two
# digits of the python version
@ -1196,24 +1199,24 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.21.1 \
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
acme==0.21.1 \
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
certbot-apache==0.21.1 \
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
certbot-nginx==0.21.1 \
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
certbot==0.22.2 \
--hash=sha256:c8c63bdf0fed6258bdbc892454314ec37bcd1c35a7f62524a083d93ccdfc420d \
--hash=sha256:e6e3639293e78397f31f7d99e3c63aff82d91e2b0d50d146ee3c77f830464bef
acme==0.22.2 \
--hash=sha256:59a55244612ee305d2caa6bb4cddd400fb60ec841bf011ed29a2899832a682c2 \
--hash=sha256:0ecd0ea369f53d5bc744d6e72717f9af2e1ceb558d109dbd433148851027adb4
certbot-apache==0.22.2 \
--hash=sha256:b5340d4b9190358fde8eb6a5be0def37e32014b5142ee79ef5d2319ccbbde754 \
--hash=sha256:3cd26912bb5732d917ddf7aad2fe870090d4ece9a408b2c2de8e9723ec99c759
certbot-nginx==0.22.2 \
--hash=sha256:91feef0d879496835d355e82841f92e5ecb5abbf6f23ea0ee5bbb8f5a92b278a \
--hash=sha256:b10bf04c1a20cf878d5e0d1877deb0e0780bc31b0ffda08ce7199bbc39d0753b
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip 8
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
@ -1237,6 +1240,7 @@ anything goes wrong, it will exit with a non-zero status code.
from __future__ import print_function
from distutils.version import StrictVersion
from hashlib import sha256
from os import environ
from os.path import join
from pipes import quote
from shutil import rmtree
@ -1270,14 +1274,14 @@ except ImportError:
from urllib.parse import urlparse # 3.4
__version__ = 1, 3, 0
__version__ = 1, 5, 1
PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
# wheel has a conditional dependency on argparse:
maybe_argparse = (
[('https://pypi.python.org/packages/18/dd/'
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'argparse-1.4.0.tar.gz',
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
if version_info < (2, 7, 0) else [])
@ -1285,18 +1289,14 @@ maybe_argparse = (
PACKAGES = maybe_argparse + [
# Pip has no dependencies, as it vendors everything:
('https://pypi.python.org/packages/11/b6/'
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'pip-{0}.tar.gz'
.format(PIP_VERSION),
('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'pip-{0}.tar.gz'.format(PIP_VERSION),
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
# This version of setuptools has only optional dependencies:
('https://pypi.python.org/packages/69/65/'
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'setuptools-20.2.2.tar.gz',
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
('https://pypi.python.org/packages/c9/1d/'
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
'setuptools-29.0.1.tar.gz',
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'wheel-0.29.0.tar.gz',
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
]
@ -1317,12 +1317,13 @@ def hashed_download(url, temp, digest):
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
# authenticity has only privacy (not arbitrary code execution)
# implications, since we're checking hashes.
def opener():
def opener(using_https=True):
opener = build_opener(HTTPSHandler())
# Strip out HTTPHandler to prevent MITM spoof:
for handler in opener.handlers:
if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
if using_https:
# Strip out HTTPHandler to prevent MITM spoof:
for handler in opener.handlers:
if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
return opener
def read_chunks(response, chunk_size):
@ -1332,8 +1333,9 @@ def hashed_download(url, temp, digest):
break
yield chunk
response = opener().open(url)
path = join(temp, urlparse(url).path.split('/')[-1])
parsed_url = urlparse(url)
response = opener(using_https=parsed_url.scheme == 'https').open(url)
path = join(temp, parsed_url.path.split('/')[-1])
actual_hash = sha256()
with open(path, 'wb') as file:
for chunk in read_chunks(response, 4096):
@ -1346,6 +1348,24 @@ def hashed_download(url, temp, digest):
return path
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
SIMPLE = '/simple'
if env_var.endswith(SIMPLE):
return env_var[:-len(SIMPLE)]
else:
return env_var
else:
return DEFAULT_INDEX_BASE
def main():
pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1])
@ -1353,11 +1373,13 @@ def main():
if pip_version >= min_pip_version:
return 0
has_pip_cache = pip_version >= StrictVersion('6.0')
index_base = get_index_base()
temp = mkdtemp(prefix='pipstrap-')
try:
downloads = [hashed_download(url, temp, digest)
for url, digest in PACKAGES]
downloads = [hashed_download(index_base + '/packages/' + path,
temp,
digest)
for path, digest in PACKAGES]
check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings:
@ -1428,6 +1450,12 @@ UNLIKELY_EOF
say "Installation succeeded."
fi
if [ "$INSTALL_ONLY" = 1 ]; then
say "Certbot is installed."
exit 0
fi
"$VENV_BIN/letsencrypt" "$@"
else

View file

@ -10,6 +10,8 @@ import sys
import OpenSSL
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
from acme import challenges
from acme import crypto_util
from acme import messages

View file

@ -5,6 +5,7 @@ import requests
import zope.interface
import six
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
from acme import crypto_util
from acme import errors as acme_errors

View file

@ -8,7 +8,7 @@ from certbot_nginx import nginxparser
def roundtrip(stuff):
success = True
for t in stuff:
print t
print(t)
if not os.path.isfile(t):
continue
with open(t, "r") as f:

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
install_requires = [
'certbot',

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-cloudflare's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_cloudflare
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_cloudflare
:members:
Indices and tables
==================

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-cloudxns's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_cloudxns
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_cloudxns
:members:
Indices and tables

View file

@ -4,14 +4,14 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-digitalocean's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_digitalocean
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_digitalocean
:members:
Indices and tables

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-dnsimple's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_dnsimple
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_dnsimple
:members:
Indices and tables

View file

@ -4,14 +4,14 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-dnsmadeeasy's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_dnsmadeeasy
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_dnsmadeeasy
:members:
Indices and tables

View file

@ -4,14 +4,14 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View file

@ -81,7 +81,7 @@ class _GoogleClient(object):
Encapsulates all communication with the Google Cloud DNS API.
"""
def __init__(self, account_json=None):
def __init__(self, account_json=None, dns_api=None):
scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite']
if account_json is not None:
@ -92,7 +92,12 @@ class _GoogleClient(object):
credentials = None
self.project_id = self.get_project_id()
self.dns = discovery.build('dns', 'v1', credentials=credentials, cache_discovery=False)
if not dns_api:
self.dns = discovery.build('dns', 'v1',
credentials=credentials,
cache_discovery=False)
else:
self.dns = dns_api
def add_txt_record(self, domain, record_name, record_content, record_ttl):
"""

View file

@ -4,7 +4,9 @@ import os
import unittest
import mock
from googleapiclient import discovery
from googleapiclient.errors import Error
from googleapiclient.http import HttpMock
from httplib2 import ServerNotFoundError
from certbot import errors
@ -68,7 +70,13 @@ class GoogleClientTest(unittest.TestCase):
def _setUp_client_with_mock(self, zone_request_side_effect):
from certbot_dns_google.dns_google import _GoogleClient
client = _GoogleClient(ACCOUNT_JSON_PATH)
pwd = os.path.dirname(__file__)
rel_path = 'testdata/discovery.json'
discovery_file = os.path.join(pwd, rel_path)
http_mock = HttpMock(discovery_file, {'status': '200'})
dns_api = discovery.build('dns', 'v1', http=http_mock)
client = _GoogleClient(ACCOUNT_JSON_PATH, dns_api)
# Setup
mock_mz = mock.MagicMock()

File diff suppressed because it is too large Load diff

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-google's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_google
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_google
:members:
Indices and tables

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-luadns's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_luadns
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_luadns
:members:
Indices and tables

View file

@ -4,14 +4,14 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-nsone's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_nsone
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_nsone
:members:
Indices and tables

View file

@ -4,14 +4,14 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View file

@ -70,11 +70,11 @@ class Authenticator(dns_common.DNSAuthenticator):
self._validate_algorithm
)
def _perform(self, domain, validation_name, validation):
self._get_rfc2136_client().add_txt_record(domain, validation_name, validation, self.ttl)
def _perform(self, _domain, validation_name, validation):
self._get_rfc2136_client().add_txt_record(validation_name, validation, self.ttl)
def _cleanup(self, domain, validation_name, validation):
self._get_rfc2136_client().del_txt_record(domain, validation_name, validation)
def _cleanup(self, _domain, validation_name, validation):
self._get_rfc2136_client().del_txt_record(validation_name, validation)
def _get_rfc2136_client(self):
return _RFC2136Client(self.credentials.conf('server'),
@ -95,18 +95,17 @@ class _RFC2136Client(object):
})
self.algorithm = key_algorithm
def add_txt_record(self, domain_name, record_name, record_content, record_ttl):
def add_txt_record(self, record_name, record_content, record_ttl):
"""
Add a TXT record using the supplied information.
:param str domain: The domain to use to find the closest SOA.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation).
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
:raises certbot.errors.PluginError: if an error occurs communicating with the DNS server
"""
domain = self._find_domain(domain_name)
domain = self._find_domain(record_name)
n = dns.name.from_text(record_name)
o = dns.name.from_text(domain)
@ -131,18 +130,17 @@ class _RFC2136Client(object):
raise errors.PluginError('Received response from server: {0}'
.format(dns.rcode.to_text(rcode)))
def del_txt_record(self, domain_name, record_name, record_content):
def del_txt_record(self, record_name, record_content):
"""
Delete a TXT record using the supplied information.
:param str domain: The domain to use to find the closest SOA.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation).
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
:raises certbot.errors.PluginError: if an error occurs communicating with the DNS server
"""
domain = self._find_domain(domain_name)
domain = self._find_domain(record_name)
n = dns.name.from_text(record_name)
o = dns.name.from_text(domain)
@ -167,17 +165,17 @@ class _RFC2136Client(object):
raise errors.PluginError('Received response from server: {0}'
.format(dns.rcode.to_text(rcode)))
def _find_domain(self, domain_name):
def _find_domain(self, record_name):
"""
Find the closest domain with an SOA record for a given domain name.
:param str domain_name: The domain name for which to find the closest SOA record.
:param str record_name: The record name for which to find the closest SOA record.
:returns: The domain, if found.
:rtype: str
:raises certbot.errors.PluginError: if no SOA record can be found.
"""
domain_name_guesses = dns_common.base_domain_name_guesses(domain_name)
domain_name_guesses = dns_common.base_domain_name_guesses(record_name)
# Loop through until we find an authoritative SOA record
for guess in domain_name_guesses:
@ -185,7 +183,7 @@ class _RFC2136Client(object):
return guess
raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.'
.format(domain_name, domain_name_guesses))
.format(record_name, domain_name_guesses))
def _query_soa(self, domain_name):
"""

View file

@ -41,7 +41,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
def test_perform(self):
self.auth.perform([self.achall])
expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
expected = [mock.call.add_txt_record('_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
self.assertEqual(expected, self.mock_client.mock_calls)
def test_cleanup(self):
@ -49,7 +49,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
self.auth._attempt_cleanup = True
self.auth.cleanup([self.achall])
expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)]
expected = [mock.call.del_txt_record('_acme-challenge.'+DOMAIN, mock.ANY)]
self.assertEqual(expected, self.mock_client.mock_calls)
def test_invalid_algorithm_raises(self):
@ -82,7 +82,7 @@ class RFC2136ClientTest(unittest.TestCase):
# _find_domain | pylint: disable=protected-access
self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com")
self.rfc2136_client.add_txt_record(DOMAIN, "bar", "baz", 42)
self.rfc2136_client.add_txt_record("bar", "baz", 42)
query_mock.assert_called_with(mock.ANY, SERVER)
self.assertTrue("bar. 42 IN TXT \"baz\"" in str(query_mock.call_args[0][0]))
@ -96,7 +96,7 @@ class RFC2136ClientTest(unittest.TestCase):
self.assertRaises(
errors.PluginError,
self.rfc2136_client.add_txt_record,
DOMAIN, "bar", "baz", 42)
"bar", "baz", 42)
@mock.patch("dns.query.tcp")
def test_add_txt_record_server_error(self, query_mock):
@ -107,7 +107,7 @@ class RFC2136ClientTest(unittest.TestCase):
self.assertRaises(
errors.PluginError,
self.rfc2136_client.add_txt_record,
DOMAIN, "bar", "baz", 42)
"bar", "baz", 42)
@mock.patch("dns.query.tcp")
def test_del_txt_record(self, query_mock):
@ -115,7 +115,7 @@ class RFC2136ClientTest(unittest.TestCase):
# _find_domain | pylint: disable=protected-access
self.rfc2136_client._find_domain = mock.MagicMock(return_value="example.com")
self.rfc2136_client.del_txt_record(DOMAIN, "bar", "baz")
self.rfc2136_client.del_txt_record("bar", "baz")
query_mock.assert_called_with(mock.ANY, SERVER)
self.assertTrue("bar. 0 NONE TXT \"baz\"" in str(query_mock.call_args[0][0]))
@ -129,7 +129,7 @@ class RFC2136ClientTest(unittest.TestCase):
self.assertRaises(
errors.PluginError,
self.rfc2136_client.del_txt_record,
DOMAIN, "bar", "baz")
"bar", "baz")
@mock.patch("dns.query.tcp")
def test_del_txt_record_server_error(self, query_mock):
@ -140,7 +140,7 @@ class RFC2136ClientTest(unittest.TestCase):
self.assertRaises(
errors.PluginError,
self.rfc2136_client.del_txt_record,
DOMAIN, "bar", "baz")
"bar", "baz")
def test_find_domain(self):
# _query_soa | pylint: disable=protected-access

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-rfc2136's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_rfc2136
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_rfc2136
:members:
Indices and tables

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -10,14 +10,14 @@ Welcome to certbot-dns-route53's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_route53
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_route53
:members:
Indices and tables

View file

@ -3,7 +3,7 @@ import sys
from distutils.core import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -61,6 +61,9 @@ class NginxConfigurator(common.Installer):
DEFAULT_LISTEN_PORT = '80'
# SSL directives that Certbot can add when installing a new certificate.
SSL_DIRECTIVES = ['ssl_certificate', 'ssl_certificate_key', 'ssl_dhparam']
@classmethod
def add_parser_arguments(cls, add):
add("server-root", default=constants.CLI_DEFAULTS["server_root"],
@ -105,6 +108,7 @@ class NginxConfigurator(common.Installer):
self.parser = None
self.version = version
self._enhance_func = {"redirect": self._enable_redirect,
"ensure-http-header": self._set_http_header,
"staple-ocsp": self._enable_ocsp_stapling}
self.reverter.recovery_routine()
@ -188,8 +192,8 @@ class NginxConfigurator(common.Installer):
cert_directives = [['\n ', 'ssl_certificate', ' ', fullchain_path],
['\n ', 'ssl_certificate_key', ' ', key_path]]
self.parser.add_server_directives(vhost,
cert_directives, replace=True)
self.parser.update_or_add_server_directives(vhost,
cert_directives)
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
self.save_notes += ("Changed vhost at %s with addresses of %s\n" %
@ -328,7 +332,8 @@ class NginxConfigurator(common.Installer):
def _vhost_from_duplicated_default(self, domain, port=None):
if self.new_vhost is None:
default_vhost = self._get_default_vhost(port)
self.new_vhost = self.parser.duplicate_vhost(default_vhost, delete_default=True)
self.new_vhost = self.parser.duplicate_vhost(default_vhost,
remove_singleton_listen_params=True)
self.new_vhost.names = set()
self._add_server_name_to_vhost(self.new_vhost, domain)
@ -340,7 +345,7 @@ class NginxConfigurator(common.Installer):
for name in vhost.names:
name_block[0].append(' ')
name_block[0].append(name)
self.parser.add_server_directives(vhost, name_block, replace=True)
self.parser.update_or_add_server_directives(vhost, name_block)
def _get_default_vhost(self, port):
vhost_list = self.parser.get_vhosts()
@ -580,7 +585,7 @@ class NginxConfigurator(common.Installer):
# have it continue to do so.
if len(vhost.addrs) == 0:
listen_block = [['\n ', 'listen', ' ', self.DEFAULT_LISTEN_PORT]]
self.parser.add_server_directives(vhost, listen_block, replace=False)
self.parser.add_server_directives(vhost, listen_block)
if vhost.ipv6_enabled():
ipv6_block = ['\n ',
@ -614,14 +619,14 @@ class NginxConfigurator(common.Installer):
])
self.parser.add_server_directives(
vhost, ssl_block, replace=False)
vhost, ssl_block)
##################################
# enhancement methods (IInstaller)
##################################
def supported_enhancements(self): # pylint: disable=no-self-use
"""Returns currently supported enhancements."""
return ['redirect', 'staple-ocsp']
return ['redirect', 'ensure-http-header', 'staple-ocsp']
def enhance(self, domain, enhancement, options=None):
"""Enhance configuration.
@ -647,13 +652,80 @@ class NginxConfigurator(common.Installer):
test_redirect_block = _test_block_from_block(_redirect_block_for_domain(domain))
return vhost.contains_list(test_redirect_block)
def _set_http_header(self, domain, header_substring):
"""Enables header identified by header_substring on domain.
If the vhost is listening plaintextishly, separates out the relevant
directives into a new server block, and only add header directive to
HTTPS block.
:param str domain: the domain to enable header for.
:param str header_substring: String to uniquely identify a header.
e.g. Strict-Transport-Security, Upgrade-Insecure-Requests
:returns: Success
:raises .errors.PluginError: If no viable HTTPS host can be created or
set with header header_substring.
"""
vhosts = self.choose_vhosts(domain)
if not vhosts:
raise errors.PluginError(
"Unable to find corresponding HTTPS host for enhancement.")
for vhost in vhosts:
if vhost.has_header(header_substring):
raise errors.PluginEnhancementAlreadyPresent(
"Existing %s header" % (header_substring))
# if there is no separate SSL block, break the block into two and
# choose the SSL block.
if vhost.ssl and any([not addr.ssl for addr in vhost.addrs]):
_, vhost = self._split_block(vhost)
header_directives = [
['\n ', 'add_header', ' ', header_substring, ' '] +
constants.HEADER_ARGS[header_substring],
['\n']]
self.parser.add_server_directives(vhost, header_directives)
def _add_redirect_block(self, vhost, domain):
"""Add redirect directive to vhost
"""
redirect_block = _redirect_block_for_domain(domain)
self.parser.add_server_directives(
vhost, redirect_block, replace=False, insert_at_top=True)
vhost, redirect_block, insert_at_top=True)
def _split_block(self, vhost, only_directives=None):
"""Splits this "virtual host" (i.e. this nginx server block) into
separate HTTP and HTTPS blocks.
:param vhost: The server block to break up into two.
:param list only_directives: If this exists, only duplicate these directives
when splitting the block.
:type vhost: :class:`~certbot_nginx.obj.VirtualHost`
:returns: tuple (http_vhost, https_vhost)
:rtype: tuple of type :class:`~certbot_nginx.obj.VirtualHost`
"""
http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives)
def _ssl_match_func(directive):
return 'ssl' in directive
def _ssl_config_match_func(directive):
return self.mod_ssl_conf in directive
def _no_ssl_match_func(directive):
return 'ssl' not in directive
# remove all ssl addresses and related directives from the new block
for directive in self.SSL_DIRECTIVES:
self.parser.remove_server_directives(http_vhost, directive)
self.parser.remove_server_directives(http_vhost, 'listen', match_func=_ssl_match_func)
self.parser.remove_server_directives(http_vhost, 'include',
match_func=_ssl_config_match_func)
# remove all non-ssl addresses from the existing block
self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
return http_vhost, vhost
def _enable_redirect(self, domain, unused_options):
"""Redirect all equivalent HTTP traffic to ssl_vhost.
@ -694,28 +766,15 @@ class NginxConfigurator(common.Installer):
:param `~obj.Vhost` vhost: vhost to enable redirect for
"""
new_vhost = None
http_vhost = None
if vhost.ssl:
new_vhost = self.parser.duplicate_vhost(vhost,
only_directives=['listen', 'server_name'])
def _ssl_match_func(directive):
return 'ssl' in directive
def _no_ssl_match_func(directive):
return 'ssl' not in directive
# remove all ssl addresses from the new block
self.parser.remove_server_directives(new_vhost, 'listen', match_func=_ssl_match_func)
# remove all non-ssl addresses from the existing block
self.parser.remove_server_directives(vhost, 'listen', match_func=_no_ssl_match_func)
http_vhost, _ = self._split_block(vhost, ['listen', 'server_name'])
# Add this at the bottom to get the right order of directives
return_404_directive = [['\n ', 'return', ' ', '404']]
self.parser.add_server_directives(new_vhost, return_404_directive, replace=False)
self.parser.add_server_directives(http_vhost, return_404_directive)
vhost = new_vhost
vhost = http_vhost
if self._has_certbot_redirect(vhost, domain):
logger.info("Traffic on port %s already redirecting to ssl in %s",
@ -763,7 +822,7 @@ class NginxConfigurator(common.Installer):
try:
self.parser.add_server_directives(vhost,
stapling_directives, replace=False)
stapling_directives)
except errors.MisconfigurationError as error:
logger.debug(error)
raise errors.PluginError("An error occurred while enabling OCSP "
@ -837,7 +896,7 @@ class NginxConfigurator(common.Installer):
raise errors.PluginError(
"Unable to run %s -V" % self.conf('ctl'))
version_regex = re.compile(r"nginx/([0-9\.]*)", re.IGNORECASE)
version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE)
version_matches = version_regex.findall(text)
sni_regex = re.compile(r"TLS SNI support enabled", re.IGNORECASE)
@ -854,7 +913,12 @@ class NginxConfigurator(common.Installer):
if not sni_matches:
raise errors.PluginError("Nginx build doesn't support SNI")
nginx_version = tuple([int(i) for i in version_matches[0].split(".")])
product_name, product_version = version_matches[0]
if product_name is not 'nginx':
logger.warning("NGINX derivative %s is not officially supported by"
" certbot", product_name)
nginx_version = tuple([int(i) for i in product_version.split(".")])
# nginx < 0.8.48 uses machine hostname as default server_name instead of
# the empty string

View file

@ -44,3 +44,7 @@ def os_constant(key):
:return: value of constant for active os
"""
return CLI_DEFAULTS[key]
HSTS_ARGS = ['\"max-age=31536000\"', ' ', 'always']
HEADER_ARGS = {'Strict-Transport-Security': HSTS_ARGS}

View file

@ -159,16 +159,22 @@ class NginxHttp01(common.ChallengePerformer):
document_root = os.path.join(
self.configurator.config.work_dir, "http_01_nonexistent")
block.extend([['server_name', ' ', achall.domain],
['root', ' ', document_root],
self._location_directive_for_achall(achall)
])
# TODO: do we want to return something else if they otherwise access this block?
return [['server'], block]
def _location_directive_for_achall(self, achall):
validation = achall.validation(achall.account_key)
validation_path = self._get_validation_path(achall)
block.extend([['server_name', ' ', achall.domain],
['root', ' ', document_root],
[['location', ' ', '=', ' ', validation_path],
[['default_type', ' ', 'text/plain'],
['return', ' ', '200', ' ', validation]]]])
# TODO: do we want to return something else if they otherwise access this block?
return [['server'], block]
location_directive = [['location', ' ', '=', ' ', validation_path],
[['default_type', ' ', 'text/plain'],
['return', ' ', '200', ' ', validation]]]
return location_directive
def _make_or_mod_server_block(self, achall):
"""Modifies a server block to respond to a challenge.
@ -191,17 +197,12 @@ class NginxHttp01(common.ChallengePerformer):
vhost = vhosts[0]
# Modify existing server block
validation = achall.validation(achall.account_key)
validation_path = self._get_validation_path(achall)
location_directive = [[['location', ' ', '=', ' ', validation_path],
[['default_type', ' ', 'text/plain'],
['return', ' ', '200', ' ', validation]]]]
location_directive = [self._location_directive_for_achall(achall)]
self.configurator.parser.add_server_directives(vhost,
location_directive, replace=False)
location_directive)
rewrite_directive = [['rewrite', ' ', '^(/.well-known/acme-challenge/.*)',
' ', '$1', ' ', 'break']]
self.configurator.parser.add_server_directives(vhost,
rewrite_directive, replace=False, insert_at_top=True)
rewrite_directive, insert_at_top=True)

View file

@ -5,7 +5,7 @@ import six
from certbot.plugins import common
REDIRECT_DIRECTIVES = ['return', 'rewrite']
ADD_HEADER_DIRECTIVE = 'add_header'
class Addr(common.Addr):
r"""Represents an Nginx address, i.e. what comes after the 'listen'
@ -29,6 +29,8 @@ class Addr(common.Addr):
:param str port: port number or "\*" or ""
:param bool ssl: Whether the directive includes 'ssl'
:param bool default: Whether the directive includes 'default_server'
:param bool default: Whether this is an IPv6 address
:param bool ipv6only: Whether the directive includes 'ipv6only=on'
"""
UNSPECIFIED_IPV4_ADDRESSES = ('', '*', '0.0.0.0')
@ -88,6 +90,8 @@ class Addr(common.Addr):
ssl = True
elif nextpart == 'default_server':
default = True
elif nextpart == 'default':
default = True
elif nextpart == "ipv6only=on":
ipv6only = True
@ -198,6 +202,14 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
tuple(self.addrs), tuple(self.names),
self.ssl, self.enabled))
def has_header(self, header_name):
"""Determine if this server block has a particular header set.
:param str header_name: The name of the header to check for, e.g.
'Strict-Transport-Security'
"""
found = _find_directive(self.raw, ADD_HEADER_DIRECTIVE, header_name)
return found is not None
def contains_list(self, test):
"""Determine if raw server block contains test list at top level
"""
@ -233,3 +245,19 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
addrs=", ".join(str(addr) for addr in self.addrs),
names=", ".join(self.names),
https="Yes" if self.ssl else "No"))
def _find_directive(directives, directive_name, match_content=None):
"""Find a directive of type directive_name in directives. If match_content is given,
Searches for `match_content` in the directive arguments.
"""
if not directives or isinstance(directives, six.string_types) or len(directives) == 0:
return None
# If match_content is None, just match on directive type. Otherwise, match on
# both directive type -and- the content!
if directives[0] == directive_name and \
(match_content is None or match_content in directives):
return directives
matches = (_find_directive(line, directive_name, match_content) for line in directives)
return next((m for m in matches if m is not None), None)

View file

@ -276,15 +276,13 @@ class NginxParser(object):
return False
def add_server_directives(self, vhost, directives, replace, insert_at_top=False):
"""Add or replace directives in the server block identified by vhost.
def add_server_directives(self, vhost, directives, insert_at_top=False):
"""Add directives to the server block identified by vhost.
This method modifies vhost to be fully consistent with the new directives.
..note :: If replace is True and the directive already exists, the first
instance will be replaced. Otherwise, the directive is added.
..note :: If replace is False nothing gets added if an identical
block exists already.
..note :: It's an error to try and add a nonrepeatable directive that already
exists in the config block with a conflicting value.
..todo :: Doesn't match server blocks whose server_name directives are
split across multiple conf files.
@ -292,13 +290,34 @@ class NginxParser(object):
:param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
whose information we use to match on
:param list directives: The directives to add
:param bool replace: Whether to only replace existing directives
:param bool insert_at_top: True if the directives need to be inserted at the top
of the server block instead of the bottom
"""
self._modify_server_directives(vhost,
functools.partial(_add_directives, directives, replace, insert_at_top))
functools.partial(_add_directives, directives, insert_at_top))
def update_or_add_server_directives(self, vhost, directives, insert_at_top=False):
"""Add or replace directives in the server block identified by vhost.
This method modifies vhost to be fully consistent with the new directives.
..note :: When a directive with the same name already exists in the
config block, the first instance will be replaced. Otherwise, the directive
will be appended/prepended to the config block as in add_server_directives.
..todo :: Doesn't match server blocks whose server_name directives are
split across multiple conf files.
:param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost
whose information we use to match on
:param list directives: The directives to add
:param bool insert_at_top: True if the directives need to be inserted at the top
of the server block instead of the bottom
"""
self._modify_server_directives(vhost,
functools.partial(_update_or_add_directives, directives, insert_at_top))
def remove_server_directives(self, vhost, directive_name, match_func=None):
"""Remove all directives of type directive_name.
@ -335,13 +354,14 @@ class NginxParser(object):
except errors.MisconfigurationError as err:
raise errors.MisconfigurationError("Problem in %s: %s" % (filename, str(err)))
def duplicate_vhost(self, vhost_template, delete_default=False, only_directives=None):
def duplicate_vhost(self, vhost_template, remove_singleton_listen_params=False,
only_directives=None):
"""Duplicate the vhost in the configuration files.
:param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost
whose information we copy
:param bool delete_default: If we should remove default_server
from listen directives in the block.
:param bool remove_singleton_listen_params: If we should remove parameters
from listen directives in the block that can only be used once per address
:param list only_directives: If it exists, only duplicate the named directives. Only
looks at first level of depth; does not expand includes.
@ -368,15 +388,21 @@ class NginxParser(object):
enclosing_block.append(raw_in_parsed)
new_vhost.path[-1] = len(enclosing_block) - 1
if delete_default:
if remove_singleton_listen_params:
for addr in new_vhost.addrs:
addr.default = False
addr.ipv6only = False
for directive in enclosing_block[new_vhost.path[-1]][1]:
if (len(directive) > 0 and directive[0] == 'listen'
and 'default_server' in directive):
del directive[directive.index('default_server')]
if len(directive) > 0 and directive[0] == 'listen':
if 'default_server' in directive:
del directive[directive.index('default_server')]
if 'default' in directive:
del directive[directive.index('default')]
if 'ipv6only=on' in directive:
del directive[directive.index('ipv6only=on')]
return new_vhost
def _parse_ssl_options(ssl_options):
if ssl_options is not None:
try:
@ -523,32 +549,23 @@ def _is_ssl_on_directive(entry):
len(entry) == 2 and entry[0] == 'ssl' and
entry[1] == 'on')
def _add_directives(directives, replace, insert_at_top, block):
"""Adds or replaces directives in a config block.
When replace=False, it's an error to try and add a nonrepeatable directive that already
exists in the config block with a conflicting value.
When replace=True and a directive with the same name already exists in the
config block, the first instance will be replaced. Otherwise, the directive
will be added to the config block.
..todo :: Find directives that are in included files.
:param list directives: The new directives.
:param bool replace: Described above.
:param bool insert_at_top: Described above.
:param list block: The block to replace in
"""
def _add_directives(directives, insert_at_top, block):
"""Adds directives to a config block."""
for directive in directives:
_add_directive(block, directive, replace, insert_at_top)
_add_directive(block, directive, insert_at_top)
if block and '\n' not in block[-1]: # could be " \n " or ["\n"] !
block.append(nginxparser.UnspacedList('\n'))
def _update_or_add_directives(directives, insert_at_top, block):
"""Adds or replaces directives in a config block."""
for directive in directives:
_update_or_add_directive(block, directive, insert_at_top)
if block and '\n' not in block[-1]: # could be " \n " or ["\n"] !
block.append(nginxparser.UnspacedList('\n'))
INCLUDE = 'include'
REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'location', 'rewrite'])
REPEATABLE_DIRECTIVES = set(['server_name', 'listen', INCLUDE, 'rewrite'])
COMMENT = ' managed by Certbot'
COMMENT_BLOCK = [' ', '#', COMMENT]
@ -600,28 +617,20 @@ def _find_location(block, directive_name, match_func=None):
return next((index for index, line in enumerate(block) \
if line and line[0] == directive_name and (match_func is None or match_func(line))), None)
def _add_directive(block, directive, replace, insert_at_top):
"""Adds or replaces a single directive in a config block.
def _is_whitespace_or_comment(directive):
"""Is this directive either a whitespace or comment directive?"""
return len(directive) == 0 or directive[0] == '#'
See _add_directives for more documentation.
"""
directive = nginxparser.UnspacedList(directive)
def is_whitespace_or_comment(directive):
"""Is this directive either a whitespace or comment directive?"""
return len(directive) == 0 or directive[0] == '#'
if is_whitespace_or_comment(directive):
def _add_directive(block, directive, insert_at_top):
if not isinstance(directive, nginxparser.UnspacedList):
directive = nginxparser.UnspacedList(directive)
if _is_whitespace_or_comment(directive):
# whitespace or comment
block.append(directive)
return
location = _find_location(block, directive[0])
if replace:
if location is not None:
block[location] = directive
comment_directive(block, location)
return
# Append or prepend directive. Fail if the name is not a repeatable directive name,
# and there is already a copy of that directive with a different value
# in the config file.
@ -646,7 +655,7 @@ def _add_directive(block, directive, replace, insert_at_top):
for included_directive in included_directives:
included_dir_loc = _find_location(block, included_directive[0])
included_dir_name = included_directive[0]
if not is_whitespace_or_comment(included_directive) \
if not _is_whitespace_or_comment(included_directive) \
and not can_append(included_dir_loc, included_dir_name):
if block[included_dir_loc] != included_directive:
raise errors.MisconfigurationError(err_fmt.format(included_directive,
@ -667,6 +676,30 @@ def _add_directive(block, directive, replace, insert_at_top):
elif block[location] != directive:
raise errors.MisconfigurationError(err_fmt.format(directive, block[location]))
def _update_directive(block, directive, location):
block[location] = directive
comment_directive(block, location)
def _update_or_add_directive(block, directive, insert_at_top):
if not isinstance(directive, nginxparser.UnspacedList):
directive = nginxparser.UnspacedList(directive)
if _is_whitespace_or_comment(directive):
# whitespace or comment
block.append(directive)
return
location = _find_location(block, directive[0])
# we can update directive
if location is not None:
_update_directive(block, directive, location)
return
_add_directive(block, directive, insert_at_top)
def _is_certbot_comment(directive):
return '#' in directive and COMMENT in directive
def _remove_directives(directive_name, match_func, block):
"""Removes directives of name directive_name from a config block if match_func matches.
"""
@ -674,6 +707,9 @@ def _remove_directives(directive_name, match_func, block):
location = _find_location(block, directive_name, match_func=match_func)
if location is None:
return
# if the directive was made by us, remove the comment following
if location + 1 < len(block) and _is_certbot_comment(block[location + 1]):
del block[location + 1]
del block[location]
def _apply_global_addr_ssl(addr_to_ssl, parsed_server):

View file

@ -94,7 +94,7 @@ class NginxConfiguratorTest(util.NginxTest):
"globalssl.com", "globalsslsetssl.com", "ipv6.com", "ipv6ssl.com"]))
def test_supported_enhancements(self):
self.assertEqual(['redirect', 'staple-ocsp'],
self.assertEqual(['redirect', 'ensure-http-header', 'staple-ocsp'],
self.config.supported_enhancements())
def test_enhance(self):
@ -113,8 +113,7 @@ class NginxConfiguratorTest(util.NginxTest):
None, [0])
self.config.parser.add_server_directives(
mock_vhost,
[['listen', ' ', '5001', ' ', 'ssl']],
replace=False)
[['listen', ' ', '5001', ' ', 'ssl']])
self.config.save()
# pylint: disable=protected-access
@ -206,9 +205,9 @@ class NginxConfiguratorTest(util.NginxTest):
"example/chain.pem",
None)
@mock.patch('certbot_nginx.parser.NginxParser.add_server_directives')
def test_deploy_cert_raise_on_add_error(self, mock_add_server_directives):
mock_add_server_directives.side_effect = errors.MisconfigurationError()
@mock.patch('certbot_nginx.parser.NginxParser.update_or_add_server_directives')
def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives):
mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError()
self.assertRaises(
errors.PluginError,
self.config.deploy_cert,
@ -510,6 +509,54 @@ class NginxConfiguratorTest(util.NginxTest):
['return', '404'], ['#', ' managed by Certbot'], [], [], []]]],
generated_conf)
def test_split_for_headers(self):
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
self.config.deploy_cert(
"example.org",
"example/cert.pem",
"example/key.pem",
"example/chain.pem",
"example/fullchain.pem")
self.config.enhance("www.example.com", "ensure-http-header", "Strict-Transport-Security")
generated_conf = self.config.parser.parsed[example_conf]
self.assertEqual(
[[['server'], [
['server_name', '.example.com'],
['server_name', 'example.*'], [],
['listen', '5001', 'ssl'], ['#', ' managed by Certbot'],
['ssl_certificate', 'example/fullchain.pem'], ['#', ' managed by Certbot'],
['ssl_certificate_key', 'example/key.pem'], ['#', ' managed by Certbot'],
['include', self.config.mod_ssl_conf], ['#', ' managed by Certbot'],
['ssl_dhparam', self.config.ssl_dhparams], ['#', ' managed by Certbot'],
[], [],
['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always'],
['#', ' managed by Certbot'],
[], []]],
[['server'], [
['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', '.example.com'],
['server_name', 'example.*'],
[], [], []]]],
generated_conf)
def test_http_header_hsts(self):
example_conf = self.config.parser.abs_path('sites-enabled/example.com')
self.config.enhance("www.example.com", "ensure-http-header",
"Strict-Transport-Security")
expected = ['add_header', 'Strict-Transport-Security', '"max-age=31536000"', 'always']
generated_conf = self.config.parser.parsed[example_conf]
self.assertTrue(util.contains_at_depth(generated_conf, expected, 2))
def test_http_header_hsts_twice(self):
self.config.enhance("www.example.com", "ensure-http-header",
"Strict-Transport-Security")
self.assertRaises(
errors.PluginEnhancementAlreadyPresent,
self.config.enhance, "www.example.com",
"ensure-http-header", "Strict-Transport-Security")
@mock.patch('certbot_nginx.obj.VirtualHost.contains_list')
def test_certbot_redirect_exists(self, mock_contains_list):
# Test that we add no redirect statement if there is already a

View file

@ -14,6 +14,7 @@ class AddrTest(unittest.TestCase):
self.addr5 = Addr.fromstring("myhost")
self.addr6 = Addr.fromstring("80 default_server spdy")
self.addr7 = Addr.fromstring("unix:/var/run/nginx.sock")
self.addr8 = Addr.fromstring("*:80 default ssl")
def test_fromstring(self):
self.assertEqual(self.addr1.get_addr(), "192.168.1.1")
@ -46,6 +47,8 @@ class AddrTest(unittest.TestCase):
self.assertFalse(self.addr6.ssl)
self.assertTrue(self.addr6.default)
self.assertTrue(self.addr8.default)
self.assertEqual(None, self.addr7)
def test_str(self):
@ -55,6 +58,7 @@ class AddrTest(unittest.TestCase):
self.assertEqual(str(self.addr4), "*:80 default_server ssl")
self.assertEqual(str(self.addr5), "myhost")
self.assertEqual(str(self.addr6), "80 default_server")
self.assertEqual(str(self.addr8), "*:80 default_server ssl")
def test_to_string(self):
self.assertEqual(self.addr1.to_string(), "192.168.1.1")
@ -77,7 +81,8 @@ class AddrTest(unittest.TestCase):
from certbot_nginx.obj import Addr
any_addresses = ("0.0.0.0:80 default_server ssl",
"80 default_server ssl",
"*:80 default_server ssl")
"*:80 default_server ssl",
"80 default ssl")
for first, second in itertools.combinations(any_addresses, 2):
self.assertEqual(Addr.fromstring(first), Addr.fromstring(second))
@ -143,6 +148,15 @@ class VirtualHostTest(unittest.TestCase):
"filp",
set([Addr.fromstring("localhost")]), False, False,
set(['localhost']), raw4, [])
raw_has_hsts = [
['listen', '69.50.225.155:9000'],
['server_name', 'return.com'],
['add_header', 'always', 'set', 'Strict-Transport-Security', '\"max-age=31536000\"'],
]
self.vhost_has_hsts = VirtualHost(
"filep",
set([Addr.fromstring("localhost")]), False, False,
set(['localhost']), raw_has_hsts, [])
def test_eq(self):
from certbot_nginx.obj import Addr
@ -162,6 +176,12 @@ class VirtualHostTest(unittest.TestCase):
'enabled: False'])
self.assertEqual(stringified, str(self.vhost1))
def test_has_header(self):
self.assertTrue(self.vhost_has_hsts.has_header('Strict-Transport-Security'))
self.assertFalse(self.vhost_has_hsts.has_header('Bogus-Header'))
self.assertFalse(self.vhost1.has_header('Strict-Transport-Security'))
self.assertFalse(self.vhost1.has_header('Bogus-Header'))
def test_contains_list(self):
from certbot_nginx.obj import VirtualHost
from certbot_nginx.obj import Addr

View file

@ -191,6 +191,31 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
['server_name', '*.www.foo.com', '*.www.example.com']]
self.assertTrue(nparser.has_ssl_on_directive(mock_vhost))
def test_remove_server_directives(self):
nparser = parser.NginxParser(self.config_path)
mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'),
None, None, None,
set(['localhost',
r'~^(www\.)?(example|bar)\.']),
None, [10, 1, 9])
example_com = nparser.abs_path('sites-enabled/example.com')
names = set(['.example.com', 'example.*'])
mock_vhost.filep = example_com
mock_vhost.names = names
mock_vhost.path = [0]
nparser.add_server_directives(mock_vhost,
[['foo', 'bar'], ['ssl_certificate',
'/etc/ssl/cert2.pem']])
nparser.remove_server_directives(mock_vhost, 'foo')
nparser.remove_server_directives(mock_vhost, 'ssl_certificate')
self.assertEqual(nparser.parsed[example_com],
[[['server'], [['listen', '69.50.225.155:9000'],
['listen', '127.0.0.1'],
['server_name', '.example.com'],
['server_name', 'example.*'],
[]]]])
def test_add_server_directives(self):
nparser = parser.NginxParser(self.config_path)
mock_vhost = obj.VirtualHost(nparser.abs_path('nginx.conf'),
@ -200,8 +225,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
None, [10, 1, 9])
nparser.add_server_directives(mock_vhost,
[['foo', 'bar'], ['\n ', 'ssl_certificate', ' ',
'/etc/ssl/cert.pem']],
replace=False)
'/etc/ssl/cert.pem']])
ssl_re = re.compile(r'\n\s+ssl_certificate /etc/ssl/cert.pem')
dump = nginxparser.dumps(nparser.parsed[nparser.abs_path('nginx.conf')])
self.assertEqual(1, len(re.findall(ssl_re, dump)))
@ -213,10 +237,8 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
mock_vhost.path = [0]
nparser.add_server_directives(mock_vhost,
[['foo', 'bar'], ['ssl_certificate',
'/etc/ssl/cert2.pem']],
replace=False)
nparser.add_server_directives(mock_vhost, [['foo', 'bar']],
replace=False)
'/etc/ssl/cert2.pem']])
nparser.add_server_directives(mock_vhost, [['foo', 'bar']])
from certbot_nginx.parser import COMMENT
self.assertEqual(nparser.parsed[example_com],
[[['server'], [['listen', '69.50.225.155:9000'],
@ -238,8 +260,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
nparser.add_server_directives,
mock_vhost,
[['foo', 'bar'],
['ssl_certificate', '/etc/ssl/cert2.pem']],
replace=False)
['ssl_certificate', '/etc/ssl/cert2.pem']])
def test_comment_is_repeatable(self):
nparser = parser.NginxParser(self.config_path)
@ -249,12 +270,10 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
set(['.example.com', 'example.*']),
None, [0])
nparser.add_server_directives(mock_vhost,
[['\n ', '#', ' ', 'what a nice comment']],
replace=False)
[['\n ', '#', ' ', 'what a nice comment']])
nparser.add_server_directives(mock_vhost,
[['\n ', 'include', ' ',
nparser.abs_path('comment_in_file.conf')]],
replace=False)
nparser.abs_path('comment_in_file.conf')]])
from certbot_nginx.parser import COMMENT
self.assertEqual(nparser.parsed[example_com],
[[['server'], [['listen', '69.50.225.155:9000'],
@ -273,8 +292,8 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
target = set(['.example.com', 'example.*'])
filep = nparser.abs_path('sites-enabled/example.com')
mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0])
nparser.add_server_directives(
mock_vhost, [['server_name', 'foobar.com']], replace=True)
nparser.update_or_add_server_directives(
mock_vhost, [['server_name', 'foobar.com']])
from certbot_nginx.parser import COMMENT
self.assertEqual(
nparser.parsed[filep],
@ -284,8 +303,8 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
['server_name', 'example.*'], []
]]])
mock_vhost.names = set(['foobar.com', 'example.*'])
nparser.add_server_directives(
mock_vhost, [['ssl_certificate', 'cert.pem']], replace=True)
nparser.update_or_add_server_directives(
mock_vhost, [['ssl_certificate', 'cert.pem']])
self.assertEqual(
nparser.parsed[filep],
[[['server'], [['listen', '69.50.225.155:9000'],
@ -411,7 +430,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
vhosts = nparser.get_vhosts()
default = [x for x in vhosts if 'default' in x.filep][0]
new_vhost = nparser.duplicate_vhost(default, delete_default=True)
new_vhost = nparser.duplicate_vhost(default, remove_singleton_listen_params=True)
nparser.filedump(ext='')
# check properties of new vhost
@ -429,6 +448,28 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
self.assertEqual(len(default.raw), len(new_vhost_parsed.raw))
self.assertTrue(next(iter(default.addrs)).super_eq(next(iter(new_vhost_parsed.addrs))))
def test_duplicate_vhost_remove_ipv6only(self):
nparser = parser.NginxParser(self.config_path)
vhosts = nparser.get_vhosts()
ipv6ssl = [x for x in vhosts if 'ipv6ssl' in x.filep][0]
new_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=True)
nparser.filedump(ext='')
for addr in new_vhost.addrs:
self.assertFalse(addr.ipv6only)
identical_vhost = nparser.duplicate_vhost(ipv6ssl, remove_singleton_listen_params=False)
nparser.filedump(ext='')
called = False
for addr in identical_vhost.addrs:
if addr.ipv6:
self.assertTrue(addr.ipv6only)
called = True
self.assertTrue(called)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.23.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -49,9 +49,9 @@ http {
server {
# IPv4.
listen 5002;
listen 5002 $default_server;
# IPv6.
listen [::]:5002 default ipv6only=on;
listen [::]:5002 $default_server;
server_name nginx.wtf nginx2.wtf;
root $root/webroot;
@ -62,5 +62,36 @@ http {
try_files \$uri \$uri/ /index.html;
}
}
server {
listen 5002;
listen [::]:5002;
server_name nginx3.wtf;
root $root/webroot;
location /.well-known/ {
return 404;
}
return 301 https://\$host\$request_uri;
}
server {
listen 8082;
listen [::]:8082;
server_name nginx4.wtf nginx5.wtf;
}
server {
listen 5002;
listen [::]:5002;
listen 5001 ssl;
listen [::]:5001 ssl;
if (\$scheme != "https") {
return 301 https://\$host\$request_uri;
}
server_name nginx6.wtf nginx7.wtf;
}
}
EOF

View file

@ -1,4 +1,4 @@
#!/bin/sh -xe
#!/bin/bash -xe
# prerequisite: apt-get install --no-install-recommends nginx-light openssl
. ./tests/integration/_common.sh
@ -6,13 +6,15 @@
export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx
nginx_root="$root/nginx"
mkdir $nginx_root
original=$(root="$nginx_root" ./certbot-nginx/tests/boulder-integration.conf.sh)
nginx_conf="$nginx_root/nginx.conf"
echo "$original" > $nginx_conf
reload_nginx () {
original=$(root="$nginx_root" ./certbot-nginx/tests/boulder-integration.conf.sh)
nginx_conf="$nginx_root/nginx.conf"
echo "$original" > $nginx_conf
killall nginx || true
nginx -c $nginx_root/nginx.conf
killall nginx || true
nginx -c $nginx_root/nginx.conf
}
certbot_test_nginx () {
certbot_test \
@ -32,10 +34,30 @@ test_deployment_and_rollback() {
diff -q <(echo "$original") $nginx_conf
}
export default_server="default_server"
reload_nginx
certbot_test_nginx --domains nginx.wtf run
test_deployment_and_rollback nginx.wtf
certbot_test_nginx --domains nginx2.wtf --preferred-challenges http
test_deployment_and_rollback nginx2.wtf
# Overlapping location block and server-block-level return 301
certbot_test_nginx --domains nginx3.wtf --preferred-challenges http
test_deployment_and_rollback nginx3.wtf
# No matching server block; default_server exists
certbot_test_nginx --domains nginx4.wtf --preferred-challenges http
test_deployment_and_rollback nginx4.wtf
# No matching server block; default_server does not exist
export default_server=""
reload_nginx
if nginx -c $nginx_root/nginx.conf -T 2>/dev/null | grep "default_server"; then
echo "Failed to remove default_server"
exit 1
fi
certbot_test_nginx --domains nginx5.wtf --preferred-challenges http
test_deployment_and_rollback nginx5.wtf
# Mutiple domains, mix of matching and not
certbot_test_nginx --domains nginx6.wtf,nginx7.wtf --preferred-challenges http
test_deployment_and_rollback nginx6.wtf
# note: not reached if anything above fails, hence "killall" at the
# top

View file

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

View file

@ -69,14 +69,15 @@ class AuthHandler(object):
# While there are still challenges remaining...
while self._has_challenges(aauthzrs):
resp = self._solve_challenges(aauthzrs)
logger.info("Waiting for verification...")
if config.debug_challenges:
notify('Challenges loaded. Press continue to submit to CA. '
'Pass "-v" for more info about challenges.', pause=True)
with error_handler.ExitHandler(self._cleanup_challenges, aauthzrs):
resp = self._solve_challenges(aauthzrs)
logger.info("Waiting for verification...")
if config.debug_challenges:
notify('Challenges loaded. Press continue to submit to CA. '
'Pass "-v" for more info about challenges.', pause=True)
# Send all Responses - this modifies achalls
self._respond(aauthzrs, resp, best_effort)
# Send all Responses - this modifies achalls
self._respond(aauthzrs, resp, best_effort)
# Just make sure all decisions are complete.
self.verify_authzr_complete(aauthzrs)
@ -118,14 +119,13 @@ class AuthHandler(object):
"""Get Responses for challenges from authenticators."""
resp = []
all_achalls = self._get_all_achalls(aauthzrs)
with error_handler.ErrorHandler(self._cleanup_challenges, all_achalls):
try:
if all_achalls:
resp = self.auth.perform(all_achalls)
except errors.AuthorizationError:
logger.critical("Failure in setting up challenges.")
logger.info("Attempting to clean up outstanding challenges...")
raise
try:
if all_achalls:
resp = self.auth.perform(all_achalls)
except errors.AuthorizationError:
logger.critical("Failure in setting up challenges.")
logger.info("Attempting to clean up outstanding challenges...")
raise
assert len(resp) == len(all_achalls)
@ -147,13 +147,10 @@ class AuthHandler(object):
"""
# TODO: chall_update is a dirty hack to get around acme-spec #105
chall_update = dict()
active_achalls = self._send_responses(aauthzrs, resp, chall_update)
self._send_responses(aauthzrs, resp, chall_update)
# Check for updated status...
try:
self._poll_challenges(aauthzrs, chall_update, best_effort)
finally:
self._cleanup_challenges(aauthzrs, active_achalls)
self._poll_challenges(aauthzrs, chall_update, best_effort)
def _send_responses(self, aauthzrs, resps, chall_update):
"""Send responses and make sure errors are handled.
@ -161,6 +158,13 @@ class AuthHandler(object):
:param aauthzrs: authorizations and the selected annotated challenges
to try and perform
:type aauthzrs: `list` of `AnnotatedAuthzr`
:param resps: challenge responses from the authenticator where
each response at index i corresponds to the annotated
challenge at index i in the list returned by
:func:`_get_all_achalls`
:type resps: `collections.abc.Iterable` of
:class:`~acme.challenges.ChallengeResponse` or `False` or
`None`
:param dict chall_update: parameter that is updated to hold
aauthzr index to list of outstanding solved annotated challenges
@ -287,19 +291,19 @@ class AuthHandler(object):
chall_prefs.extend(plugin_pref)
return chall_prefs
def _cleanup_challenges(self, aauthzrs, achall_list=None):
def _cleanup_challenges(self, aauthzrs, achalls=None):
"""Cleanup challenges.
If achall_list is not provided, cleanup all achallenges.
:param aauthzrs: authorizations and their selected annotated
challenges
:type aauthzrs: `list` of `AnnotatedAuthzr`
:param achalls: annotated challenges to cleanup
:type achalls: `list` of :class:`certbot.achallenges.AnnotatedChallenge`
"""
logger.info("Cleaning up challenges")
if achall_list is None:
if achalls is None:
achalls = self._get_all_achalls(aauthzrs)
else:
achalls = achall_list
if achalls:
self.auth.cleanup(achalls)
for achall in achalls:

View file

@ -84,7 +84,7 @@ CLI_DEFAULTS = dict(
config_dir="/etc/letsencrypt",
work_dir="/var/lib/letsencrypt",
logs_dir="/var/log/letsencrypt",
server="https://acme-v01.api.letsencrypt.org/directory",
server="https://acme-v02.api.letsencrypt.org/directory",
# Plugins parsers
configurator=None,
@ -107,7 +107,7 @@ CLI_DEFAULTS = dict(
dns_route53=False
)
STAGING_URI = "https://acme-staging.api.letsencrypt.org/directory"
STAGING_URI = "https://acme-staging-v02.api.letsencrypt.org/directory"
# The set of reasons for revoking a certificate is defined in RFC 5280 in
# section 5.3.1. The reasons that users are allowed to submit are restricted to
@ -135,13 +135,13 @@ RENEWER_DEFAULTS = dict(
"""Defaults for renewer script."""
ENHANCEMENTS = ["redirect", "http-header", "ocsp-stapling", "spdy"]
ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling", "spdy"]
"""List of possible :class:`certbot.interfaces.IInstaller`
enhancements.
List of expected options parameters:
- redirect: None
- http-header: TODO
- ensure-http-header: name of header (i.e. Strict-Transport-Security)
- ocsp-stapling: certificate chain file path
- spdy: TODO

View file

@ -445,5 +445,5 @@ def cert_and_chain_from_fullchain(fullchain_pem):
"""
cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, fullchain_pem)).decode()
chain = fullchain_pem[len(cert):]
chain = fullchain_pem[len(cert):].lstrip()
return (cert, chain)

View file

@ -24,7 +24,6 @@ if os.name != "nt":
if signal.getsignal(signal_code) != signal.SIG_IGN:
_SIGNALS.append(signal_code)
class ErrorHandler(object):
"""Context manager for running code that must be cleaned up on failure.
@ -55,6 +54,7 @@ class ErrorHandler(object):
"""
def __init__(self, func=None, *args, **kwargs):
self.call_on_regular_exit = False
self.body_executed = False
self.funcs = []
self.prev_handlers = {}
@ -70,8 +70,11 @@ class ErrorHandler(object):
self.body_executed = True
retval = False
# SystemExit is ignored to properly handle forks that don't exec
if exec_type in (None, SystemExit):
if exec_type is SystemExit:
return retval
elif exec_type is None:
if not self.call_on_regular_exit:
return retval
elif exec_type is errors.SignalExit:
logger.debug("Encountered signals: %s", self.received_signals)
retval = True
@ -136,3 +139,15 @@ class ErrorHandler(object):
for signum in self.received_signals:
logger.debug("Calling signal %s", signum)
os.kill(os.getpid(), signum)
class ExitHandler(ErrorHandler):
"""Context manager for running code that must be cleaned up.
Subclass of ErrorHandler, with the same usage and parameters.
In addition to cleaning up on all signals, also cleans up on
regular exit.
"""
def __init__(self, func=None, *args, **kwargs):
ErrorHandler.__init__(self, func, *args, **kwargs)
self.call_on_regular_exit = True

View file

@ -19,7 +19,6 @@ import logging.handlers
import os
import sys
import tempfile
import time
import traceback
from acme import messages
@ -148,7 +147,6 @@ def setup_log_file_handler(config, logfile, fmt):
handler.doRollover() # TODO: creates empty letsencrypt.log.1 file
handler.setLevel(logging.DEBUG)
handler_formatter = logging.Formatter(fmt=fmt)
handler_formatter.converter = time.gmtime # don't use localtime
handler.setFormatter(handler_formatter)
return handler, log_file_path

View file

@ -278,6 +278,43 @@ class HandleAuthorizationsTest(unittest.TestCase):
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
def test_perform_error(self):
self.mock_auth.perform.side_effect = errors.AuthorizationError
authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=True)
mock_order = mock.MagicMock(authorizations=[authzr])
self.assertRaises(errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
@mock.patch("certbot.auth_handler.AuthHandler._respond")
def test_respond_error(self, mock_respond):
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)]
mock_order = mock.MagicMock(authorizations=authzrs)
mock_respond.side_effect = errors.AuthorizationError
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
@mock.patch("certbot.auth_handler.AuthHandler._poll_challenges")
@mock.patch("certbot.auth_handler.AuthHandler.verify_authzr_complete")
def test_incomplete_authzr_error(self, mock_verify, mock_poll):
authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)]
mock_order = mock.MagicMock(authorizations=authzrs)
mock_verify.side_effect = errors.AuthorizationError
mock_poll.side_effect = self._validate_all
self.assertRaises(
errors.AuthorizationError, self.handler.handle_authorizations, mock_order)
self.assertEqual(self.mock_auth.cleanup.call_count, 1)
self.assertEqual(
self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01")
def _validate_all(self, aauthzrs, unused_1, unused_2):
for i, aauthzr in enumerate(aauthzrs):
azr = aauthzr.authzr

View file

@ -380,10 +380,12 @@ class CertAndChainFromFullchainTest(unittest.TestCase):
cert_pem = CERT.decode()
chain_pem = cert_pem + SS_CERT.decode()
fullchain_pem = cert_pem + chain_pem
spacey_fullchain_pem = cert_pem + u'\n' + chain_pem
from certbot.crypto_util import cert_and_chain_from_fullchain
cert_out, chain_out = cert_and_chain_from_fullchain(fullchain_pem)
self.assertEqual(cert_out, cert_pem)
self.assertEqual(chain_out, chain_pem)
for fullchain in (fullchain_pem, spacey_fullchain_pem):
cert_out, chain_out = cert_and_chain_from_fullchain(fullchain)
self.assertEqual(cert_out, cert_pem)
self.assertEqual(chain_out, chain_pem)
if __name__ == '__main__':

View file

@ -36,7 +36,7 @@ def send_signal(signum):
class ErrorHandlerTest(unittest.TestCase):
"""Tests for certbot.error_handler."""
"""Tests for certbot.error_handler.ErrorHandler."""
def setUp(self):
from certbot import error_handler
@ -47,6 +47,7 @@ class ErrorHandlerTest(unittest.TestCase):
self.handler = error_handler.ErrorHandler(self.init_func,
*self.init_args,
**self.init_kwargs)
# pylint: disable=protected-access
self.signals = error_handler._SIGNALS
@ -113,6 +114,33 @@ class ErrorHandlerTest(unittest.TestCase):
pass
self.assertFalse(self.init_func.called)
def test_regular_exit(self):
func = mock.MagicMock()
self.handler.register(func)
with self.handler:
pass
self.init_func.assert_not_called()
func.assert_not_called()
class ExitHandlerTest(ErrorHandlerTest):
"""Tests for certbot.error_handler.ExitHandler."""
def setUp(self):
from certbot import error_handler
super(ExitHandlerTest, self).setUp()
self.handler = error_handler.ExitHandler(self.init_func,
*self.init_args,
**self.init_kwargs)
def test_regular_exit(self):
func = mock.MagicMock()
self.handler.register(func)
with self.handler:
pass
self.init_func.assert_called_once_with(*self.init_args,
**self.init_kwargs)
func.assert_called_once_with()
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -156,7 +156,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase):
handler.close()
self.assertEqual(handler.level, logging.DEBUG)
self.assertEqual(handler.formatter.converter, time.gmtime)
self.assertEqual(handler.formatter.converter, time.localtime)
expected_path = os.path.join(self.config.logs_dir, log_file)
self.assertEqual(log_path, expected_path)

View file

@ -726,7 +726,7 @@ class RenewableCertTests(BaseRenewableCertTest):
self.test_rc.configuration["renewalparams"] = {}
rp = self.test_rc.configuration["renewalparams"]
self.assertEqual(self.test_rc.is_test_cert, False)
rp["server"] = "https://acme-staging.api.letsencrypt.org/directory"
rp["server"] = "https://acme-staging-v02.api.letsencrypt.org/directory"
self.assertEqual(self.test_rc.is_test_cert, True)
rp["server"] = "https://staging.someotherca.com/directory"
self.assertEqual(self.test_rc.is_test_cert, True)

View file

@ -61,7 +61,7 @@ chain_path = /home/ubuntu/letsencrypt/chain.pem
break_my_certs = False
standalone = True
manual = False
server = https://acme-staging.api.letsencrypt.org/directory
server = https://acme-staging-v02.api.letsencrypt.org/directory
standalone_supported_challenges = "tls-sni-01,http-01"
webroot = False
os_packages_only = False

View file

@ -604,7 +604,7 @@ def enforce_domain_sanity(domain):
def is_wildcard_domain(domain):
""""Is domain a wildcard domain?
:param damain: domain to check
:param domain: domain to check
:type domain: `bytes` or `str` or `unicode`
:returns: True if domain is a wildcard, otherwise, False

View file

@ -107,7 +107,7 @@ optional arguments:
case, and to know when to deprecate support for past
Python versions and flags. If you wish to hide this
information from the Let's Encrypt server, set this to
"". (default: CertbotACMEClient/0.21.1 (certbot;
"". (default: CertbotACMEClient/0.22.2 (certbot;
darwin 10.13.3) Authenticator/XXX Installer/YYY
(SUBCOMMAND; flags: FLAGS) Py/2.7.14). The flags
encoded in the user agent are: --duplicate, --force-
@ -200,7 +200,7 @@ testing:
--test-cert, --staging
Use the staging server to obtain or revoke test
(invalid) certificates; equivalent to --server https
://acme-staging.api.letsencrypt.org/directory
://acme-staging-v02.api.letsencrypt.org/directory
(default: False)
--debug Show tracebacks in case of errors, and allow certbot-
auto execution on experimental platforms (default:

View file

@ -24,7 +24,7 @@ running:
If you're on macOS, we recommend you skip the rest of this section and instead
run Certbot in Docker. You can find instructions for how to do this :ref:`here
<docker>`. If you're running on Linux, you can run the following commands to
<docker-dev>`. If you're running on Linux, you can run the following commands to
install dependencies and set up a virtual environment where you can run
Certbot. You will need to repeat this when Certbot's dependencies change or when
a new plugin is introduced.
@ -43,7 +43,7 @@ each shell where you're working:
.. code-block:: shell
source ./venv/bin/activate
export SERVER=https://acme-staging.api.letsencrypt.org/directory
export SERVER=https://acme-staging-v02.api.letsencrypt.org/directory
source tests/integration/_common.sh
After that, your shell will be using the virtual environment, your copy of
@ -377,7 +377,7 @@ This should generate documentation in the ``docs/_build/html``
directory.
.. _docker:
.. _docker-dev:
Running the client with Docker
==============================
@ -443,10 +443,10 @@ For squeeze you will need to:
FreeBSD
-------
Packages can be installed on FreeBSD using ``pkg``,
or any other port-management tool (``portupgrade``, ``portmanager``, etc.)
from the pre-built package or can be built and installed from ports.
Either way will ensure proper installation of all the dependencies required
Packages can be installed on FreeBSD using ``pkg``,
or any other port-management tool (``portupgrade``, ``portmanager``, etc.)
from the pre-built package or can be built and installed from ports.
Either way will ensure proper installation of all the dependencies required
for the package.
FreeBSD by default uses ``tcsh``. In order to activate virtualenv (see

View file

@ -94,6 +94,8 @@ Disable and remove the swapfile once the virtual environment is constructed::
user@webserver:~$ sudo swapoff /tmp/swapfile
user@webserver:~$ sudo rm /tmp/swapfile
.. _docker-user:
Running with Docker
-------------------
@ -115,13 +117,17 @@ these make much sense to you, you should definitely use the
certbot-auto_ method, which enables you to use installer plugins
that cover both of those hard topics.
If you're still not convinced and have decided to use this method,
from the server that the domain you're requesting a certficate for resolves
to, `install Docker`_, then issue the following command:
If you're still not convinced and have decided to use this method, from
the server that the domain you're requesting a certficate for resolves
to, `install Docker`_, then issue a command like the one found below. If
you are using Certbot with the :ref:`Standalone` plugin, you will need
to make the port it uses accessible from outside of the container by
including something like ``-p 80:80`` or ``-p 443:443`` on the command
line before ``certbot/certbot``.
.. code-block:: shell
sudo docker run -it --rm -p 443:443 -p 80:80 --name certbot \
sudo docker run -it --rm --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
certbot/certbot certonly
@ -131,6 +137,19 @@ Running Certbot with the ``certonly`` command will obtain a certificate and plac
within Docker, you must install the certificate manually according to the procedure
recommended by the provider of your webserver.
There are also Docker images for each of Certbot's DNS plugins available
at https://hub.docker.com/u/certbot which automate doing domain
validation over DNS for popular providers. To use one, just replace
``certbot/certbot`` in the command above with the name of the image you
want to use. For example, to use Certbot's plugin for Amazon Route 53,
you'd use ``certbot/dns-route53``. You may also need to add flags to
Certbot and/or mount additional directories to provide access to your
DNS API credentials as specified in the :ref:`DNS plugin documentation
<dns_plugins>`. If you would like to obtain a wildcard certificate from
Let's Encrypt's ACMEv2 server, you'll need to include ``--server
https://acme-v02.api.letsencrypt.org/directory`` on the command line as
well.
For more information about the layout
of the ``/etc/letsencrypt`` directory, see :ref:`where-certs`.
@ -187,10 +206,11 @@ want to use the Apache plugin, it has to be installed separately:
emerge -av app-crypt/certbot
emerge -av app-crypt/certbot-apache
When using the Apache plugin, you will run into a "cannot find a cert or key
directive" error if you're sporting the default Gentoo ``httpd.conf``.
You can fix this by commenting out two lines in ``/etc/apache2/httpd.conf``
as follows:
When using the Apache plugin, you will run into a "cannot find an
SSLCertificateFile directive" or "cannot find an SSLCertificateKeyFile
directive for certificate" error if you're sporting the default Gentoo
``httpd.conf``. You can fix this by commenting out two lines in
``/etc/apache2/httpd.conf`` as follows:
Change
@ -240,4 +260,3 @@ whole process is described in the :doc:`contributing`.
e.g. ``sudo python setup.py install``, ``sudo pip install``, ``sudo
./venv/bin/...``. These modes of operation might corrupt your operating
system and are **not supported** by the Certbot team!

View file

@ -45,7 +45,7 @@ a combination_ of distinct authenticator and installer plugins.
Plugin Auth Inst Notes Challenge types (and port)
=========== ==== ==== =============================================================== =============================
apache_ Y Y | Automates obtaining and installing a certificate with Apache tls-sni-01_ (443)
| 2.4 on Debian-based distributions with ``libaugeas0`` 1.0+.
| 2.4 on OSes with ``libaugeas0`` 1.0+.
webroot_ Y N | Obtains a certificate by writing to the webroot directory of http-01_ (80)
| an already running webserver.
nginx_ Y Y | Automates obtaining and installing a certificate with Nginx. tls-sni-01_ (443)
@ -54,12 +54,19 @@ standalone_ Y N | Uses a "standalone" webserver to obtain a certificate.
| Requires port 80 or 443 to be available. This is useful on tls-sni-01_ (443)
| systems with no webserver, or when direct integration with
| the local webserver is not supported or not desired.
|dns_plugs| Y N | This category of plugins automates obtaining a certificate by dns-01_ (53)
| modifying DNS records to prove you have control over a
| domain. Doing domain validation in this way is
| the only way to obtain wildcard certificates from Let's
| Encrypt.
manual_ Y N | Helps you obtain a certificate by giving you instructions to http-01_ (80),
| perform domain validation yourself. Additionally allows you dns-01_ (53) or
| to specify scripts to automate the validation task in a tls-sni-01_ (443)
| customized way.
=========== ==== ==== =============================================================== =============================
.. |dns_plugs| replace:: :ref:`DNS plugins <dns_plugins>`
Under the hood, plugins use one of several ACME protocol challenges_ to
prove you control a domain. The options are http-01_ (which uses port 80),
tls-sni-01_ (port 443) and dns-01_ (requiring configuration of a DNS server on
@ -80,7 +87,7 @@ Apache
The Apache plugin currently requires an OS with augeas version 1.0; currently `it
supports
<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/constants.py>`_
<https://github.com/certbot/certbot/blob/master/certbot-apache/certbot_apache/entrypoint.py>`_
modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin.
This automates both obtaining *and* installing certificates on an Apache
webserver. To specify this plugin on the command line, simply include
@ -141,6 +148,8 @@ the ``--nginx`` flag on the commandline.
certbot --nginx
.. _standalone:
Standalone
----------
@ -164,6 +173,33 @@ the Internet on the specified port using each requested domain name.
.. note:: The ``--standalone-supported-challenges`` option has been
deprecated since ``certbot`` version 0.9.0.
.. _dns_plugins:
DNS Plugins
-----------
If you'd like to obtain a wildcard certificate from Let's Encrypt or run
``certbot`` on a machine other than your target webserver, you can use one of
Certbot's DNS plugins.
These plugins are still in the process of being packaged
by many distributions and cannot currently be installed with ``certbot-auto``.
If, however, you are comfortable installing the certificates yourself,
you can run these plugins with :ref:`Docker <docker-user>`.
Once installed, you can find documentation on how to use each plugin at:
* `certbot-dns-cloudflare <https://certbot-dns-cloudflare.readthedocs.io>`_
* `certbot-dns-cloudxns <https://certbot-dns-cloudxns.readthedocs.io>`_
* `certbot-dns-digitalocean <https://certbot-dns-digitalocean.readthedocs.io>`_
* `certbot-dns-dnsimple <https://certbot-dns-dnsimple.readthedocs.io>`_
* `certbot-dns-dnsmadeeasy <https://certbot-dns-dnsmadeeasy.readthedocs.io>`_
* `certbot-dns-google <https://certbot-dns-google.readthedocs.io>`_
* `certbot-dns-luadns <https://certbot-dns-luadns.readthedocs.io>`_
* `certbot-dns-nsone <https://certbot-dns-nsone.readthedocs.io>`_
* `certbot-dns-rfc2136 <https://certbot-dns-rfc2136.readthedocs.io>`_
* `certbot-dns-route53 <https://certbot-dns-route53.readthedocs.io>`_
Manual
------
@ -516,6 +552,12 @@ can run on a regular basis, like every week or every day). In that case,
you are likely to want to use the ``-q`` or ``--quiet`` quiet flag to
silence all output except errors.
.. seealso:: Many of the certbot clients obtained through a
distribution come with automatic renewal out of the box,
such as Debian and Ubuntu versions installed through `apt`,
CentOS/RHEL 7 through EPEL, etc. See `Automated Renewals`_
for more details.
If you are manually renewing all of your certificates, the
``--force-renewal`` flag may be helpful; it causes the expiration time of
the certificate(s) to be ignored when considering renewal, and attempts to
@ -611,6 +653,31 @@ The following commands could be used to specify where these files are located::
sed -i 's,/etc/letsencrypt/live/example.com,/home/user/me/certbot,g' /etc/letsencrypt/renewal/example.com.conf
certbot update_symlinks
Automated Renewals
------------------
Many Linux distributions provide automated renewal when you use the
packages installed through their system package manager. The
following table is an *incomplete* list of distributions which do so,
as well as their methods for doing so.
If you are not sure whether or not your system has this already
automated, refer to your distribution's documentation, or check your
system's crontab (typically in `/etc/crontab/` and `/etc/cron.*/*` and
systemd timers (`systemctl list-timers`).
.. csv-table:: Distributions with Automated Renewal
:header: "Distribution Name", "Distribution Version", "Automation Method"
"CentOS", "EPEL 7", "systemd"
"Debian", "jessie", "cron, systemd"
"Debian", "stretch", "cron, systemd"
"Debian", "testing/sid", "cron, systemd"
"Fedora", "26", "systemd"
"Fedora", "27", "systemd"
"RHEL", "EPEL 7", "systemd"
"Ubuntu", "17.10", "cron, systemd"
"Ubuntu", "certbot PPA", "cron, systemd"
.. _where-certs:
@ -801,6 +868,27 @@ Example usage for DNS-01 (Cloudflare API v4) (for example purposes only, do not
.. _lock-files:
Changing the ACME Server
========================
By default, Certbot uses Let's Encrypt's initial production server at
https://acme-v01.api.letsencrypt.org/. You can tell Certbot to use a
different CA by providing ``--server`` on the command line or in a
:ref:`configuration file <config-file>` with the URL of the server's
ACME directory. For example, if you would like to use Let's Encrypt's
new ACMEv2 server, you would add ``--server
https://acme-v02.api.letsencrypt.org/directory`` to the command line.
Certbot will automatically select which version of the ACME protocol to
use based on the contents served at the provided URL.
If you use ``--server`` to specify an ACME CA that implements a newer
version of the spec, you may be able to obtain a certificate for a
wildcard domain. Some CAs (such as Let's Encrypt) require that domain
validation for wildcard domains must be done through modifications to
DNS records which means that the dns-01_ challenge type must be used. To
see a list of Certbot plugins that support this challenge type and how
to use them, see plugins_.
Lock Files
==========
@ -831,7 +919,7 @@ Certbot accepts a global configuration file that applies its options to all invo
of Certbot. Certificate specific configuration choices should be set in the ``.conf``
files that can be found in ``/etc/letsencrypt/renewal``.
By default no cli.ini file is created, after creating one
By default no cli.ini file is created, after creating one
it is possible to specify the location of this configuration file with
``certbot-auto --config cli.ini`` (or shorter ``-c cli.ini``). An
example configuration file is shown below:
@ -867,6 +955,12 @@ the oldest one to make room for new logs. The number of subsequent logs can be
changed by passing the desired number to the command line flag
``--max-log-backups``.
.. note:: Some distributions, including Debian and Ubuntu, disable
certbot's internal log rotation in favor of a more traditional
logrotate script. If you are using a distribution's packages and
want to alter the log rotation, check `/etc/logrotate.d/` for a
certbot rotation script.
.. _command-line:
Certbot command-line options

View file

@ -1,5 +1,5 @@
# Always use the staging/testing server - avoids rate limiting
server = https://acme-staging.api.letsencrypt.org/directory
server = https://acme-staging-v02.api.letsencrypt.org/directory
# This is an example configuration file for developers
config-dir = /tmp/le/conf

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.21.1"
LE_AUTO_VERSION="0.22.2"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
--no-bootstrap do not install OS dependencies
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
--install-only install certbot, upgrade if needed, and exit
-v, --verbose provide more output
-q, --quiet provide only update/error output;
implies --non-interactive
@ -60,6 +61,8 @@ for arg in "$@" ; do
DEBUG=1;;
--os-packages-only)
OS_PACKAGES_ONLY=1;;
--install-only)
INSTALL_ONLY=1;;
--no-self-upgrade)
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
@ -246,7 +249,7 @@ DeprecationBootstrap() {
fi
}
MIN_PYTHON_VERSION="2.6"
MIN_PYTHON_VERSION="2.7"
MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//')
# Sets LE_PYTHON to Python version string and PYVER to the first two
# digits of the python version
@ -1196,24 +1199,24 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.21.1 \
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
acme==0.21.1 \
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
certbot-apache==0.21.1 \
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
certbot-nginx==0.21.1 \
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
certbot==0.22.2 \
--hash=sha256:c8c63bdf0fed6258bdbc892454314ec37bcd1c35a7f62524a083d93ccdfc420d \
--hash=sha256:e6e3639293e78397f31f7d99e3c63aff82d91e2b0d50d146ee3c77f830464bef
acme==0.22.2 \
--hash=sha256:59a55244612ee305d2caa6bb4cddd400fb60ec841bf011ed29a2899832a682c2 \
--hash=sha256:0ecd0ea369f53d5bc744d6e72717f9af2e1ceb558d109dbd433148851027adb4
certbot-apache==0.22.2 \
--hash=sha256:b5340d4b9190358fde8eb6a5be0def37e32014b5142ee79ef5d2319ccbbde754 \
--hash=sha256:3cd26912bb5732d917ddf7aad2fe870090d4ece9a408b2c2de8e9723ec99c759
certbot-nginx==0.22.2 \
--hash=sha256:91feef0d879496835d355e82841f92e5ecb5abbf6f23ea0ee5bbb8f5a92b278a \
--hash=sha256:b10bf04c1a20cf878d5e0d1877deb0e0780bc31b0ffda08ce7199bbc39d0753b
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip 8
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
@ -1237,6 +1240,7 @@ anything goes wrong, it will exit with a non-zero status code.
from __future__ import print_function
from distutils.version import StrictVersion
from hashlib import sha256
from os import environ
from os.path import join
from pipes import quote
from shutil import rmtree
@ -1270,14 +1274,14 @@ except ImportError:
from urllib.parse import urlparse # 3.4
__version__ = 1, 3, 0
__version__ = 1, 5, 1
PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
# wheel has a conditional dependency on argparse:
maybe_argparse = (
[('https://pypi.python.org/packages/18/dd/'
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'argparse-1.4.0.tar.gz',
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
if version_info < (2, 7, 0) else [])
@ -1285,18 +1289,14 @@ maybe_argparse = (
PACKAGES = maybe_argparse + [
# Pip has no dependencies, as it vendors everything:
('https://pypi.python.org/packages/11/b6/'
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'pip-{0}.tar.gz'
.format(PIP_VERSION),
('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'pip-{0}.tar.gz'.format(PIP_VERSION),
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
# This version of setuptools has only optional dependencies:
('https://pypi.python.org/packages/69/65/'
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'setuptools-20.2.2.tar.gz',
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
('https://pypi.python.org/packages/c9/1d/'
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
'setuptools-29.0.1.tar.gz',
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'wheel-0.29.0.tar.gz',
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
]
@ -1317,12 +1317,13 @@ def hashed_download(url, temp, digest):
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
# authenticity has only privacy (not arbitrary code execution)
# implications, since we're checking hashes.
def opener():
def opener(using_https=True):
opener = build_opener(HTTPSHandler())
# Strip out HTTPHandler to prevent MITM spoof:
for handler in opener.handlers:
if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
if using_https:
# Strip out HTTPHandler to prevent MITM spoof:
for handler in opener.handlers:
if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
return opener
def read_chunks(response, chunk_size):
@ -1332,8 +1333,9 @@ def hashed_download(url, temp, digest):
break
yield chunk
response = opener().open(url)
path = join(temp, urlparse(url).path.split('/')[-1])
parsed_url = urlparse(url)
response = opener(using_https=parsed_url.scheme == 'https').open(url)
path = join(temp, parsed_url.path.split('/')[-1])
actual_hash = sha256()
with open(path, 'wb') as file:
for chunk in read_chunks(response, 4096):
@ -1346,6 +1348,24 @@ def hashed_download(url, temp, digest):
return path
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
SIMPLE = '/simple'
if env_var.endswith(SIMPLE):
return env_var[:-len(SIMPLE)]
else:
return env_var
else:
return DEFAULT_INDEX_BASE
def main():
pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1])
@ -1353,11 +1373,13 @@ def main():
if pip_version >= min_pip_version:
return 0
has_pip_cache = pip_version >= StrictVersion('6.0')
index_base = get_index_base()
temp = mkdtemp(prefix='pipstrap-')
try:
downloads = [hashed_download(url, temp, digest)
for url, digest in PACKAGES]
downloads = [hashed_download(index_base + '/packages/' + path,
temp,
digest)
for path, digest in PACKAGES]
check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings:
@ -1428,6 +1450,12 @@ UNLIKELY_EOF
say "Installation succeeded."
fi
if [ "$INSTALL_ONLY" = 1 ]; then
say "Certbot is installed."
exit 0
fi
"$VENV_BIN/letsencrypt" "$@"
else

View file

@ -11,7 +11,7 @@ RUN yum install -y python-pip sudo
COPY ./pieces/pipstrap.py /opt
RUN /opt/pipstrap.py
# Pin pytest version for increased stability
RUN pip install pytest==3.2.5
RUN pip install pytest==3.2.5 six==1.10.0
# Add an unprivileged user:
RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups wheel --uid 1000 lea

View file

@ -15,7 +15,7 @@ RUN apt-get update && \
COPY ./pieces/pipstrap.py /opt
RUN /opt/pipstrap.py
# Pin pytest version for increased stability
RUN pip install pytest==3.2.5
RUN pip install pytest==3.2.5 six==1.10.0
# Let that user sudo:
RUN sed -i.bkp -e \

View file

@ -19,7 +19,7 @@ RUN apt-get update && \
COPY ./pieces/pipstrap.py /opt
RUN /opt/pipstrap.py
# Pin pytest version for increased stability
RUN pip install pytest==3.2.5
RUN pip install pytest==3.2.5 six==1.10.0
RUN mkdir -p /home/lea/certbot

View file

@ -14,7 +14,7 @@ RUN apt-get update && \
COPY ./pieces/pipstrap.py /opt
RUN /opt/pipstrap.py
# Pin pytest version for increased stability
RUN pip install pytest==3.2.5
RUN pip install pytest==3.2.5 six==1.10.0
# Let that user sudo:
RUN sed -i.bkp -e \

View file

@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlpqMlYACgkQTRfJlc2X
dfKHfQgAnZQJ34jFoVqEodT0EjvkFKZif4V/zXTsVwTHn107BcLCpH/9gjANrSo3
JpvseH2q0odhOAZA4rZKH4Geh+5fsUl3Ew9YB28RXeyqEfCATUqPq6q+jAi55SLc
a064Ux5N7eOIh9gxvpDKBeSFD0eNB8IDtPQhUspr+WnoycawrJHNGawL8WIfrWY3
0ZPF981iPCWCdN3woDP9wHA2QtBClAk2pQ1aMgdkK9r/QLO+DY92xmT/Uu4ik2jR
zv+QplsQLftjD+bRar5R9jiCWV5phPqrOF3ypMiU0K5bsnrZfGBzBcoEyfKuB+UR
F/j/631OC6yLRasr+xcL1gc+SCryfA==
=tkZT
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlqwWJwACgkQTRfJlc2X
dfIzmwgAghmc3W63/qpCtJdezYeGLJdu03LvKoWYc7dTNYj2+0P5qmAAgCvKNY34
qYzXA1jfCOgILSzRNE5WY+rbgjcmxxsxH+luYm6Ik0909MaMQ0D3h+5cRFs/tTtd
5cX0gxL3RQQTBwpnwbAZibe7lhjs9pXBiob2ek67hVr+xEwem69BQMlOhtYJbOs1
osccoKc4NqaKbrfgOjjtMaL8YoRPO9vJHS9rRr6hxRZlPsmvusAHAiCbIrbX4XKE
CgxJFnuHK+amtfRoZg/xCqIK3Z94yZXPezywsri/YvDteOIs+DZ2qG/StfUrNYFX
WYfFFFyld0xwQtb4Oi9u4mx4sPg7lw==
=jZDE
-----END PGP SIGNATURE-----

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.22.0.dev0"
LE_AUTO_VERSION="0.23.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -1199,18 +1199,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.21.1 \
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
acme==0.21.1 \
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
certbot-apache==0.21.1 \
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
certbot-nginx==0.21.1 \
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
certbot==0.22.2 \
--hash=sha256:c8c63bdf0fed6258bdbc892454314ec37bcd1c35a7f62524a083d93ccdfc420d \
--hash=sha256:e6e3639293e78397f31f7d99e3c63aff82d91e2b0d50d146ee3c77f830464bef
acme==0.22.2 \
--hash=sha256:59a55244612ee305d2caa6bb4cddd400fb60ec841bf011ed29a2899832a682c2 \
--hash=sha256:0ecd0ea369f53d5bc744d6e72717f9af2e1ceb558d109dbd433148851027adb4
certbot-apache==0.22.2 \
--hash=sha256:b5340d4b9190358fde8eb6a5be0def37e32014b5142ee79ef5d2319ccbbde754 \
--hash=sha256:3cd26912bb5732d917ddf7aad2fe870090d4ece9a408b2c2de8e9723ec99c759
certbot-nginx==0.22.2 \
--hash=sha256:91feef0d879496835d355e82841f92e5ecb5abbf6f23ea0ee5bbb8f5a92b278a \
--hash=sha256:b10bf04c1a20cf878d5e0d1877deb0e0780bc31b0ffda08ce7199bbc39d0753b
UNLIKELY_EOF
# -------------------------------------------------------------------------

View file

@ -1,12 +1,12 @@
certbot==0.21.1 \
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
acme==0.21.1 \
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
certbot-apache==0.21.1 \
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
certbot-nginx==0.21.1 \
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
certbot==0.22.2 \
--hash=sha256:c8c63bdf0fed6258bdbc892454314ec37bcd1c35a7f62524a083d93ccdfc420d \
--hash=sha256:e6e3639293e78397f31f7d99e3c63aff82d91e2b0d50d146ee3c77f830464bef
acme==0.22.2 \
--hash=sha256:59a55244612ee305d2caa6bb4cddd400fb60ec841bf011ed29a2899832a682c2 \
--hash=sha256:0ecd0ea369f53d5bc744d6e72717f9af2e1ceb558d109dbd433148851027adb4
certbot-apache==0.22.2 \
--hash=sha256:b5340d4b9190358fde8eb6a5be0def37e32014b5142ee79ef5d2319ccbbde754 \
--hash=sha256:3cd26912bb5732d917ddf7aad2fe870090d4ece9a408b2c2de8e9723ec99c759
certbot-nginx==0.22.2 \
--hash=sha256:91feef0d879496835d355e82841f92e5ecb5abbf6f23ea0ee5bbb8f5a92b278a \
--hash=sha256:b10bf04c1a20cf878d5e0d1877deb0e0780bc31b0ffda08ce7199bbc39d0753b

View file

@ -2,6 +2,6 @@
Run these locally by saying... ::
./build.py && docker build -t lea . && docker run --rm -t -i lea
./build.py && docker build -t lea . -f Dockerfile.<distro> && docker run --rm -t -i lea
"""

View file

@ -18,6 +18,7 @@ from threading import Thread
from unittest import TestCase
from pytest import mark
from six.moves import xrange # pylint: disable=redefined-builtin
@mark.skip

View file

@ -12,7 +12,7 @@ botocore==1.7.41
cloudflare==1.5.1
coverage==4.4.2
decorator==4.1.2
dns-lexicon==2.1.14
dns-lexicon==2.2.1
dnspython==1.15.0
docutils==0.14
execnet==1.5.0

View file

@ -1,4 +1,4 @@
#!/bin/bash -e
#!/bin/sh -e
# pip installs packages using pinned package versions. If CERTBOT_OLDEST is set
# to 1, a combination of tools/oldest_constraints.txt,
# tools/dev_constraints.txt, and local-oldest-requirements.txt contained in the

View file

@ -14,7 +14,7 @@ def serve_forever(port=0):
"""
server = HTTPServer(('', port), SimpleHTTPRequestHandler)
print 'Serving HTTP on {0} port {1} ...'.format(*server.server_address)
print('Serving HTTP on {0} port {1} ...'.format(*server.server_address))
sys.stdout.flush()
server.serve_forever()

View file

@ -25,7 +25,7 @@ API Documentation
:glob:
api/**" > api.rst
sed -i -e "s| :caption: Contents:| :caption: Contents:\n\n.. toctree::\n :maxdepth: 1\n\n api\n\n.. automodule:: ${PROJECT//-/_}\n :members:|" index.rst
sed -i -e "s| :caption: Contents:| :caption: Contents:\n\n.. automodule:: ${PROJECT//-/_}\n :members:\n\n.. toctree::\n :maxdepth: 1\n\n api|" index.rst
echo "Suggested next steps:
* Add API docs to: $PROJECT/docs/api/