Compare commits
31 Commits
update-ser
...
fix-issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54616ccd2e | ||
|
|
726f3ce8b3 | ||
|
|
f40e04401f | ||
|
|
398bd4a2cd | ||
|
|
a024aaf59d | ||
|
|
261d063b10 | ||
|
|
a9e01ade4c | ||
|
|
5c7fc07ccf | ||
|
|
6dc8b66760 | ||
|
|
590ec375ec | ||
|
|
523cdc578d | ||
|
|
e0a5b1229f | ||
|
|
6253acf335 | ||
|
|
a708504d5b | ||
|
|
2d31598484 | ||
|
|
6b29d159a2 | ||
|
|
88ceaa38d5 | ||
|
|
e7db97df87 | ||
|
|
4a8e35289c | ||
|
|
58626c3197 | ||
|
|
56fb667e15 | ||
|
|
0153c04af3 | ||
|
|
db938dcc0e | ||
|
|
0e30621355 | ||
|
|
16b2539f72 | ||
|
|
b6afba0d64 | ||
|
|
b24d9dddc3 | ||
|
|
9996730fb1 | ||
|
|
2c502e6f8b | ||
|
|
f01aa1295f | ||
|
|
a26a78e84e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -38,6 +38,7 @@ tests/letstest/venv/
|
||||
|
||||
# pytest cache
|
||||
.cache
|
||||
.mypy_cache/
|
||||
|
||||
# docker files
|
||||
.docker
|
||||
|
||||
@@ -29,6 +29,8 @@ matrix:
|
||||
addons:
|
||||
- python: "2.7"
|
||||
env: TOXENV=lint
|
||||
- python: "3.5"
|
||||
env: TOXENV=mypy
|
||||
- python: "2.7"
|
||||
env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest'
|
||||
sudo: required
|
||||
|
||||
43
CHANGELOG.md
43
CHANGELOG.md
@@ -2,6 +2,49 @@
|
||||
|
||||
Certbot adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## 0.23.0 - 2018-04-04
|
||||
|
||||
### Added
|
||||
|
||||
* Support for OpenResty was added to the Nginx plugin.
|
||||
|
||||
### Changed
|
||||
|
||||
* The timestamps in Certbot's logfiles now use the system's local time zone
|
||||
rather than UTC.
|
||||
* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able
|
||||
to create and delete multiple TXT records on a single domain.
|
||||
* certbot-dns-google's test suite now works without an internet connection.
|
||||
|
||||
### Fixed
|
||||
|
||||
* Removed a small window that if during which an error occurred, Certbot
|
||||
wouldn't clean up performed challenges.
|
||||
* The parameters `default` and `ipv6only` are now removed from `listen`
|
||||
directives when creating a new server block in the Nginx plugin.
|
||||
* `server_name` directives enclosed in quotation marks in Nginx are now properly
|
||||
supported.
|
||||
* Resolved an issue preventing the Apache plugin from starting Apache when it's
|
||||
not currently running on RHEL and Gentoo based systems.
|
||||
|
||||
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
|
||||
* certbot-apache
|
||||
* certbot-dns-cloudxns
|
||||
* certbot-dns-dnsimple
|
||||
* certbot-dns-dnsmadeeasy
|
||||
* certbot-dns-google
|
||||
* certbot-dns-luadns
|
||||
* certbot-dns-nsone
|
||||
* certbot-dns-rfc2136
|
||||
* certbot-nginx
|
||||
|
||||
More details about these changes can be found on our GitHub repo:
|
||||
https://github.com/certbot/certbot/milestone/50?closed=1
|
||||
|
||||
## 0.22.2 - 2018-03-19
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -435,6 +435,7 @@ class Authorization(ResourceBody):
|
||||
# be absent'... then acme-spec gives example with 'expires'
|
||||
# present... That's confusing!
|
||||
expires = fields.RFC3339Field('expires', omitempty=True)
|
||||
wildcard = jose.Field('wildcard', omitempty=True)
|
||||
|
||||
@challenges.decoder
|
||||
def challenges(value): # pylint: disable=missing-docstring,no-self-argument
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
||||
@@ -323,7 +323,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# Returned objects are guaranteed to be ssl vhosts
|
||||
return self._choose_vhosts_wildcard(domain, create_if_no_ssl)
|
||||
else:
|
||||
return [self.choose_vhost(domain)]
|
||||
return [self.choose_vhost(domain, create_if_no_ssl)]
|
||||
|
||||
def _vhosts_for_wildcard(self, domain):
|
||||
"""
|
||||
@@ -475,20 +475,21 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
if chain_path is not None:
|
||||
self.save_notes += "\tSSLCertificateChainFile %s\n" % chain_path
|
||||
|
||||
def choose_vhost(self, target_name, temp=False):
|
||||
def choose_vhost(self, target_name, create_if_no_ssl=True):
|
||||
"""Chooses a virtual host based on the given domain name.
|
||||
|
||||
If there is no clear virtual host to be selected, the user is prompted
|
||||
with all available choices.
|
||||
|
||||
The returned vhost is guaranteed to have TLS enabled unless temp is
|
||||
True. If temp is True, there is no such guarantee and the result is
|
||||
not cached.
|
||||
The returned vhost is guaranteed to have TLS enabled unless
|
||||
create_if_no_ssl is set to False, in which case there is no such guarantee
|
||||
and the result is not cached.
|
||||
|
||||
:param str target_name: domain name
|
||||
:param bool temp: whether the vhost is only used temporarily
|
||||
:param bool create_if_no_ssl: If found VirtualHost doesn't have a HTTPS
|
||||
counterpart, should one get created
|
||||
|
||||
:returns: ssl vhost associated with name
|
||||
:returns: vhost associated with name
|
||||
:rtype: :class:`~certbot_apache.obj.VirtualHost`
|
||||
|
||||
:raises .errors.PluginError: If no vhost is available or chosen
|
||||
@@ -501,7 +502,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# Try to find a reasonable vhost
|
||||
vhost = self._find_best_vhost(target_name)
|
||||
if vhost is not None:
|
||||
if temp:
|
||||
if not create_if_no_ssl:
|
||||
return vhost
|
||||
if not vhost.ssl:
|
||||
vhost = self.make_vhost_ssl(vhost)
|
||||
@@ -510,7 +511,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
self.assoc[target_name] = vhost
|
||||
return vhost
|
||||
|
||||
return self._choose_vhost_from_list(target_name, temp)
|
||||
# Negate create_if_no_ssl value to indicate if we want a SSL vhost
|
||||
# to get created if a non-ssl vhost is selected.
|
||||
return self._choose_vhost_from_list(target_name, temp=not create_if_no_ssl)
|
||||
|
||||
def _choose_vhost_from_list(self, target_name, temp=False):
|
||||
# Select a vhost from a list
|
||||
@@ -1505,7 +1508,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
raise errors.PluginError(
|
||||
"Unsupported enhancement: {0}".format(enhancement))
|
||||
|
||||
vhosts = self.choose_vhosts(domain, create_if_no_ssl=False)
|
||||
matched_vhosts = self.choose_vhosts(domain, create_if_no_ssl=False)
|
||||
# We should be handling only SSL vhosts for enhancements
|
||||
vhosts = [vhost for vhost in matched_vhosts if vhost.ssl]
|
||||
|
||||
if not vhosts:
|
||||
msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a "
|
||||
"domain {0} for enabling enhancement \"{1}\". The requested "
|
||||
"enhancement was not configured.")
|
||||
msg_enhancement = enhancement
|
||||
if options:
|
||||
msg_enhancement += ": " + options
|
||||
msg = msg_tmpl.format(domain, msg_enhancement)
|
||||
logger.warning(msg)
|
||||
raise errors.PluginError(msg)
|
||||
try:
|
||||
for vhost in vhosts:
|
||||
func(vhost, options)
|
||||
@@ -2000,10 +2016,27 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
:raises .errors.MisconfigurationError: If reload fails
|
||||
|
||||
"""
|
||||
error = ""
|
||||
try:
|
||||
util.run_script(self.constant("restart_cmd"))
|
||||
except errors.SubprocessError as err:
|
||||
raise errors.MisconfigurationError(str(err))
|
||||
logger.info("Unable to restart apache using %s",
|
||||
self.constant("restart_cmd"))
|
||||
alt_restart = self.constant("restart_cmd_alt")
|
||||
if alt_restart:
|
||||
logger.debug("Trying alternative restart command: %s",
|
||||
alt_restart)
|
||||
# There is an alternative restart command available
|
||||
# This usually is "restart" verb while original is "graceful"
|
||||
try:
|
||||
util.run_script(self.constant(
|
||||
"restart_cmd_alt"))
|
||||
return
|
||||
except errors.SubprocessError as secerr:
|
||||
error = str(secerr)
|
||||
else:
|
||||
error = str(err)
|
||||
raise errors.MisconfigurationError(error)
|
||||
|
||||
def config_test(self): # pylint: disable=no-self-use
|
||||
"""Check the configuration of Apache for errors.
|
||||
|
||||
@@ -21,6 +21,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
|
||||
version_cmd=['apachectl', '-v'],
|
||||
apache_cmd="apachectl",
|
||||
restart_cmd=['apachectl', 'graceful'],
|
||||
restart_cmd_alt=['apachectl', 'restart'],
|
||||
conftest_cmd=['apachectl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
|
||||
@@ -21,6 +21,7 @@ class GentooConfigurator(configurator.ApacheConfigurator):
|
||||
version_cmd=['/usr/sbin/apache2', '-v'],
|
||||
apache_cmd="apache2ctl",
|
||||
restart_cmd=['apache2ctl', 'graceful'],
|
||||
restart_cmd_alt=['apache2ctl', 'restart'],
|
||||
conftest_cmd=['apache2ctl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
|
||||
@@ -4,6 +4,8 @@ import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot_apache import obj
|
||||
from certbot_apache import override_centos
|
||||
from certbot_apache.tests import util
|
||||
@@ -121,5 +123,17 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
|
||||
self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys())
|
||||
self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"])
|
||||
|
||||
@mock.patch("certbot_apache.configurator.util.run_script")
|
||||
def test_alt_restart_works(self, mock_run_script):
|
||||
mock_run_script.side_effect = [None, errors.SubprocessError, None]
|
||||
self.config.restart()
|
||||
self.assertEquals(mock_run_script.call_count, 3)
|
||||
|
||||
@mock.patch("certbot_apache.configurator.util.run_script")
|
||||
def test_alt_restart_errors(self, mock_run_script):
|
||||
mock_run_script.side_effect = [None,
|
||||
errors.SubprocessError,
|
||||
errors.SubprocessError]
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.restart)
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -246,7 +246,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
@mock.patch("certbot_apache.display_ops.select_vhost")
|
||||
def test_choose_vhost_select_vhost_with_temp(self, mock_select):
|
||||
mock_select.return_value = self.vh_truth[0]
|
||||
chosen_vhost = self.config.choose_vhost("none.com", temp=True)
|
||||
chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False)
|
||||
self.assertEqual(self.vh_truth[0], chosen_vhost)
|
||||
|
||||
@mock.patch("certbot_apache.display_ops.select_vhost")
|
||||
@@ -936,6 +936,22 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
errors.PluginError,
|
||||
self.config.enhance, "certbot.demo", "unknown_enhancement")
|
||||
|
||||
def test_enhance_no_ssl_vhost(self):
|
||||
with mock.patch("certbot_apache.configurator.logger.warning") as mock_log:
|
||||
self.assertRaises(errors.PluginError, self.config.enhance,
|
||||
"certbot.demo", "redirect")
|
||||
# Check that correct logger.warning was printed
|
||||
self.assertTrue("not able to find" in mock_log.call_args[0][0])
|
||||
self.assertTrue("\"redirect\"" in mock_log.call_args[0][0])
|
||||
|
||||
mock_log.reset_mock()
|
||||
|
||||
self.assertRaises(errors.PluginError, self.config.enhance,
|
||||
"certbot.demo", "ensure-http-header", "Test")
|
||||
# Check that correct logger.warning was printed
|
||||
self.assertTrue("not able to find" in mock_log.call_args[0][0])
|
||||
self.assertTrue("Test" in mock_log.call_args[0][0])
|
||||
|
||||
@mock.patch("certbot.util.exe_exists")
|
||||
def test_ocsp_stapling(self, mock_exe):
|
||||
self.config.parser.update_runtime_variables = mock.Mock()
|
||||
@@ -945,6 +961,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "staple-ocsp")
|
||||
|
||||
# Get the ssl vhost for certbot.demo
|
||||
@@ -971,6 +988,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_exe.return_value = True
|
||||
|
||||
# Checking the case with already enabled ocsp stapling configuration
|
||||
self.config.choose_vhost("ocspvhost.com")
|
||||
self.config.enhance("ocspvhost.com", "staple-ocsp")
|
||||
|
||||
# Get the ssl vhost for letsencrypt.demo
|
||||
@@ -995,6 +1013,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("socache_shmcb_module")
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
|
||||
self.assertRaises(errors.PluginError,
|
||||
self.config.enhance, "certbot.demo", "staple-ocsp")
|
||||
@@ -1020,6 +1039,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
|
||||
@@ -1039,7 +1059,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# skip the enable mod
|
||||
self.config.parser.modules.add("headers_module")
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
# This will create an ssl vhost for encryption-example.demo
|
||||
self.config.choose_vhost("encryption-example.demo")
|
||||
self.config.enhance("encryption-example.demo", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
|
||||
@@ -1058,6 +1079,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
@@ -1079,7 +1101,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# skip the enable mod
|
||||
self.config.parser.modules.add("headers_module")
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
# This will create an ssl vhost for encryption-example.demo
|
||||
self.config.choose_vhost("encryption-example.demo")
|
||||
self.config.enhance("encryption-example.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
|
||||
@@ -1097,6 +1120,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2))
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "redirect")
|
||||
|
||||
# These are not immediately available in find_dir even with save() and
|
||||
@@ -1147,6 +1171,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.save()
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "redirect")
|
||||
|
||||
# These are not immediately available in find_dir even with save() and
|
||||
@@ -1213,6 +1238,9 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
|
||||
# Creates ssl vhost for the domain
|
||||
self.config.choose_vhost("red.blue.purple.com")
|
||||
|
||||
self.config.enhance("red.blue.purple.com", "redirect")
|
||||
verify_no_redirect = ("certbot_apache.configurator."
|
||||
"ApacheConfigurator._verify_no_certbot_redirect")
|
||||
@@ -1224,7 +1252,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# Skip the enable mod
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
|
||||
|
||||
self.config.choose_vhost("red.blue.purple.com")
|
||||
self.config.enhance("red.blue.purple.com", "redirect")
|
||||
# Clear state about enabling redirect on this run
|
||||
# pylint: disable=protected-access
|
||||
@@ -1446,6 +1474,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
# pylint: disable=protected-access
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("headers_module")
|
||||
self.vh_truth[3].ssl = True
|
||||
self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]]
|
||||
self.config.enhance("*.certbot.demo", "ensure-http-header",
|
||||
"Upgrade-Insecure-Requests")
|
||||
@@ -1453,6 +1482,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator._choose_vhosts_wildcard")
|
||||
def test_enhance_wildcard_no_install(self, mock_choose):
|
||||
self.vh_truth[3].ssl = True
|
||||
mock_choose.return_value = [self.vh_truth[3]]
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.parser.modules.add("headers_module")
|
||||
|
||||
@@ -161,6 +161,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
self.config.parser.modules.add("mod_ssl.c")
|
||||
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
|
||||
mock_exe.return_value = True
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "staple-ocsp")
|
||||
self.assertTrue("socache_shmcb_module" in self.config.parser.modules)
|
||||
|
||||
@@ -172,6 +174,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
mock_exe.return_value = True
|
||||
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "ensure-http-header",
|
||||
"Strict-Transport-Security")
|
||||
self.assertTrue("headers_module" in self.config.parser.modules)
|
||||
@@ -183,6 +186,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
|
||||
mock_exe.return_value = True
|
||||
self.config.get_version = mock.Mock(return_value=(2, 2))
|
||||
# This will create an ssl vhost for certbot.demo
|
||||
self.config.choose_vhost("certbot.demo")
|
||||
self.config.enhance("certbot.demo", "redirect")
|
||||
self.assertTrue("rewrite_module" in self.config.parser.modules)
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot_apache import override_gentoo
|
||||
from certbot_apache import obj
|
||||
from certbot_apache.tests import util
|
||||
@@ -123,5 +125,11 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
|
||||
self.assertEquals(len(self.config.parser.modules), 4)
|
||||
self.assertTrue("mod_another.c" in self.config.parser.modules)
|
||||
|
||||
@mock.patch("certbot_apache.configurator.util.run_script")
|
||||
def test_alt_restart_works(self, mock_run_script):
|
||||
mock_run_script.side_effect = [None, errors.SubprocessError, None]
|
||||
self.config.restart()
|
||||
self.assertEquals(mock_run_script.call_count, 3)
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -16,30 +16,9 @@ from certbot_apache.tests import util
|
||||
NUM_ACHALLS = 3
|
||||
|
||||
|
||||
class ApacheHttp01TestMeta(type):
|
||||
"""Generates parmeterized tests for testing perform."""
|
||||
def __new__(mcs, name, bases, class_dict):
|
||||
|
||||
def _gen_test(num_achalls, minor_version):
|
||||
def _test(self):
|
||||
achalls = self.achalls[:num_achalls]
|
||||
vhosts = self.vhosts[:num_achalls]
|
||||
self.config.version = (2, minor_version)
|
||||
self.common_perform_test(achalls, vhosts)
|
||||
return _test
|
||||
|
||||
for i in range(1, NUM_ACHALLS + 1):
|
||||
for j in (2, 4):
|
||||
test_name = "test_perform_{0}_{1}".format(i, j)
|
||||
class_dict[test_name] = _gen_test(i, j)
|
||||
return type.__new__(mcs, name, bases, class_dict)
|
||||
|
||||
|
||||
class ApacheHttp01Test(util.ApacheTest):
|
||||
"""Test for certbot_apache.http_01.ApacheHttp01."""
|
||||
|
||||
__metaclass__ = ApacheHttp01TestMeta
|
||||
|
||||
def setUp(self, *args, **kwargs):
|
||||
super(ApacheHttp01Test, self).setUp(*args, **kwargs)
|
||||
|
||||
@@ -71,7 +50,7 @@ class ApacheHttp01Test(util.ApacheTest):
|
||||
self.assertFalse(self.http.perform())
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_enable_modules_22(self, mock_enmod):
|
||||
def test_enable_modules_apache_2_2(self, mock_enmod):
|
||||
self.config.version = (2, 2)
|
||||
self.config.parser.modules.remove("authz_host_module")
|
||||
self.config.parser.modules.remove("mod_authz_host.c")
|
||||
@@ -80,7 +59,7 @@ class ApacheHttp01Test(util.ApacheTest):
|
||||
self.assertEqual(enmod_calls[0][0][0], "authz_host")
|
||||
|
||||
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
|
||||
def test_enable_modules_24(self, mock_enmod):
|
||||
def test_enable_modules_apache_2_4(self, mock_enmod):
|
||||
self.config.parser.modules.remove("authz_core_module")
|
||||
self.config.parser.modules.remove("mod_authz_core.c")
|
||||
|
||||
@@ -137,6 +116,31 @@ class ApacheHttp01Test(util.ApacheTest):
|
||||
self.config.config.http01_port = 12345
|
||||
self.assertRaises(errors.PluginError, self.http.perform)
|
||||
|
||||
def test_perform_1_achall_apache_2_2(self):
|
||||
self.combinations_perform_test(num_achalls=1, minor_version=2)
|
||||
|
||||
def test_perform_1_achall_apache_2_4(self):
|
||||
self.combinations_perform_test(num_achalls=1, minor_version=4)
|
||||
|
||||
def test_perform_2_achall_apache_2_2(self):
|
||||
self.combinations_perform_test(num_achalls=2, minor_version=2)
|
||||
|
||||
def test_perform_2_achall_apache_2_4(self):
|
||||
self.combinations_perform_test(num_achalls=2, minor_version=4)
|
||||
|
||||
def test_perform_3_achall_apache_2_2(self):
|
||||
self.combinations_perform_test(num_achalls=3, minor_version=2)
|
||||
|
||||
def test_perform_3_achall_apache_2_4(self):
|
||||
self.combinations_perform_test(num_achalls=3, minor_version=4)
|
||||
|
||||
def combinations_perform_test(self, num_achalls, minor_version):
|
||||
"""Test perform with the given achall count and Apache version."""
|
||||
achalls = self.achalls[:num_achalls]
|
||||
vhosts = self.vhosts[:num_achalls]
|
||||
self.config.version = (2, minor_version)
|
||||
self.common_perform_test(achalls, vhosts)
|
||||
|
||||
def common_perform_test(self, achalls, vhosts):
|
||||
"""Tests perform with the given achalls."""
|
||||
challenge_dir = self.http.challenge_dir
|
||||
|
||||
@@ -123,7 +123,8 @@ class ApacheTlsSni01(common.TLSSNI01):
|
||||
self.configurator.config.tls_sni_01_port)))
|
||||
|
||||
try:
|
||||
vhost = self.configurator.choose_vhost(achall.domain, temp=True)
|
||||
vhost = self.configurator.choose_vhost(achall.domain,
|
||||
create_if_no_ssl=False)
|
||||
except (PluginError, MissingCommandlineFlag):
|
||||
# We couldn't find the virtualhost for this domain, possibly
|
||||
# because it's a new vhost that's not configured yet
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
26
certbot-auto
26
certbot-auto
@@ -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.2"
|
||||
LE_AUTO_VERSION="0.23.0"
|
||||
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.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
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -50,7 +50,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
|
||||
|
||||
|
||||
class DigitalOceanClientTest(unittest.TestCase):
|
||||
id = 1
|
||||
|
||||
id_num = 1
|
||||
record_prefix = "_acme-challenge"
|
||||
record_name = record_prefix + "." + DOMAIN
|
||||
record_content = "bar"
|
||||
@@ -70,7 +71,7 @@ class DigitalOceanClientTest(unittest.TestCase):
|
||||
|
||||
domain_mock = mock.MagicMock()
|
||||
domain_mock.name = DOMAIN
|
||||
domain_mock.create_new_domain_record.return_value = {'domain_record': {'id': self.id}}
|
||||
domain_mock.create_new_domain_record.return_value = {'domain_record': {'id': self.id_num}}
|
||||
|
||||
self.manager.get_all_domains.return_value = [wrong_domain_mock, domain_mock]
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
include LICENSE.txt
|
||||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include certbot_dns_google/testdata *
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -21,8 +21,9 @@ Credentials
|
||||
-----------
|
||||
|
||||
Use of this plugin requires a configuration file containing the target DNS
|
||||
server that supports RFC 2136 Dynamic Updates, the name of the TSIG key, the
|
||||
TSIG key secret itself and the algorithm used if it's different to HMAC-MD5.
|
||||
server and optional port that supports RFC 2136 Dynamic Updates, the name
|
||||
of the TSIG key, the TSIG key secret itself and the algorithm used if it's
|
||||
different to HMAC-MD5.
|
||||
|
||||
.. code-block:: ini
|
||||
:name: credentials.ini
|
||||
@@ -30,6 +31,8 @@ TSIG key secret itself and the algorithm used if it's different to HMAC-MD5.
|
||||
|
||||
# Target DNS server
|
||||
dns_rfc2136_server = 192.0.2.1
|
||||
# Target DNS port
|
||||
dns_rfc2136_port = 53
|
||||
# TSIG key name
|
||||
dns_rfc2136_name = keyname.
|
||||
# TSIG key secret
|
||||
|
||||
@@ -36,6 +36,8 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
'HMAC-SHA512': dns.tsig.HMAC_SHA512
|
||||
}
|
||||
|
||||
PORT = 53
|
||||
|
||||
description = 'Obtain certificates using a DNS TXT record (if you are using BIND for DNS).'
|
||||
ttl = 120
|
||||
|
||||
@@ -78,6 +80,7 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
|
||||
def _get_rfc2136_client(self):
|
||||
return _RFC2136Client(self.credentials.conf('server'),
|
||||
int(self.credentials.conf('port') or self.PORT),
|
||||
self.credentials.conf('name'),
|
||||
self.credentials.conf('secret'),
|
||||
self.ALGORITHMS.get(self.credentials.conf('algorithm'),
|
||||
@@ -88,8 +91,9 @@ class _RFC2136Client(object):
|
||||
"""
|
||||
Encapsulates all communication with the target DNS server.
|
||||
"""
|
||||
def __init__(self, server, key_name, key_secret, key_algorithm):
|
||||
def __init__(self, server, port, key_name, key_secret, key_algorithm):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.keyring = dns.tsigkeyring.from_text({
|
||||
key_name: key_secret
|
||||
})
|
||||
@@ -118,7 +122,7 @@ class _RFC2136Client(object):
|
||||
update.add(rel, record_ttl, dns.rdatatype.TXT, record_content)
|
||||
|
||||
try:
|
||||
response = dns.query.tcp(update, self.server)
|
||||
response = dns.query.tcp(update, self.server, port=self.port)
|
||||
except Exception as e:
|
||||
raise errors.PluginError('Encountered error adding TXT record: {0}'
|
||||
.format(e))
|
||||
@@ -153,7 +157,7 @@ class _RFC2136Client(object):
|
||||
update.delete(rel, dns.rdatatype.TXT, record_content)
|
||||
|
||||
try:
|
||||
response = dns.query.tcp(update, self.server)
|
||||
response = dns.query.tcp(update, self.server, port=self.port)
|
||||
except Exception as e:
|
||||
raise errors.PluginError('Encountered error deleting TXT record: {0}'
|
||||
.format(e))
|
||||
@@ -202,7 +206,7 @@ class _RFC2136Client(object):
|
||||
request.flags ^= dns.flags.RD
|
||||
|
||||
try:
|
||||
response = dns.query.udp(request, self.server)
|
||||
response = dns.query.udp(request, self.server, port=self.port)
|
||||
rcode = response.rcode()
|
||||
|
||||
# Authoritative Answer bit should be set
|
||||
|
||||
@@ -14,6 +14,7 @@ from certbot.plugins.dns_test_common import DOMAIN
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
SERVER = '192.0.2.1'
|
||||
PORT = 53
|
||||
NAME = 'a-tsig-key.'
|
||||
SECRET = 'SSB3b25kZXIgd2hvIHdpbGwgYm90aGVyIHRvIGRlY29kZSB0aGlzIHRleHQK'
|
||||
VALID_CONFIG = {"rfc2136_server": SERVER, "rfc2136_name": NAME, "rfc2136_secret": SECRET}
|
||||
@@ -74,7 +75,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client
|
||||
|
||||
self.rfc2136_client = _RFC2136Client(SERVER, NAME, SECRET, dns.tsig.HMAC_MD5)
|
||||
self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5)
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
def test_add_txt_record(self, query_mock):
|
||||
@@ -84,7 +85,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
||||
|
||||
self.rfc2136_client.add_txt_record("bar", "baz", 42)
|
||||
|
||||
query_mock.assert_called_with(mock.ANY, SERVER)
|
||||
query_mock.assert_called_with(mock.ANY, SERVER, port=PORT)
|
||||
self.assertTrue("bar. 42 IN TXT \"baz\"" in str(query_mock.call_args[0][0]))
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
@@ -117,7 +118,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
||||
|
||||
self.rfc2136_client.del_txt_record("bar", "baz")
|
||||
|
||||
query_mock.assert_called_with(mock.ANY, SERVER)
|
||||
query_mock.assert_called_with(mock.ANY, SERVER, port=PORT)
|
||||
self.assertTrue("bar. 0 NONE TXT \"baz\"" in str(query_mock.call_args[0][0]))
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
@@ -169,7 +170,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
result = self.rfc2136_client._query_soa(DOMAIN)
|
||||
|
||||
query_mock.assert_called_with(mock.ANY, SERVER)
|
||||
query_mock.assert_called_with(mock.ANY, SERVER, port=PORT)
|
||||
self.assertTrue(result == True)
|
||||
|
||||
@mock.patch("dns.query.udp")
|
||||
@@ -179,7 +180,7 @@ class RFC2136ClientTest(unittest.TestCase):
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
result = self.rfc2136_client._query_soa(DOMAIN)
|
||||
|
||||
query_mock.assert_called_with(mock.ANY, SERVER)
|
||||
query_mock.assert_called_with(mock.ANY, SERVER, port=PORT)
|
||||
self.assertTrue(result == False)
|
||||
|
||||
@mock.patch("dns.query.udp")
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
from distutils.core import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -4,7 +4,7 @@ from setuptools import setup
|
||||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.23.0.dev0'
|
||||
version = '0.24.0.dev0'
|
||||
|
||||
# Remember to update local-oldest-requirements.txt when changing the minimum
|
||||
# acme/certbot version.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Certbot client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '0.23.0.dev0'
|
||||
__version__ = '0.24.0.dev0'
|
||||
|
||||
@@ -189,7 +189,7 @@ class AuthHandler(object):
|
||||
return active_achalls
|
||||
|
||||
def _poll_challenges(self, aauthzrs, chall_update,
|
||||
best_effort, min_sleep=3, max_rounds=15):
|
||||
best_effort, min_sleep=3, max_rounds=30):
|
||||
"""Wait for all challenge results to be determined."""
|
||||
indices_to_check = set(chall_update.keys())
|
||||
comp_indices = set()
|
||||
|
||||
@@ -46,7 +46,7 @@ def rename_lineage(config):
|
||||
"""
|
||||
disp = zope.component.getUtility(interfaces.IDisplay)
|
||||
|
||||
certname = _get_certnames(config, "rename")[0]
|
||||
certname = get_certnames(config, "rename")[0]
|
||||
|
||||
new_certname = config.new_certname
|
||||
if not new_certname:
|
||||
@@ -88,7 +88,7 @@ def certificates(config):
|
||||
|
||||
def delete(config):
|
||||
"""Delete Certbot files associated with a certificate lineage."""
|
||||
certnames = _get_certnames(config, "delete", allow_multiple=True)
|
||||
certnames = get_certnames(config, "delete", allow_multiple=True)
|
||||
for certname in certnames:
|
||||
storage.delete_files(config, certname)
|
||||
disp = zope.component.getUtility(interfaces.IDisplay)
|
||||
@@ -288,11 +288,7 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False):
|
||||
cert.privkey))
|
||||
return "".join(certinfo)
|
||||
|
||||
###################
|
||||
# Private Helpers
|
||||
###################
|
||||
|
||||
def _get_certnames(config, verb, allow_multiple=False):
|
||||
def get_certnames(config, verb, allow_multiple=False, custom_prompt=None):
|
||||
"""Get certname from flag, interactively, or error out.
|
||||
"""
|
||||
certname = config.certname
|
||||
@@ -305,22 +301,32 @@ def _get_certnames(config, verb, allow_multiple=False):
|
||||
if not choices:
|
||||
raise errors.Error("No existing certificates found.")
|
||||
if allow_multiple:
|
||||
if not custom_prompt:
|
||||
prompt = "Which certificate(s) would you like to {0}?".format(verb)
|
||||
else:
|
||||
prompt = custom_prompt
|
||||
code, certnames = disp.checklist(
|
||||
"Which certificate(s) would you like to {0}?".format(verb),
|
||||
choices, cli_flag="--cert-name",
|
||||
force_interactive=True)
|
||||
prompt, choices, cli_flag="--cert-name", force_interactive=True)
|
||||
if code != display_util.OK:
|
||||
raise errors.Error("User ended interaction.")
|
||||
else:
|
||||
code, index = disp.menu("Which certificate would you like to {0}?".format(verb),
|
||||
choices, cli_flag="--cert-name",
|
||||
force_interactive=True)
|
||||
if not custom_prompt:
|
||||
prompt = "Which certificate would you like to {0}?".format(verb)
|
||||
else:
|
||||
prompt = custom_prompt
|
||||
|
||||
code, index = disp.menu(
|
||||
prompt, choices, cli_flag="--cert-name", force_interactive=True)
|
||||
|
||||
if code != display_util.OK or index not in range(0, len(choices)):
|
||||
raise errors.Error("User ended interaction.")
|
||||
certnames = [choices[index]]
|
||||
return certnames
|
||||
|
||||
###################
|
||||
# Private Helpers
|
||||
###################
|
||||
|
||||
def _report_lines(msgs):
|
||||
"""Format a results report for a category of single-line renewal outcomes"""
|
||||
return " " + "\n ".join(str(msg) for msg in msgs)
|
||||
|
||||
@@ -76,6 +76,7 @@ obtain, install, and renew certificates:
|
||||
(default) run Obtain & install a certificate in your current webserver
|
||||
certonly Obtain or renew a certificate, but do not install it
|
||||
renew Renew all previously obtained certificates that are near expiry
|
||||
enhance Add security enhancements to your existing configuration
|
||||
-d DOMAINS Comma-separated list of domains to obtain a certificate for
|
||||
|
||||
%s
|
||||
@@ -415,6 +416,12 @@ VERB_HELP = [
|
||||
os.path.join(flag_default("config_dir"), "live"))),
|
||||
"usage": "\n\n certbot update_symlinks [options]\n\n"
|
||||
}),
|
||||
("enhance", {
|
||||
"short": "Add security enhancements to your existing configuration",
|
||||
"opts": ("Helps to harden the TLS configration by adding security enhancements "
|
||||
"to already existing configuration."),
|
||||
"usage": "\n\n certbot enhance [options]\n\n"
|
||||
}),
|
||||
|
||||
]
|
||||
# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful
|
||||
@@ -449,6 +456,7 @@ class HelpfulArgumentParser(object):
|
||||
"update_symlinks": main.update_symlinks,
|
||||
"certificates": main.certificates,
|
||||
"delete": main.delete,
|
||||
"enhance": main.enhance,
|
||||
}
|
||||
|
||||
# Get notification function for printing
|
||||
@@ -883,21 +891,22 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
||||
"flag to 0 disables log rotation entirely, causing "
|
||||
"Certbot to always append to the same log file.")
|
||||
helpful.add(
|
||||
[None, "automation", "run", "certonly"], "-n", "--non-interactive", "--noninteractive",
|
||||
[None, "automation", "run", "certonly", "enhance"],
|
||||
"-n", "--non-interactive", "--noninteractive",
|
||||
dest="noninteractive_mode", action="store_true",
|
||||
default=flag_default("noninteractive_mode"),
|
||||
help="Run without ever asking for user input. This may require "
|
||||
"additional command line flags; the client will try to explain "
|
||||
"which ones are required if it finds one missing")
|
||||
helpful.add(
|
||||
[None, "register", "run", "certonly"],
|
||||
[None, "register", "run", "certonly", "enhance"],
|
||||
constants.FORCE_INTERACTIVE_FLAG, action="store_true",
|
||||
default=flag_default("force_interactive"),
|
||||
help="Force Certbot to be interactive even if it detects it's not "
|
||||
"being run in a terminal. This flag cannot be used with the "
|
||||
"renew subcommand.")
|
||||
helpful.add(
|
||||
[None, "run", "certonly", "certificates"],
|
||||
[None, "run", "certonly", "certificates", "enhance"],
|
||||
"-d", "--domains", "--domain", dest="domains",
|
||||
metavar="DOMAIN", action=_DomainsAction,
|
||||
default=flag_default("domains"),
|
||||
@@ -913,8 +922,8 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
||||
"name. In the case of a name collision it will append a number "
|
||||
"like 0001 to the file path name. (default: Ask)")
|
||||
helpful.add(
|
||||
[None, "run", "certonly", "manage", "delete", "certificates", "renew"],
|
||||
"--cert-name", dest="certname",
|
||||
[None, "run", "certonly", "manage", "delete", "certificates",
|
||||
"renew", "enhance"], "--cert-name", dest="certname",
|
||||
metavar="CERTNAME", default=flag_default("certname"),
|
||||
help="Certificate name to apply. This name is used by Certbot for housekeeping "
|
||||
"and in file paths; it doesn't affect the content of the certificate itself. "
|
||||
@@ -1085,7 +1094,8 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
||||
dest="must_staple", default=flag_default("must_staple"),
|
||||
help=config_help("must_staple"))
|
||||
helpful.add(
|
||||
"security", "--redirect", action="store_true", dest="redirect",
|
||||
["security", "enhance"],
|
||||
"--redirect", action="store_true", dest="redirect",
|
||||
default=flag_default("redirect"),
|
||||
help="Automatically redirect all HTTP traffic to HTTPS for the newly "
|
||||
"authenticated vhost. (default: Ask)")
|
||||
@@ -1095,7 +1105,8 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
||||
help="Do not automatically redirect all HTTP traffic to HTTPS for the newly "
|
||||
"authenticated vhost. (default: Ask)")
|
||||
helpful.add(
|
||||
"security", "--hsts", action="store_true", dest="hsts", default=flag_default("hsts"),
|
||||
["security", "enhance"],
|
||||
"--hsts", action="store_true", dest="hsts", default=flag_default("hsts"),
|
||||
help="Add the Strict-Transport-Security header to every HTTP response."
|
||||
" Forcing browser to always use SSL for the domain."
|
||||
" Defends against SSL Stripping.")
|
||||
@@ -1103,7 +1114,8 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
||||
"security", "--no-hsts", action="store_false", dest="hsts",
|
||||
default=flag_default("hsts"), help=argparse.SUPPRESS)
|
||||
helpful.add(
|
||||
"security", "--uir", action="store_true", dest="uir", default=flag_default("uir"),
|
||||
["security", "enhance"],
|
||||
"--uir", action="store_true", dest="uir", default=flag_default("uir"),
|
||||
help='Add the "Content-Security-Policy: upgrade-insecure-requests"'
|
||||
' header to every HTTP response. Forcing the browser to use'
|
||||
' https:// for every http:// resource.')
|
||||
|
||||
@@ -338,9 +338,10 @@ class Client(object):
|
||||
authenticator and installer, and then create a new renewable lineage
|
||||
containing it.
|
||||
|
||||
:param list domains: Domains to request.
|
||||
:param plugins: A PluginsFactory object.
|
||||
:param str certname: Name of new cert
|
||||
:param domains: domains to request a certificate for
|
||||
:type domains: `list` of `str`
|
||||
:param certname: requested name of lineage
|
||||
:type certname: `str` or `None`
|
||||
|
||||
:returns: A new :class:`certbot.storage.RenewableCert` instance
|
||||
referred to the enrolled cert lineage, False if the cert could not
|
||||
@@ -351,17 +352,11 @@ class Client(object):
|
||||
|
||||
if (self.config.config_dir != constants.CLI_DEFAULTS["config_dir"] or
|
||||
self.config.work_dir != constants.CLI_DEFAULTS["work_dir"]):
|
||||
logger.warning(
|
||||
logger.info(
|
||||
"Non-standard path(s), might not work with crontab installed "
|
||||
"by your operating system package manager")
|
||||
|
||||
if certname:
|
||||
new_name = certname
|
||||
elif util.is_wildcard_domain(domains[0]):
|
||||
# Don't make files and directories starting with *.
|
||||
new_name = domains[0][2:]
|
||||
else:
|
||||
new_name = domains[0]
|
||||
new_name = self._choose_lineagename(domains, certname)
|
||||
|
||||
if self.config.dry_run:
|
||||
logger.debug("Dry run: Skipping creating new lineage for %s",
|
||||
@@ -373,6 +368,26 @@ class Client(object):
|
||||
key.pem, chain,
|
||||
self.config)
|
||||
|
||||
def _choose_lineagename(self, domains, certname):
|
||||
"""Chooses a name for the new lineage.
|
||||
|
||||
:param domains: domains in certificate request
|
||||
:type domains: `list` of `str`
|
||||
:param certname: requested name of lineage
|
||||
:type certname: `str` or `None`
|
||||
|
||||
:returns: lineage name that should be used
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
if certname:
|
||||
return certname
|
||||
elif util.is_wildcard_domain(domains[0]):
|
||||
# Don't make files and directories starting with *.
|
||||
return domains[0][2:]
|
||||
else:
|
||||
return domains[0]
|
||||
|
||||
def save_certificate(self, cert_pem, chain_pem,
|
||||
cert_path, chain_path, fullchain_path):
|
||||
"""Saves the certificate received from the ACME server.
|
||||
@@ -451,7 +466,7 @@ class Client(object):
|
||||
# sites may have been enabled / final cleanup
|
||||
self.installer.restart()
|
||||
|
||||
def enhance_config(self, domains, chain_path):
|
||||
def enhance_config(self, domains, chain_path, ask_redirect=True):
|
||||
"""Enhance the configuration.
|
||||
|
||||
:param list domains: list of domains to configure
|
||||
@@ -478,8 +493,9 @@ class Client(object):
|
||||
for config_name, enhancement_name, option in enhancement_info:
|
||||
config_value = getattr(self.config, config_name)
|
||||
if enhancement_name in supported:
|
||||
if config_name == "redirect" and config_value is None:
|
||||
config_value = enhancements.ask(enhancement_name)
|
||||
if ask_redirect:
|
||||
if config_name == "redirect" and config_value is None:
|
||||
config_value = enhancements.ask(enhancement_name)
|
||||
if config_value:
|
||||
self.apply_enhancement(domains, enhancement_name, option)
|
||||
enhanced = True
|
||||
@@ -515,8 +531,12 @@ class Client(object):
|
||||
try:
|
||||
self.installer.enhance(dom, enhancement, options)
|
||||
except errors.PluginEnhancementAlreadyPresent:
|
||||
logger.warning("Enhancement %s was already set.",
|
||||
enhancement)
|
||||
if enhancement == "ensure-http-header":
|
||||
logger.warning("Enhancement %s was already set.",
|
||||
options)
|
||||
else:
|
||||
logger.warning("Enhancement %s was already set.",
|
||||
enhancement)
|
||||
except errors.PluginError:
|
||||
logger.warning("Unable to set enhancement %s for %s",
|
||||
enhancement, dom)
|
||||
|
||||
@@ -84,7 +84,7 @@ CLI_DEFAULTS = dict(
|
||||
config_dir="/etc/letsencrypt",
|
||||
work_dir="/var/lib/letsencrypt",
|
||||
logs_dir="/var/log/letsencrypt",
|
||||
server="https://acme-v02.api.letsencrypt.org/directory",
|
||||
server="https://acme-v01.api.letsencrypt.org/directory",
|
||||
|
||||
# Plugins parsers
|
||||
configurator=None,
|
||||
|
||||
@@ -13,7 +13,7 @@ import pyrfc3339
|
||||
import six
|
||||
import zope.component
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
from cryptography import x509
|
||||
from cryptography import x509 # type: ignore
|
||||
|
||||
from acme import crypto_util as acme_crypto_util
|
||||
|
||||
|
||||
@@ -86,13 +86,31 @@ def choose_account(accounts):
|
||||
else:
|
||||
return None
|
||||
|
||||
def choose_values(values, question=None):
|
||||
"""Display screen to let user pick one or multiple values from the provided
|
||||
list.
|
||||
|
||||
def choose_names(installer):
|
||||
:param list values: Values to select from
|
||||
|
||||
:returns: List of selected values
|
||||
:rtype: list
|
||||
"""
|
||||
code, items = z_util(interfaces.IDisplay).checklist(
|
||||
question, tags=values, force_interactive=True)
|
||||
if code == display_util.OK and items:
|
||||
return items
|
||||
else:
|
||||
return []
|
||||
|
||||
def choose_names(installer, question=None):
|
||||
"""Display screen to select domains to validate.
|
||||
|
||||
:param installer: An installer object
|
||||
:type installer: :class:`certbot.interfaces.IInstaller`
|
||||
|
||||
:param `str` question: Overriding dialog question to ask the user if asked
|
||||
to choose from domain names.
|
||||
|
||||
:returns: List of selected names
|
||||
:rtype: `list` of `str`
|
||||
|
||||
@@ -108,7 +126,7 @@ def choose_names(installer):
|
||||
return _choose_names_manually(
|
||||
"No names were found in your configuration files. ")
|
||||
|
||||
code, names = _filter_names(names)
|
||||
code, names = _filter_names(names, question)
|
||||
if code == display_util.OK and names:
|
||||
return names
|
||||
else:
|
||||
@@ -142,7 +160,7 @@ def _sort_names(FQDNs):
|
||||
return sorted(FQDNs, key=lambda fqdn: fqdn.split('.')[::-1][1:])
|
||||
|
||||
|
||||
def _filter_names(names):
|
||||
def _filter_names(names, override_question=None):
|
||||
"""Determine which names the user would like to select from a list.
|
||||
|
||||
:param list names: domain names
|
||||
@@ -155,10 +173,12 @@ def _filter_names(names):
|
||||
"""
|
||||
#Sort by domain first, and then by subdomain
|
||||
sorted_names = _sort_names(names)
|
||||
|
||||
if override_question:
|
||||
question = override_question
|
||||
else:
|
||||
question = "Which names would you like to activate HTTPS for?"
|
||||
code, names = z_util(interfaces.IDisplay).checklist(
|
||||
"Which names would you like to activate HTTPS for?",
|
||||
tags=sorted_names, cli_flag="--domains", force_interactive=True)
|
||||
question, tags=sorted_names, cli_flag="--domains", force_interactive=True)
|
||||
return code, [str(s) for s in names]
|
||||
|
||||
|
||||
|
||||
@@ -87,6 +87,10 @@ class NotSupportedError(PluginError):
|
||||
"""Certbot Plugin function not supported error."""
|
||||
|
||||
|
||||
class PluginStorageError(PluginError):
|
||||
"""Certbot Plugin Storage error."""
|
||||
|
||||
|
||||
class StandaloneBindError(Error):
|
||||
"""Standalone plugin bind error."""
|
||||
|
||||
|
||||
@@ -382,7 +382,7 @@ def _ask_user_to_confirm_new_names(config, new_domains, certname, old_domains):
|
||||
if not obj.yesno(msg, "Update cert", "Cancel", default=True):
|
||||
raise errors.ConfigurationError("Specified mismatched cert name and domains.")
|
||||
|
||||
def _find_domains_or_certname(config, installer):
|
||||
def _find_domains_or_certname(config, installer, question=None):
|
||||
"""Retrieve domains and certname from config or user input.
|
||||
|
||||
:param config: Configuration object
|
||||
@@ -391,6 +391,8 @@ def _find_domains_or_certname(config, installer):
|
||||
:param installer: Installer object
|
||||
:type installer: interfaces.IInstaller
|
||||
|
||||
:param `str` question: Overriding dialog question to ask the user if asked
|
||||
to choose from domain names.
|
||||
|
||||
:returns: Two-part tuple of domains and certname
|
||||
:rtype: `tuple` of list of `str` and `str`
|
||||
@@ -411,7 +413,7 @@ def _find_domains_or_certname(config, installer):
|
||||
# that certname might not have existed, or there was a problem.
|
||||
# try to get domains from the user.
|
||||
if not domains:
|
||||
domains = display_ops.choose_names(installer)
|
||||
domains = display_ops.choose_names(installer, question)
|
||||
|
||||
if not domains and not certname:
|
||||
raise errors.Error("Please specify --domains, or --installer that "
|
||||
@@ -859,6 +861,53 @@ def plugins_cmd(config, plugins):
|
||||
logger.debug("Prepared plugins: %s", available)
|
||||
notify(str(available))
|
||||
|
||||
def enhance(config, plugins):
|
||||
"""Add security enhancements to existing configuration
|
||||
|
||||
:param config: Configuration object
|
||||
:type config: interfaces.IConfig
|
||||
|
||||
:param plugins: List of plugins
|
||||
:type plugins: `list` of `str`
|
||||
|
||||
:returns: `None`
|
||||
:rtype: None
|
||||
|
||||
"""
|
||||
supported_enhancements = ["hsts", "redirect", "uir", "staple"]
|
||||
# Check that at least one enhancement was requested on command line
|
||||
if not any([getattr(config, enh) for enh in supported_enhancements]):
|
||||
msg = ("Please specify one or more enhancement types to configure. To list "
|
||||
"the available enhancement types, run:\n\n%s --help enhance\n")
|
||||
logger.warning(msg, sys.argv[0])
|
||||
raise errors.MisconfigurationError("No enhancements requested, exiting.")
|
||||
|
||||
try:
|
||||
installer, _ = plug_sel.choose_configurator_plugins(config, plugins, "enhance")
|
||||
except errors.PluginSelectionError as e:
|
||||
return str(e)
|
||||
|
||||
certname_question = ("Which certificate would you like to use to enhance "
|
||||
"your configuration?")
|
||||
config.certname = cert_manager.get_certnames(
|
||||
config, "enhance", allow_multiple=False,
|
||||
custom_prompt=certname_question)[0]
|
||||
cert_domains = cert_manager.domains_for_certname(config, config.certname)
|
||||
if config.noninteractive_mode:
|
||||
domains = cert_domains
|
||||
else:
|
||||
domain_question = ("Which domain names would you like to enable the "
|
||||
"selected enhancements for?")
|
||||
domains = display_ops.choose_values(cert_domains, domain_question)
|
||||
if not domains:
|
||||
raise errors.Error("User cancelled the domain selection. No domains "
|
||||
"defined, exiting.")
|
||||
if not config.chain_path:
|
||||
lineage = cert_manager.lineage_for_certname(config, config.certname)
|
||||
config.chain_path = lineage.chain_path
|
||||
le_client = _init_le_client(config, authenticator=None, installer=installer)
|
||||
le_client.enhance_config(domains, config.chain_path, ask_redirect=False)
|
||||
|
||||
|
||||
def rollback(config, plugins):
|
||||
"""Rollback server configuration changes made during install.
|
||||
|
||||
@@ -18,6 +18,8 @@ from certbot import interfaces
|
||||
from certbot import reverter
|
||||
from certbot import util
|
||||
|
||||
from certbot.plugins.storage import PluginStorage
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -99,7 +101,6 @@ class Plugin(object):
|
||||
def conf(self, var):
|
||||
"""Find a configuration value for variable ``var``."""
|
||||
return getattr(self.config, self.dest(var))
|
||||
# other
|
||||
|
||||
|
||||
class Installer(Plugin):
|
||||
@@ -110,6 +111,7 @@ class Installer(Plugin):
|
||||
"""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Installer, self).__init__(*args, **kwargs)
|
||||
self.storage = PluginStorage(self.config, self.name)
|
||||
self.reverter = reverter.Reverter(self.config)
|
||||
|
||||
def add_to_checkpoint(self, save_files, save_notes, temporary=False):
|
||||
|
||||
@@ -147,6 +147,7 @@ def record_chosen_plugins(config, plugins, auth, inst):
|
||||
|
||||
|
||||
def choose_configurator_plugins(config, plugins, verb):
|
||||
# pylint: disable=too-many-branches
|
||||
"""
|
||||
Figure out which configurator we're going to use, modifies
|
||||
config.authenticator and config.installer strings to reflect that choice if
|
||||
@@ -159,6 +160,11 @@ def choose_configurator_plugins(config, plugins, verb):
|
||||
"""
|
||||
|
||||
req_auth, req_inst = cli_plugin_requests(config)
|
||||
installer_question = None
|
||||
|
||||
if verb == "enhance":
|
||||
installer_question = ("Which installer would you like to use to "
|
||||
"configure the selected enhancements?")
|
||||
|
||||
# Which plugins do we need?
|
||||
if verb == "run":
|
||||
@@ -176,11 +182,11 @@ def choose_configurator_plugins(config, plugins, verb):
|
||||
need_inst = need_auth = False
|
||||
if verb == "certonly":
|
||||
need_auth = True
|
||||
if verb == "install":
|
||||
if verb == "install" or verb == "enhance":
|
||||
need_inst = True
|
||||
if config.authenticator:
|
||||
logger.warning("Specifying an authenticator doesn't make sense in install mode")
|
||||
|
||||
logger.warning("Specifying an authenticator doesn't make sense when "
|
||||
"running Certbot with verb \"%s\"", verb)
|
||||
# Try to meet the user's request and/or ask them to pick plugins
|
||||
authenticator = installer = None
|
||||
if verb == "run" and req_auth == req_inst:
|
||||
@@ -189,7 +195,7 @@ def choose_configurator_plugins(config, plugins, verb):
|
||||
authenticator = installer = pick_configurator(config, req_inst, plugins)
|
||||
else:
|
||||
if need_inst or req_inst:
|
||||
installer = pick_installer(config, req_inst, plugins)
|
||||
installer = pick_installer(config, req_inst, plugins, installer_question)
|
||||
if need_auth:
|
||||
authenticator = pick_authenticator(config, req_auth, plugins)
|
||||
logger.debug("Selected authenticator %s and installer %s", authenticator, installer)
|
||||
|
||||
117
certbot/plugins/storage.py
Normal file
117
certbot/plugins/storage.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""Plugin storage class."""
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
|
||||
from certbot import errors
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class PluginStorage(object):
|
||||
"""Class implementing storage functionality for plugins"""
|
||||
|
||||
def __init__(self, config, classkey):
|
||||
"""Initializes PluginStorage object storing required configuration
|
||||
options.
|
||||
|
||||
:param .configuration.NamespaceConfig config: Configuration object
|
||||
:param str classkey: class name to use as root key in storage file
|
||||
|
||||
"""
|
||||
|
||||
self._config = config
|
||||
self._classkey = classkey
|
||||
self._initialized = False
|
||||
self._data = None
|
||||
self._storagepath = None
|
||||
|
||||
def _initialize_storage(self):
|
||||
"""Initializes PluginStorage data and reads current state from the disk
|
||||
if the storage json exists."""
|
||||
|
||||
self._storagepath = os.path.join(self._config.config_dir, ".pluginstorage.json")
|
||||
self._load()
|
||||
self._initialized = True
|
||||
|
||||
def _load(self):
|
||||
"""Reads PluginStorage content from the disk to a dict structure
|
||||
|
||||
:raises .errors.PluginStorageError: when unable to open or read the file
|
||||
"""
|
||||
data = dict()
|
||||
filedata = ""
|
||||
try:
|
||||
with open(self._storagepath, 'r') as fh:
|
||||
filedata = fh.read()
|
||||
except IOError as e:
|
||||
errmsg = "Could not read PluginStorage data file: {0} : {1}".format(
|
||||
self._storagepath, str(e))
|
||||
if os.path.isfile(self._storagepath):
|
||||
# Only error out if file exists, but cannot be read
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
try:
|
||||
data = json.loads(filedata)
|
||||
except ValueError:
|
||||
if not filedata:
|
||||
logger.debug("Plugin storage file %s was empty, no values loaded",
|
||||
self._storagepath)
|
||||
else:
|
||||
errmsg = "PluginStorage file {0} is corrupted.".format(
|
||||
self._storagepath)
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
self._data = data
|
||||
|
||||
def save(self):
|
||||
"""Saves PluginStorage content to disk
|
||||
|
||||
:raises .errors.PluginStorageError: when unable to serialize the data
|
||||
or write it to the filesystem
|
||||
"""
|
||||
if not self._initialized:
|
||||
errmsg = "Unable to save, no values have been added to PluginStorage."
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
|
||||
try:
|
||||
serialized = json.dumps(self._data)
|
||||
except TypeError as e:
|
||||
errmsg = "Could not serialize PluginStorage data: {0}".format(
|
||||
str(e))
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
try:
|
||||
with os.fdopen(os.open(self._storagepath,
|
||||
os.O_WRONLY | os.O_CREAT, 0o600), 'w') as fh:
|
||||
fh.write(serialized)
|
||||
except IOError as e:
|
||||
errmsg = "Could not write PluginStorage data to file {0} : {1}".format(
|
||||
self._storagepath, str(e))
|
||||
logger.error(errmsg)
|
||||
raise errors.PluginStorageError(errmsg)
|
||||
|
||||
def put(self, key, value):
|
||||
"""Put configuration value to PluginStorage
|
||||
|
||||
:param str key: Key to store the value to
|
||||
:param value: Data to store
|
||||
"""
|
||||
if not self._initialized:
|
||||
self._initialize_storage()
|
||||
|
||||
if not self._classkey in self._data.keys():
|
||||
self._data[self._classkey] = dict()
|
||||
self._data[self._classkey][key] = value
|
||||
|
||||
def fetch(self, key):
|
||||
"""Get configuration value from PluginStorage
|
||||
|
||||
:param str key: Key to get value from the storage
|
||||
|
||||
:raises KeyError: If the key doesn't exist in the storage
|
||||
"""
|
||||
if not self._initialized:
|
||||
self._initialize_storage()
|
||||
|
||||
return self._data[self._classkey][key]
|
||||
117
certbot/plugins/storage_test.py
Normal file
117
certbot/plugins/storage_test.py
Normal file
@@ -0,0 +1,117 @@
|
||||
"""Tests for certbot.plugins.storage.PluginStorage"""
|
||||
import json
|
||||
import mock
|
||||
import os
|
||||
import unittest
|
||||
|
||||
from certbot import errors
|
||||
|
||||
from certbot.plugins import common
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
class PluginStorageTest(test_util.ConfigTestCase):
|
||||
"""Test for certbot.plugins.storage.PluginStorage"""
|
||||
|
||||
def setUp(self):
|
||||
super(PluginStorageTest, self).setUp()
|
||||
self.plugin_cls = common.Installer
|
||||
os.mkdir(self.config.config_dir)
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
self.plugin = self.plugin_cls(config=self.config, name="mockplugin")
|
||||
|
||||
def test_load_errors_cant_read(self):
|
||||
with open(os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json"), "w") as fh:
|
||||
fh.write("dummy")
|
||||
# When unable to read file that exists
|
||||
mock_open = mock.mock_open()
|
||||
mock_open.side_effect = IOError
|
||||
self.plugin.storage.storagepath = os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json")
|
||||
with mock.patch("six.moves.builtins.open", mock_open):
|
||||
with mock.patch('os.path.isfile', return_value=True):
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin.storage._load) # pylint: disable=protected-access
|
||||
|
||||
def test_load_errors_empty(self):
|
||||
with open(os.path.join(self.config.config_dir, ".pluginstorage.json"), "w") as fh:
|
||||
fh.write('')
|
||||
with mock.patch("certbot.plugins.storage.logger.debug") as mock_log:
|
||||
# Should not error out but write a debug log line instead
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
nocontent = self.plugin_cls(self.config, "mockplugin")
|
||||
self.assertRaises(KeyError,
|
||||
nocontent.storage.fetch, "value")
|
||||
self.assertTrue(mock_log.called)
|
||||
self.assertTrue("no values loaded" in mock_log.call_args[0][0])
|
||||
|
||||
def test_load_errors_corrupted(self):
|
||||
with open(os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json"), "w") as fh:
|
||||
fh.write('invalid json')
|
||||
with mock.patch("certbot.plugins.storage.logger.error") as mock_log:
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
corrupted = self.plugin_cls(self.config, "mockplugin")
|
||||
self.assertRaises(errors.PluginError,
|
||||
corrupted.storage.fetch,
|
||||
"value")
|
||||
self.assertTrue("is corrupted" in mock_log.call_args[0][0])
|
||||
|
||||
def test_save_errors_cant_serialize(self):
|
||||
with mock.patch("certbot.plugins.storage.logger.error") as mock_log:
|
||||
# Set data as something that can't be serialized
|
||||
self.plugin.storage._initialized = True # pylint: disable=protected-access
|
||||
self.plugin.storage.storagepath = "/tmp/whatever"
|
||||
self.plugin.storage._data = self.plugin_cls # pylint: disable=protected-access
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin.storage.save)
|
||||
self.assertTrue("Could not serialize" in mock_log.call_args[0][0])
|
||||
|
||||
def test_save_errors_unable_to_write_file(self):
|
||||
mock_open = mock.mock_open()
|
||||
mock_open.side_effect = IOError
|
||||
with mock.patch("os.open", mock_open):
|
||||
with mock.patch("certbot.plugins.storage.logger.error") as mock_log:
|
||||
self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access
|
||||
self.plugin.storage._initialized = True # pylint: disable=protected-access
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin.storage.save)
|
||||
self.assertTrue("Could not write" in mock_log.call_args[0][0])
|
||||
|
||||
def test_save_uninitialized(self):
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
self.assertRaises(errors.PluginStorageError,
|
||||
self.plugin_cls(self.config, "x").storage.save)
|
||||
|
||||
def test_namespace_isolation(self):
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
plugin1 = self.plugin_cls(self.config, "first")
|
||||
plugin2 = self.plugin_cls(self.config, "second")
|
||||
plugin1.storage.put("first_key", "first_value")
|
||||
self.assertRaises(KeyError,
|
||||
plugin2.storage.fetch, "first_key")
|
||||
self.assertRaises(KeyError,
|
||||
plugin2.storage.fetch, "first")
|
||||
self.assertEqual(plugin1.storage.fetch("first_key"), "first_value")
|
||||
|
||||
|
||||
def test_saved_state(self):
|
||||
self.plugin.storage.put("testkey", "testvalue")
|
||||
# Write to disk
|
||||
self.plugin.storage.save()
|
||||
with mock.patch("certbot.reverter.util"):
|
||||
another = self.plugin_cls(self.config, "mockplugin")
|
||||
self.assertEqual(another.storage.fetch("testkey"), "testvalue")
|
||||
|
||||
with open(os.path.join(self.config.config_dir,
|
||||
".pluginstorage.json"), 'r') as fh:
|
||||
psdata = fh.read()
|
||||
psjson = json.loads(psdata)
|
||||
self.assertTrue("mockplugin" in psjson.keys())
|
||||
self.assertEqual(len(psjson), 1)
|
||||
self.assertEqual(psjson["mockplugin"]["testkey"], "testvalue")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
@@ -1053,6 +1053,9 @@ class RenewableCert(object):
|
||||
"`cert.pem` : will break many server configurations, and "
|
||||
"should not be used\n"
|
||||
" without reading further documentation (see link below).\n\n"
|
||||
"WARNING: DO NOT MOVE THESE FILES!\n"
|
||||
" Certbot expects these files to remain in this location in order\n"
|
||||
" to function properly!\n\n"
|
||||
"We recommend not moving these files. For more information, see the Certbot\n"
|
||||
"User Guide at https://certbot.eff.org/docs/using.html#where-are-my-"
|
||||
"certificates.\n")
|
||||
|
||||
@@ -216,11 +216,12 @@ class CertificatesTest(BaseCertManagerTest):
|
||||
cert.is_test_cert = False
|
||||
parsed_certs = [cert]
|
||||
|
||||
mock_config = mock.MagicMock(certname=None, lineagename=None)
|
||||
# pylint: disable=protected-access
|
||||
|
||||
# pylint: disable=protected-access
|
||||
get_report = lambda: cert_manager._report_human_readable(mock_config, parsed_certs)
|
||||
|
||||
mock_config = mock.MagicMock(certname=None, lineagename=None)
|
||||
# pylint: disable=protected-access
|
||||
out = get_report()
|
||||
self.assertTrue("INVALID: EXPIRED" in out)
|
||||
|
||||
@@ -568,5 +569,103 @@ class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest):
|
||||
self.assertRaises(errors.OverlappingMatchFound, self._call, self.config, None, None, None)
|
||||
|
||||
|
||||
class GetCertnameTest(unittest.TestCase):
|
||||
"""Tests for certbot.cert_manager."""
|
||||
|
||||
def setUp(self):
|
||||
self.get_utility_patch = test_util.patch_get_utility()
|
||||
self.mock_get_utility = self.get_utility_patch.start()
|
||||
self.config = mock.MagicMock()
|
||||
self.config.certname = None
|
||||
|
||||
def tearDown(self):
|
||||
self.get_utility_patch.stop()
|
||||
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.lineagename_for_filename')
|
||||
def test_get_certnames(self, mock_name, mock_files):
|
||||
mock_files.return_value = ['example.com.conf']
|
||||
mock_name.return_value = 'example.com'
|
||||
from certbot import cert_manager
|
||||
prompt = "Which certificate would you"
|
||||
self.mock_get_utility().menu.return_value = (display_util.OK, 0)
|
||||
self.assertEquals(
|
||||
cert_manager.get_certnames(
|
||||
self.config, "verb", allow_multiple=False), ['example.com'])
|
||||
self.assertTrue(
|
||||
prompt in self.mock_get_utility().menu.call_args[0][0])
|
||||
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.lineagename_for_filename')
|
||||
def test_get_certnames_custom_prompt(self, mock_name, mock_files):
|
||||
mock_files.return_value = ['example.com.conf']
|
||||
mock_name.return_value = 'example.com'
|
||||
from certbot import cert_manager
|
||||
prompt = "custom prompt"
|
||||
self.mock_get_utility().menu.return_value = (display_util.OK, 0)
|
||||
self.assertEquals(
|
||||
cert_manager.get_certnames(
|
||||
self.config, "verb", allow_multiple=False, custom_prompt=prompt),
|
||||
['example.com'])
|
||||
self.assertEquals(self.mock_get_utility().menu.call_args[0][0],
|
||||
prompt)
|
||||
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.lineagename_for_filename')
|
||||
def test_get_certnames_user_abort(self, mock_name, mock_files):
|
||||
mock_files.return_value = ['example.com.conf']
|
||||
mock_name.return_value = 'example.com'
|
||||
from certbot import cert_manager
|
||||
self.mock_get_utility().menu.return_value = (display_util.CANCEL, 0)
|
||||
self.assertRaises(
|
||||
errors.Error,
|
||||
cert_manager.get_certnames,
|
||||
self.config, "erroring_anyway", allow_multiple=False)
|
||||
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.lineagename_for_filename')
|
||||
def test_get_certnames_allow_multiple(self, mock_name, mock_files):
|
||||
mock_files.return_value = ['example.com.conf']
|
||||
mock_name.return_value = 'example.com'
|
||||
from certbot import cert_manager
|
||||
prompt = "Which certificate(s) would you"
|
||||
self.mock_get_utility().checklist.return_value = (display_util.OK,
|
||||
['example.com'])
|
||||
self.assertEquals(
|
||||
cert_manager.get_certnames(
|
||||
self.config, "verb", allow_multiple=True), ['example.com'])
|
||||
self.assertTrue(
|
||||
prompt in self.mock_get_utility().checklist.call_args[0][0])
|
||||
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.lineagename_for_filename')
|
||||
def test_get_certnames_allow_multiple_custom_prompt(self, mock_name, mock_files):
|
||||
mock_files.return_value = ['example.com.conf']
|
||||
mock_name.return_value = 'example.com'
|
||||
from certbot import cert_manager
|
||||
prompt = "custom prompt"
|
||||
self.mock_get_utility().checklist.return_value = (display_util.OK,
|
||||
['example.com'])
|
||||
self.assertEquals(
|
||||
cert_manager.get_certnames(
|
||||
self.config, "verb", allow_multiple=True, custom_prompt=prompt),
|
||||
['example.com'])
|
||||
self.assertEquals(
|
||||
self.mock_get_utility().checklist.call_args[0][0],
|
||||
prompt)
|
||||
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.lineagename_for_filename')
|
||||
def test_get_certnames_allow_multiple_user_abort(self, mock_name, mock_files):
|
||||
mock_files.return_value = ['example.com.conf']
|
||||
mock_name.return_value = 'example.com'
|
||||
from certbot import cert_manager
|
||||
self.mock_get_utility().checklist.return_value = (display_util.CANCEL, [])
|
||||
self.assertRaises(
|
||||
errors.Error,
|
||||
cert_manager.get_certnames,
|
||||
self.config, "erroring_anyway", allow_multiple=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -433,6 +433,22 @@ class EnhanceConfigTest(ClientTestCommon):
|
||||
self.client.installer.enhance.assert_not_called()
|
||||
mock_enhancements.ask.assert_not_called()
|
||||
|
||||
@mock.patch("certbot.client.logger")
|
||||
def test_already_exists_header(self, mock_log):
|
||||
self.config.hsts = True
|
||||
self._test_with_already_existing()
|
||||
self.assertTrue(mock_log.warning.called)
|
||||
self.assertEquals(mock_log.warning.call_args[0][1],
|
||||
'Strict-Transport-Security')
|
||||
|
||||
@mock.patch("certbot.client.logger")
|
||||
def test_already_exists_redirect(self, mock_log):
|
||||
self.config.redirect = True
|
||||
self._test_with_already_existing()
|
||||
self.assertTrue(mock_log.warning.called)
|
||||
self.assertEquals(mock_log.warning.call_args[0][1],
|
||||
'redirect')
|
||||
|
||||
def test_no_ask_hsts(self):
|
||||
self.config.hsts = True
|
||||
self._test_with_all_supported()
|
||||
@@ -508,6 +524,13 @@ class EnhanceConfigTest(ClientTestCommon):
|
||||
self.assertEqual(self.client.installer.save.call_count, 1)
|
||||
self.assertEqual(self.client.installer.restart.call_count, 1)
|
||||
|
||||
def _test_with_already_existing(self):
|
||||
self.client.installer = mock.MagicMock()
|
||||
self.client.installer.supported_enhancements.return_value = [
|
||||
"ensure-http-header", "redirect", "staple-ocsp"]
|
||||
self.client.installer.enhance.side_effect = errors.PluginEnhancementAlreadyPresent()
|
||||
self.client.enhance_config([self.domain], None)
|
||||
|
||||
|
||||
class RollbackTest(unittest.TestCase):
|
||||
"""Tests for certbot.client.rollback."""
|
||||
|
||||
@@ -207,9 +207,9 @@ class ChooseNamesTest(unittest.TestCase):
|
||||
self.mock_install = mock.MagicMock()
|
||||
|
||||
@classmethod
|
||||
def _call(cls, installer):
|
||||
def _call(cls, installer, question=None):
|
||||
from certbot.display.ops import choose_names
|
||||
return choose_names(installer)
|
||||
return choose_names(installer, question)
|
||||
|
||||
@mock.patch("certbot.display.ops._choose_names_manually")
|
||||
def test_no_installer(self, mock_manual):
|
||||
@@ -281,6 +281,15 @@ class ChooseNamesTest(unittest.TestCase):
|
||||
self.assertEqual(names, ["example.com"])
|
||||
self.assertEqual(mock_util().checklist.call_count, 1)
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_filter_namees_override_question(self, mock_util):
|
||||
self.mock_install.get_all_names.return_value = set(["example.com"])
|
||||
mock_util().checklist.return_value = (display_util.OK, ["example.com"])
|
||||
names = self._call(self.mock_install, "Custom")
|
||||
self.assertEqual(names, ["example.com"])
|
||||
self.assertEqual(mock_util().checklist.call_count, 1)
|
||||
self.assertEqual(mock_util().checklist.call_args[0][0], "Custom")
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_filter_names_nothing_selected(self, mock_util):
|
||||
self.mock_install.get_all_names.return_value = set(["example.com"])
|
||||
@@ -481,5 +490,42 @@ class ValidatorTests(unittest.TestCase):
|
||||
self.__validator, "msg", default="")
|
||||
|
||||
|
||||
class ChooseValuesTest(unittest.TestCase):
|
||||
"""Test choose_values."""
|
||||
@classmethod
|
||||
def _call(cls, values, question):
|
||||
from certbot.display.ops import choose_values
|
||||
return choose_values(values, question)
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_choose_names_success(self, mock_util):
|
||||
items = ["first", "second", "third"]
|
||||
mock_util().checklist.return_value = (display_util.OK, [items[2]])
|
||||
result = self._call(items, None)
|
||||
self.assertEquals(result, [items[2]])
|
||||
self.assertTrue(mock_util().checklist.called)
|
||||
self.assertEquals(mock_util().checklist.call_args[0][0], None)
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_choose_names_success_question(self, mock_util):
|
||||
items = ["first", "second", "third"]
|
||||
question = "Which one?"
|
||||
mock_util().checklist.return_value = (display_util.OK, [items[1]])
|
||||
result = self._call(items, question)
|
||||
self.assertEquals(result, [items[1]])
|
||||
self.assertTrue(mock_util().checklist.called)
|
||||
self.assertEquals(mock_util().checklist.call_args[0][0], question)
|
||||
|
||||
@test_util.patch_get_utility("certbot.display.ops.z_util")
|
||||
def test_choose_names_user_cancel(self, mock_util):
|
||||
items = ["first", "second", "third"]
|
||||
question = "Want to cancel?"
|
||||
mock_util().checklist.return_value = (display_util.CANCEL, [])
|
||||
result = self._call(items, question)
|
||||
self.assertEquals(result, [])
|
||||
self.assertTrue(mock_util().checklist.called)
|
||||
self.assertEquals(mock_util().checklist.call_args[0][0], question)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -1533,5 +1533,114 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase):
|
||||
strict=self.config.strict_permissions)
|
||||
|
||||
|
||||
class EnhanceTest(unittest.TestCase):
|
||||
"""Tests for certbot.main.enhance."""
|
||||
|
||||
def setUp(self):
|
||||
self.get_utility_patch = test_util.patch_get_utility()
|
||||
self.mock_get_utility = self.get_utility_patch.start()
|
||||
|
||||
def tearDown(self):
|
||||
self.get_utility_patch.stop()
|
||||
|
||||
def _call(self, args):
|
||||
plugins = disco.PluginsRegistry.find_all()
|
||||
config = configuration.NamespaceConfig(
|
||||
cli.prepare_and_parse_args(plugins, args))
|
||||
|
||||
with mock.patch('certbot.cert_manager.get_certnames') as mock_certs:
|
||||
mock_certs.return_value = ['example.com']
|
||||
with mock.patch('certbot.cert_manager.domains_for_certname') as mock_dom:
|
||||
mock_dom.return_value = ['example.com']
|
||||
with mock.patch('certbot.main._init_le_client') as mock_init:
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.config = config
|
||||
mock_init.return_value = mock_client
|
||||
main.enhance(config, plugins)
|
||||
return mock_client # returns the client
|
||||
|
||||
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
|
||||
@mock.patch('certbot.cert_manager.lineage_for_certname')
|
||||
@mock.patch('certbot.main.display_ops.choose_values')
|
||||
@mock.patch('certbot.main._find_domains_or_certname')
|
||||
def test_selection_question(self, mock_find, mock_choose, mock_lineage, _rec):
|
||||
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
|
||||
mock_choose.return_value = ['example.com']
|
||||
mock_find.return_value = (None, None)
|
||||
with mock.patch('certbot.main.plug_sel.pick_installer') as mock_pick:
|
||||
self._call(['enhance', '--redirect'])
|
||||
self.assertTrue(mock_pick.called)
|
||||
# Check that the message includes "enhancements"
|
||||
self.assertTrue("enhancements" in mock_pick.call_args[0][3])
|
||||
|
||||
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
|
||||
@mock.patch('certbot.cert_manager.lineage_for_certname')
|
||||
@mock.patch('certbot.main.display_ops.choose_values')
|
||||
@mock.patch('certbot.main._find_domains_or_certname')
|
||||
def test_selection_auth_warning(self, mock_find, mock_choose, mock_lineage, _rec):
|
||||
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
|
||||
mock_choose.return_value = ["example.com"]
|
||||
mock_find.return_value = (None, None)
|
||||
with mock.patch('certbot.main.plug_sel.pick_installer'):
|
||||
with mock.patch('certbot.main.plug_sel.logger.warning') as mock_log:
|
||||
mock_client = self._call(['enhance', '-a', 'webroot', '--redirect'])
|
||||
self.assertTrue(mock_log.called)
|
||||
self.assertTrue("make sense" in mock_log.call_args[0][0])
|
||||
self.assertTrue(mock_client.enhance_config.called)
|
||||
|
||||
@mock.patch('certbot.cert_manager.lineage_for_certname')
|
||||
@mock.patch('certbot.main.display_ops.choose_values')
|
||||
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
|
||||
def test_enhance_config_call(self, _rec, mock_choose, mock_lineage):
|
||||
mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent")
|
||||
mock_choose.return_value = ["example.com"]
|
||||
with mock.patch('certbot.main.plug_sel.pick_installer'):
|
||||
mock_client = self._call(['enhance', '--redirect', '--hsts'])
|
||||
req_enh = ["redirect", "hsts"]
|
||||
not_req_enh = ["uir"]
|
||||
self.assertTrue(mock_client.enhance_config.called)
|
||||
self.assertTrue(
|
||||
all([getattr(mock_client.config, e) for e in req_enh]))
|
||||
self.assertFalse(
|
||||
any([getattr(mock_client.config, e) for e in not_req_enh]))
|
||||
self.assertTrue(
|
||||
"example.com" in mock_client.enhance_config.call_args[0][0])
|
||||
|
||||
@mock.patch('certbot.cert_manager.lineage_for_certname')
|
||||
@mock.patch('certbot.main.display_ops.choose_values')
|
||||
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
|
||||
def test_enhance_noninteractive(self, _rec, mock_choose, mock_lineage):
|
||||
mock_lineage.return_value = mock.MagicMock(
|
||||
chain_path="/tmp/nonexistent")
|
||||
mock_choose.return_value = ["example.com"]
|
||||
with mock.patch('certbot.main.plug_sel.pick_installer'):
|
||||
mock_client = self._call(['enhance', '--redirect',
|
||||
'--hsts', '--non-interactive'])
|
||||
self.assertTrue(mock_client.enhance_config.called)
|
||||
self.assertFalse(mock_choose.called)
|
||||
|
||||
@mock.patch('certbot.main.display_ops.choose_values')
|
||||
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
|
||||
def test_user_abort_domains(self, _rec, mock_choose):
|
||||
mock_choose.return_value = []
|
||||
with mock.patch('certbot.main.plug_sel.pick_installer'):
|
||||
self.assertRaises(errors.Error,
|
||||
self._call,
|
||||
['enhance', '--redirect', '--hsts'])
|
||||
|
||||
def test_no_enhancements_defined(self):
|
||||
self.assertRaises(errors.MisconfigurationError,
|
||||
self._call, ['enhance'])
|
||||
|
||||
@mock.patch('certbot.main.plug_sel.choose_configurator_plugins')
|
||||
@mock.patch('certbot.main.display_ops.choose_values')
|
||||
@mock.patch('certbot.main.plug_sel.record_chosen_plugins')
|
||||
def test_plugin_selection_error(self, _rec, mock_choose, mock_pick):
|
||||
mock_choose.return_value = ["example.com"]
|
||||
mock_pick.return_value = (None, None)
|
||||
mock_pick.side_effect = errors.PluginSelectionError()
|
||||
mock_client = self._call(['enhance', '--hsts'])
|
||||
self.assertFalse(mock_client.enhance_config.called)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
||||
@@ -54,7 +54,7 @@ _INITIAL_PID = os.getpid()
|
||||
# the dict are attempted to be cleaned up at program exit. If the
|
||||
# program exits before the lock is cleaned up, it is automatically
|
||||
# released, but the file isn't deleted.
|
||||
_LOCKS = OrderedDict()
|
||||
_LOCKS = OrderedDict() # type: OrderedDict[str, lock.LockFile]
|
||||
|
||||
|
||||
def run_script(params, log=logger.error):
|
||||
|
||||
@@ -107,8 +107,8 @@ 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.22.2 (certbot;
|
||||
darwin 10.13.3) Authenticator/XXX Installer/YYY
|
||||
"". (default: CertbotACMEClient/0.23.0 (certbot;
|
||||
darwin 10.13.4) Authenticator/XXX Installer/YYY
|
||||
(SUBCOMMAND; flags: FLAGS) Py/2.7.14). The flags
|
||||
encoded in the user agent are: --duplicate, --force-
|
||||
renew, --allow-subset-of-names, -n, and whether any
|
||||
|
||||
@@ -63,7 +63,7 @@ Find issues to work on
|
||||
----------------------
|
||||
|
||||
You can find the open issues in the `github issue tracker`_. Comparatively
|
||||
easy ones are marked `Good Volunteer Task`_. If you're starting work on
|
||||
easy ones are marked `good first issue`_. If you're starting work on
|
||||
something, post a comment to let others know and seek feedback on your plan
|
||||
where appropriate.
|
||||
|
||||
@@ -72,7 +72,7 @@ your pull request must have thorough unit test coverage, pass our
|
||||
tests, and be compliant with the :ref:`coding style <coding-style>`.
|
||||
|
||||
.. _github issue tracker: https://github.com/certbot/certbot/issues
|
||||
.. _Good Volunteer Task: https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+Volunteer+Task%22
|
||||
.. _good first issue: https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22
|
||||
|
||||
.. _testing:
|
||||
|
||||
@@ -376,6 +376,9 @@ commands:
|
||||
This should generate documentation in the ``docs/_build/html``
|
||||
directory.
|
||||
|
||||
.. note:: If you skipped the "Getting Started" instructions above,
|
||||
run ``pip install -e ".[docs]"`` to install Certbot's docs extras modules.
|
||||
|
||||
|
||||
.. _docker-dev:
|
||||
|
||||
|
||||
@@ -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.2"
|
||||
LE_AUTO_VERSION="0.23.0"
|
||||
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.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
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
|
||||
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlqwWJwACgkQTRfJlc2X
|
||||
dfIzmwgAghmc3W63/qpCtJdezYeGLJdu03LvKoWYc7dTNYj2+0P5qmAAgCvKNY34
|
||||
qYzXA1jfCOgILSzRNE5WY+rbgjcmxxsxH+luYm6Ik0909MaMQ0D3h+5cRFs/tTtd
|
||||
5cX0gxL3RQQTBwpnwbAZibe7lhjs9pXBiob2ek67hVr+xEwem69BQMlOhtYJbOs1
|
||||
osccoKc4NqaKbrfgOjjtMaL8YoRPO9vJHS9rRr6hxRZlPsmvusAHAiCbIrbX4XKE
|
||||
CgxJFnuHK+amtfRoZg/xCqIK3Z94yZXPezywsri/YvDteOIs+DZ2qG/StfUrNYFX
|
||||
WYfFFFyld0xwQtb4Oi9u4mx4sPg7lw==
|
||||
=jZDE
|
||||
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlrFS/EACgkQTRfJlc2X
|
||||
dfK+rQf8DcKY5bMi5eJnwwAlui6WIyWSrf1KAKt09tEGZSHQ1fcyCPrGVhk7VVDg
|
||||
NJ1/XiYBquPW+7mYUcHrIRsiKYbTUcmVjyqP6tZd67IxRH9ToNqBzA6kq99T+IPd
|
||||
iTGdczHMSPcxM6/Fa5PYMHXy2+ctTr/8+gnsxth9QfcM62Yd6ecfqIdoId3vk9Aw
|
||||
UBMENZhUasIvgZDWuow+1XVZ/DAmdvj2Xl/E3sA9i2ArREJhkhVegtdrHkwSY+Hm
|
||||
MKfZGqNVse6ZAF/8YdEVBum0OngMMs63DwucwFxmw5DqWtmnXm6awLNW/LQ/3R5L
|
||||
xuKjcVaAT1h5TgIyRT6opH8JBKmLpg==
|
||||
=Ouj4
|
||||
-----END PGP SIGNATURE-----
|
||||
|
||||
@@ -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.23.0.dev0"
|
||||
LE_AUTO_VERSION="0.24.0.dev0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
@@ -1089,7 +1089,7 @@ cryptography==2.0.2 \
|
||||
--hash=sha256:01e6e60654df64cca53733cda39446d67100c819c181d403afb120e0d2a71e1b \
|
||||
--hash=sha256:d46f4e5d455cb5563685c52ef212696f0a6cc1ea627603218eabbd8a095291d8 \
|
||||
--hash=sha256:3780b2663ee7ebb37cb83263326e3cd7f8b2ea439c448539d4b87de12c8d06ab
|
||||
enum34==1.1.2 \
|
||||
enum34==1.1.2 ; python_version < '3.4' \
|
||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||
funcsigs==1.0.2 \
|
||||
@@ -1199,18 +1199,18 @@ letsencrypt==0.7.0 \
|
||||
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
|
||||
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
|
||||
|
||||
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
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
Binary file not shown.
@@ -1,12 +1,12 @@
|
||||
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
|
||||
certbot==0.23.0 \
|
||||
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
|
||||
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
|
||||
acme==0.23.0 \
|
||||
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
|
||||
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
|
||||
certbot-apache==0.23.0 \
|
||||
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
|
||||
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
|
||||
certbot-nginx==0.23.0 \
|
||||
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
|
||||
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
|
||||
|
||||
@@ -93,7 +93,7 @@ cryptography==2.0.2 \
|
||||
--hash=sha256:01e6e60654df64cca53733cda39446d67100c819c181d403afb120e0d2a71e1b \
|
||||
--hash=sha256:d46f4e5d455cb5563685c52ef212696f0a6cc1ea627603218eabbd8a095291d8 \
|
||||
--hash=sha256:3780b2663ee7ebb37cb83263326e3cd7f8b2ea439c448539d4b87de12c8d06ab
|
||||
enum34==1.1.2 \
|
||||
enum34==1.1.2 ; python_version < '3.4' \
|
||||
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
|
||||
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
|
||||
funcsigs==1.0.2 \
|
||||
|
||||
3
mypy.ini
Normal file
3
mypy.ini
Normal file
@@ -0,0 +1,3 @@
|
||||
[mypy]
|
||||
python_version = 2.7
|
||||
ignore_missing_imports = True
|
||||
@@ -1,2 +0,0 @@
|
||||
[pytest]
|
||||
addopts = --quiet
|
||||
9
setup.py
9
setup.py
@@ -34,9 +34,7 @@ version = meta['version']
|
||||
# specified here to avoid masking the more specific request requirements in
|
||||
# acme. See https://github.com/pypa/pip/issues/988 for more info.
|
||||
install_requires = [
|
||||
# Remember to update local-oldest-requirements.txt when changing the
|
||||
# minimum acme version.
|
||||
'acme>0.21.1',
|
||||
'acme>=0.22.1',
|
||||
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but
|
||||
# saying so here causes a runtime error against our temporary fork of 0.9.3
|
||||
# in which we added 2.6 support (see #2243), so we relax the requirement.
|
||||
@@ -67,6 +65,10 @@ dev_extras = [
|
||||
'wheel',
|
||||
]
|
||||
|
||||
dev3_extras = [
|
||||
'mypy',
|
||||
]
|
||||
|
||||
docs_extras = [
|
||||
'repoze.sphinx.autointerface',
|
||||
# autodoc_member_order = 'bysource', autodoc_default_flags, and #4686
|
||||
@@ -112,6 +114,7 @@ setup(
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'dev': dev_extras,
|
||||
'dev3': dev3_extras,
|
||||
'docs': docs_extras,
|
||||
},
|
||||
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
targets:
|
||||
#-----------------------------------------------------------------------------
|
||||
#Ubuntu
|
||||
- ami: ami-26d5af4c
|
||||
name: ubuntu15.10
|
||||
type: ubuntu
|
||||
virt: hvm
|
||||
user: ubuntu
|
||||
- ami: ami-d92e6bb3
|
||||
name: ubuntu15.04LTS
|
||||
type: ubuntu
|
||||
virt: hvm
|
||||
user: ubuntu
|
||||
- ami: ami-7b89cc11
|
||||
name: ubuntu14.04LTS
|
||||
type: ubuntu
|
||||
@@ -21,11 +11,6 @@ targets:
|
||||
type: ubuntu
|
||||
virt: pv
|
||||
user: ubuntu
|
||||
- ami: ami-0611546c
|
||||
name: ubuntu12.04LTS
|
||||
type: ubuntu
|
||||
virt: hvm
|
||||
user: ubuntu
|
||||
#-----------------------------------------------------------------------------
|
||||
# Debian
|
||||
- ami: ami-116d857a
|
||||
|
||||
@@ -30,6 +30,7 @@ josepy==1.0.1
|
||||
logger==1.4
|
||||
logilab-common==1.4.1
|
||||
MarkupSafe==1.0
|
||||
mypy==0.580
|
||||
ndg-httpsclient==0.3.2
|
||||
oauth2client==2.0.0
|
||||
pathlib2==2.3.0
|
||||
@@ -66,6 +67,8 @@ tox==2.9.1
|
||||
tqdm==4.19.4
|
||||
traitlets==4.3.2
|
||||
twine==1.9.1
|
||||
typed-ast==1.1.0
|
||||
typing==3.6.4
|
||||
uritemplate==0.6
|
||||
virtualenv==15.1.0
|
||||
wcwidth==0.1.7
|
||||
|
||||
@@ -19,5 +19,5 @@ for requirement in "$@" ; do
|
||||
if [ $pkg = "." ]; then
|
||||
pkg="certbot"
|
||||
fi
|
||||
"$(dirname $0)/pytest.sh" --pyargs $pkg
|
||||
pytest --numprocesses auto --quiet --pyargs $pkg
|
||||
done
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Runs pytest with the provided arguments, adding --numprocesses to the command
|
||||
# line. This argument is set to "auto" if the environmnent variable TRAVIS is
|
||||
# not set, otherwise, it is set to 2. This works around
|
||||
# https://github.com/pytest-dev/pytest-xdist/issues/9. Currently every Travis
|
||||
# environnment provides two cores. See
|
||||
# https://docs.travis-ci.com/user/reference/overview/#Virtualization-environments.
|
||||
|
||||
if ${TRAVIS:-false}; then
|
||||
NUMPROCESSES="2"
|
||||
else
|
||||
NUMPROCESSES="auto"
|
||||
fi
|
||||
|
||||
pytest --numprocesses "$NUMPROCESSES" "$@"
|
||||
@@ -51,8 +51,7 @@ cover () {
|
||||
fi
|
||||
|
||||
pkg_dir=$(echo "$1" | tr _ -)
|
||||
pytest="$(dirname $0)/tools/pytest.sh"
|
||||
"$pytest" --cov "$pkg_dir" --cov-append --cov-report= --pyargs "$1"
|
||||
pytest --cov "$pkg_dir" --cov-append --cov-report= --numprocesses auto --pyargs "$1"
|
||||
coverage report --fail-under="$min" --include="$pkg_dir/*" --show-missing
|
||||
}
|
||||
|
||||
|
||||
22
tox.ini
22
tox.ini
@@ -60,8 +60,6 @@ commands =
|
||||
setenv =
|
||||
PYTHONPATH = {toxinidir}
|
||||
PYTHONHASHSEED = 0
|
||||
passenv =
|
||||
TRAVIS
|
||||
|
||||
[testenv:py27-oldest]
|
||||
commands =
|
||||
@@ -69,40 +67,30 @@ commands =
|
||||
setenv =
|
||||
{[testenv]setenv}
|
||||
CERTBOT_OLDEST=1
|
||||
passenv =
|
||||
{[testenv]passenv}
|
||||
|
||||
[testenv:py27-acme-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} acme[dev]
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-apache-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} certbot-apache
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-certbot-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} .[dev]
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-dns-oldest]
|
||||
commands =
|
||||
{[base]install_and_test} {[base]dns_packages}
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27-nginx-oldest]
|
||||
commands =
|
||||
@@ -110,8 +98,6 @@ commands =
|
||||
python tests/lock_test.py
|
||||
setenv =
|
||||
{[testenv:py27-oldest]setenv}
|
||||
passenv =
|
||||
{[testenv:py27-oldest]passenv}
|
||||
|
||||
[testenv:py27_install]
|
||||
basepython = python2.7
|
||||
@@ -123,8 +109,6 @@ basepython = python2.7
|
||||
commands =
|
||||
{[base]install_packages}
|
||||
./tox.cover.sh
|
||||
passenv =
|
||||
{[testenv]passenv}
|
||||
|
||||
[testenv:lint]
|
||||
basepython = python2.7
|
||||
@@ -136,11 +120,11 @@ commands =
|
||||
pylint --reports=n --rcfile=.pylintrc {[base]source_paths}
|
||||
|
||||
[testenv:mypy]
|
||||
basepython = python3.4
|
||||
basepython = python3
|
||||
commands =
|
||||
{[base]pip_install} mypy
|
||||
{[base]pip_install} .[dev3]
|
||||
{[base]install_packages}
|
||||
mypy --py2 --ignore-missing-imports {[base]source_paths}
|
||||
mypy {[base]source_paths}
|
||||
|
||||
[testenv:apacheconftest]
|
||||
#basepython = python2.7
|
||||
|
||||
Reference in New Issue
Block a user