mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 08:45:22 +02:00
Merge remote-tracking branch 'origin/master' into no-conflicting-declarations3
This commit is contained in:
+1
-1
@@ -24,7 +24,7 @@ class Fixed(jose.Field):
|
||||
|
||||
def encode(self, value):
|
||||
if value != self.value:
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
'Overriding fixed field (%s) with %r', self.json_name, value)
|
||||
return value
|
||||
|
||||
|
||||
@@ -244,7 +244,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
|
||||
if not path["cert_path"] or not path["cert_key"]:
|
||||
# Throw some can't find all of the directives error"
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"Cannot find a cert or key directive in %s. "
|
||||
"VirtualHost was not modified", vhost.path)
|
||||
# Presumably break here so that the virtualhost is not modified
|
||||
@@ -522,7 +522,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
try:
|
||||
args = self.aug.match(path + "/arg")
|
||||
except RuntimeError:
|
||||
logger.warn("Encountered a problem while parsing file: %s, skipping", path)
|
||||
logger.warning("Encountered a problem while parsing file: %s, skipping", path)
|
||||
return None
|
||||
for arg in args:
|
||||
addrs.add(obj.Addr.fromstring(self.parser.get_arg(arg)))
|
||||
@@ -1089,7 +1089,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
try:
|
||||
func(self.choose_vhost(domain), options)
|
||||
except errors.PluginError:
|
||||
logger.warn("Failed %s for %s", enhancement, domain)
|
||||
logger.warning("Failed %s for %s", enhancement, domain)
|
||||
raise
|
||||
|
||||
def _enable_ocsp_stapling(self, ssl_vhost, unused_options):
|
||||
@@ -1276,9 +1276,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# but redirect loops are possible in very obscure cases; see #1620
|
||||
# for reasoning.
|
||||
if self._is_rewrite_exists(general_vh):
|
||||
logger.warn("Added an HTTP->HTTPS rewrite in addition to "
|
||||
"other RewriteRules; you may wish to check for "
|
||||
"overall consistency.")
|
||||
logger.warning("Added an HTTP->HTTPS rewrite in addition to "
|
||||
"other RewriteRules; you may wish to check for "
|
||||
"overall consistency.")
|
||||
|
||||
# Add directives to server
|
||||
# Note: These are not immediately searchable in sites-enabled
|
||||
|
||||
@@ -91,7 +91,7 @@ def _vhost_menu(domain, vhosts):
|
||||
"non-interactive mode. Currently Certbot needs each vhost to be "
|
||||
"in its own conf file, and may need vhosts to be explicitly "
|
||||
"labelled with ServerName or ServerAlias directories.")
|
||||
logger.warn(msg)
|
||||
logger.warning(msg)
|
||||
raise errors.MissingCommandlineFlag(msg)
|
||||
|
||||
return code, tag
|
||||
|
||||
@@ -146,7 +146,7 @@ class ApacheParser(object):
|
||||
constants.os_constant("define_cmd"))
|
||||
# Small errors that do not impede
|
||||
if proc.returncode != 0:
|
||||
logger.warn("Error in checking parameter list: %s", stderr)
|
||||
logger.warning("Error in checking parameter list: %s", stderr)
|
||||
raise errors.MisconfigurationError(
|
||||
"Apache is unable to check whether or not the module is "
|
||||
"loaded because Apache is misconfigured.")
|
||||
|
||||
@@ -56,7 +56,7 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_surgery.return_value = False
|
||||
with mock.patch.dict('os.environ', silly_path):
|
||||
self.assertRaises(errors.NoInstallationError, self.config.prepare)
|
||||
self.assertEquals(mock_surgery.call_count, 1)
|
||||
self.assertEqual(mock_surgery.call_count, 1)
|
||||
|
||||
@mock.patch("certbot_apache.augeas_configurator.AugeasConfigurator.init_augeas")
|
||||
def test_prepare_no_augeas(self, mock_init_augeas):
|
||||
@@ -1242,8 +1242,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
||||
mock_match = mock.Mock(return_value=["something"])
|
||||
self.config.aug.match = mock_match
|
||||
# pylint: disable=protected-access
|
||||
self.assertEquals(self.config._check_aug_version(),
|
||||
["something"])
|
||||
self.assertEqual(self.config._check_aug_version(),
|
||||
["something"])
|
||||
self.config.aug.match.side_effect = RuntimeError
|
||||
self.assertFalse(self.config._check_aug_version())
|
||||
|
||||
|
||||
@@ -129,7 +129,7 @@ class ApacheTlsSni01(common.TLSSNI01):
|
||||
# because it's a new vhost that's not configured yet (GH #677),
|
||||
# or perhaps because there were multiple <VirtualHost> sections
|
||||
# in the config file (GH #1042). See also GH #2600.
|
||||
logger.warn("Falling back to default vhost %s...", default_addr)
|
||||
logger.warning("Falling back to default vhost %s...", default_addr)
|
||||
addrs.add(default_addr)
|
||||
return addrs
|
||||
|
||||
|
||||
@@ -369,10 +369,10 @@ def main():
|
||||
plugin.cleanup_from_tests()
|
||||
|
||||
if overall_success:
|
||||
logger.warn("All compatibility tests succeeded")
|
||||
logger.warning("All compatibility tests succeeded")
|
||||
sys.exit(0)
|
||||
else:
|
||||
logger.warn("One or more compatibility tests failed")
|
||||
logger.warning("One or more compatibility tests failed")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
@@ -179,7 +179,7 @@ class NginxConfigurator(common.Plugin):
|
||||
vhost.filep, vhost.names)
|
||||
except errors.MisconfigurationError as error:
|
||||
logger.debug(error)
|
||||
logger.warn(
|
||||
logger.warning(
|
||||
"Cannot find a cert or key directive in %s for %s. "
|
||||
"VirtualHost was not modified.", vhost.filep, vhost.names)
|
||||
# Presumably break here so that the virtualhost is not modified
|
||||
@@ -386,7 +386,7 @@ class NginxConfigurator(common.Plugin):
|
||||
raise errors.PluginError(
|
||||
"Unsupported enhancement: {0}".format(enhancement))
|
||||
except errors.PluginError:
|
||||
logger.warn("Failed %s for %s", enhancement, domain)
|
||||
logger.warning("Failed %s for %s", enhancement, domain)
|
||||
|
||||
def _enable_redirect(self, vhost, unused_options):
|
||||
"""Redirect all equivalent HTTP traffic to ssl_vhost.
|
||||
|
||||
@@ -25,11 +25,19 @@ class RawNginxParser(object):
|
||||
key = Word(alphanums + "_/+-.")
|
||||
dollar_var = Combine(Literal('$') + Regex(r"[^\{\};,\s]+"))
|
||||
condition = Regex(r"\(.+\)")
|
||||
# Matches anything that is not a special character AND any chars in single
|
||||
# or double quotes
|
||||
# Matches anything that is not a special character, and ${SHELL_VARS}, AND
|
||||
# any chars in single or double quotes
|
||||
# All of these COULD be upgraded to something like
|
||||
# https://stackoverflow.com/a/16130746
|
||||
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
|
||||
dquoted = Regex(r'(\".*\")')
|
||||
squoted = Regex(r"(\'.*\')")
|
||||
nonspecial = Regex(r"[^\{\};,]")
|
||||
varsub = Regex(r"(\$\{\w+\})")
|
||||
# nonspecial nibbles one character at a time, but the other objects take
|
||||
# precedence. We use ZeroOrMore to allow entries like "break ;" to be
|
||||
# parsed as assignments
|
||||
value = Combine(ZeroOrMore(dquoted | squoted | varsub | nonspecial))
|
||||
|
||||
location = CharsNotIn("{};," + string.whitespace)
|
||||
# modifier for location uri [ = | ~ | ~* | ^~ ]
|
||||
modifier = Literal("=") | Literal("~*") | Literal("~") | Literal("^~")
|
||||
@@ -40,6 +48,7 @@ class RawNginxParser(object):
|
||||
assignment = space + key + Optional(space + value, default=None) + semicolon
|
||||
location_statement = space + Optional(modifier) + Optional(space + location + space)
|
||||
if_statement = space + Literal("if") + space + condition + space
|
||||
charset_map_statement = space + Literal("charset_map") + space + value + space + value
|
||||
|
||||
map_statement = space + Literal("map") + space + nonspace + space + dollar_var + space
|
||||
# This is NOT an accurate way to parse nginx map entries; it's almost
|
||||
@@ -52,24 +61,27 @@ class RawNginxParser(object):
|
||||
map_pattern = Regex(r'".*"') | Regex(r"'.*'") | nonspace
|
||||
map_entry = space + map_pattern + space + value + space + semicolon
|
||||
map_block = Group(
|
||||
# key could for instance be "server" or "http", or "location" (in which case
|
||||
# location_statement needs to have a non-empty location)
|
||||
Group(map_statement).leaveWhitespace() +
|
||||
left_bracket +
|
||||
Group(ZeroOrMore(Group(comment | map_entry)) + space).leaveWhitespace() +
|
||||
right_bracket)
|
||||
|
||||
block = Forward()
|
||||
block << Group(
|
||||
# key could for instance be "server" or "http", or "location" (in which case
|
||||
# location_statement needs to have a non-empty location)
|
||||
(Group(space + key + location_statement) ^ Group(if_statement)).leaveWhitespace() +
|
||||
left_bracket +
|
||||
Group(ZeroOrMore(Group(comment | assignment) | block | map_block) + space).leaveWhitespace()
|
||||
+ right_bracket)
|
||||
|
||||
# key could for instance be "server" or "http", or "location" (in which case
|
||||
# location_statement needs to have a non-empty location)
|
||||
|
||||
block_begin = (Group(space + key + location_statement) ^
|
||||
Group(if_statement) ^
|
||||
Group(charset_map_statement)).leaveWhitespace()
|
||||
|
||||
block_innards = Group(ZeroOrMore(Group(comment | assignment) | block | map_block)
|
||||
+ space).leaveWhitespace()
|
||||
|
||||
block << Group(block_begin + left_bracket + block_innards + right_bracket)
|
||||
|
||||
script = OneOrMore(Group(comment | assignment) ^ block ^ map_block) + space + stringEnd
|
||||
script.parseWithTabs()
|
||||
script.parseWithTabs().leaveWhitespace()
|
||||
|
||||
def __init__(self, source):
|
||||
self.source = source
|
||||
|
||||
@@ -168,7 +168,7 @@ class NginxParser(object):
|
||||
self.parsed[item] = parsed
|
||||
trees.append(parsed)
|
||||
except IOError:
|
||||
logger.warn("Could not open file: %s", item)
|
||||
logger.warning("Could not open file: %s", item)
|
||||
except pyparsing.ParseException:
|
||||
logger.debug("Could not parse file: %s", item)
|
||||
return trees
|
||||
|
||||
@@ -37,8 +37,8 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
errors.NoInstallationError, self.config.prepare)
|
||||
|
||||
def test_prepare(self):
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
self.assertEquals(5, len(self.config.parser.parsed))
|
||||
self.assertEqual((1, 6, 2), self.config.version)
|
||||
self.assertEqual(5, len(self.config.parser.parsed))
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.util.exe_exists")
|
||||
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
|
||||
@@ -56,7 +56,7 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
self.config.version = None
|
||||
self.config.config_test = mock.Mock()
|
||||
self.config.prepare()
|
||||
self.assertEquals((1, 6, 2), self.config.version)
|
||||
self.assertEqual((1, 6, 2), self.config.version)
|
||||
|
||||
@mock.patch("certbot_nginx.configurator.socket.gethostbyaddr")
|
||||
def test_get_all_names(self, mock_gethostbyaddr):
|
||||
|
||||
@@ -134,8 +134,8 @@ class TestRawNginxParser(unittest.TestCase):
|
||||
parsed_new = load(handle)
|
||||
try:
|
||||
self.maxDiff = None
|
||||
self.assertEquals(parsed[0], parsed_new[0])
|
||||
self.assertEquals(parsed[1:], parsed_new[1:])
|
||||
self.assertEqual(parsed[0], parsed_new[0])
|
||||
self.assertEqual(parsed[1:], parsed_new[1:])
|
||||
finally:
|
||||
os.unlink(util.get_data_filename('nginx.new.conf'))
|
||||
|
||||
@@ -150,7 +150,7 @@ class TestRawNginxParser(unittest.TestCase):
|
||||
parsed_new = load(handle)
|
||||
|
||||
try:
|
||||
self.assertEquals(parsed, parsed_new)
|
||||
self.assertEqual(parsed, parsed_new)
|
||||
|
||||
self.assertEqual(parsed_new, [
|
||||
['#', " Use bar.conf when it's a full moon!"],
|
||||
|
||||
@@ -117,9 +117,9 @@ class NginxParserTest(util.NginxTest):
|
||||
fooconf = [x for x in vhosts if 'foo.conf' in x.filep][0]
|
||||
self.assertEqual(vhost5, fooconf)
|
||||
localhost = [x for x in vhosts if 'localhost' in x.names][0]
|
||||
self.assertEquals(vhost1, localhost)
|
||||
self.assertEqual(vhost1, localhost)
|
||||
somename = [x for x in vhosts if 'somename' in x.names][0]
|
||||
self.assertEquals(vhost2, somename)
|
||||
self.assertEqual(vhost2, somename)
|
||||
|
||||
def test_add_server_directives(self):
|
||||
nparser = parser.NginxParser(self.config_path, self.ssl_options)
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ def possible_deprecation_warning(config):
|
||||
# need warnings
|
||||
return
|
||||
if "CERTBOT_AUTO" not in os.environ:
|
||||
logger.warn("You are running with an old copy of letsencrypt-auto that does "
|
||||
logger.warning("You are running with an old copy of letsencrypt-auto that does "
|
||||
"not receive updates, and is less reliable than more recent versions. "
|
||||
"We recommend upgrading to the latest certbot-auto script, or using native "
|
||||
"OS packages.")
|
||||
|
||||
+4
-4
@@ -104,10 +104,10 @@ def register(config, account_storage, tos_cb=None):
|
||||
if not config.register_unsafely_without_email:
|
||||
msg = ("No email was provided and "
|
||||
"--register-unsafely-without-email was not present.")
|
||||
logger.warn(msg)
|
||||
logger.warning(msg)
|
||||
raise errors.Error(msg)
|
||||
if not config.dry_run:
|
||||
logger.warn("Registering without email!")
|
||||
logger.warning("Registering without email!")
|
||||
|
||||
# Each new registration shall use a fresh new key
|
||||
key = jose.JWKRSA(key=jose.ComparableRSAKey(
|
||||
@@ -453,10 +453,10 @@ class Client(object):
|
||||
try:
|
||||
self.installer.enhance(dom, enhancement, options)
|
||||
except errors.PluginEnhancementAlreadyPresent:
|
||||
logger.warn("Enhancement %s was already set.",
|
||||
logger.warning("Enhancement %s was already set.",
|
||||
enhancement)
|
||||
except errors.PluginError:
|
||||
logger.warn("Unable to set enhancement %s for %s",
|
||||
logger.warning("Unable to set enhancement %s for %s",
|
||||
enhancement, dom)
|
||||
raise
|
||||
|
||||
|
||||
+54
-26
@@ -5,6 +5,7 @@ import os
|
||||
import signal
|
||||
import traceback
|
||||
|
||||
from certbot import errors
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -20,43 +21,62 @@ _SIGNALS = ([signal.SIGTERM] if os.name == "nt" else
|
||||
|
||||
|
||||
class ErrorHandler(object):
|
||||
"""Registers functions to be called if an exception or signal occurs.
|
||||
"""Context manager for running code that must be cleaned up on failure.
|
||||
|
||||
This class allows you to register functions that will be called when
|
||||
an exception (excluding SystemExit) or signal is encountered. The
|
||||
class works best as a context manager. For example:
|
||||
The context manager allows you to register functions that will be called
|
||||
when an exception (excluding SystemExit) or signal is encountered. Usage:
|
||||
|
||||
with ErrorHandler(cleanup_func):
|
||||
handler = ErrorHandler(cleanup1_func, *cleanup1_args, **cleanup1_kwargs)
|
||||
handler.register(cleanup2_func, *cleanup2_args, **cleanup2_kwargs)
|
||||
|
||||
with handler:
|
||||
do_something()
|
||||
|
||||
If an exception is raised out of do_something, cleanup_func will be
|
||||
called. The exception is not caught by the ErrorHandler. Similarly,
|
||||
if a signal is encountered, cleanup_func is called followed by the
|
||||
previously registered signal handler.
|
||||
Or for one cleanup function:
|
||||
|
||||
Every registered function is attempted to be run to completion
|
||||
exactly once. If a registered function raises an exception, it is
|
||||
logged and the next function is called. If a (different) handled
|
||||
signal occurs while calling a registered function, it is attempted
|
||||
to be called again by the next signal handler.
|
||||
with ErrorHandler(func, args, kwargs):
|
||||
do_something()
|
||||
|
||||
If an exception is raised out of do_something, the cleanup functions will
|
||||
be called in last in first out order. Then the exception is raised.
|
||||
Similarly, if a signal is encountered, the cleanup functions are called
|
||||
followed by the previously received signal handler.
|
||||
|
||||
Each registered cleanup function is called exactly once. If a registered
|
||||
function raises an exception, it is logged and the next function is called.
|
||||
Signals received while the registered functions are executing are
|
||||
deferred until they finish.
|
||||
|
||||
"""
|
||||
def __init__(self, func=None, *args, **kwargs):
|
||||
self.body_executed = False
|
||||
self.funcs = []
|
||||
self.prev_handlers = {}
|
||||
self.received_signals = []
|
||||
if func is not None:
|
||||
self.register(func, *args, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
self.set_signal_handlers()
|
||||
self.body_executed = False
|
||||
self._set_signal_handlers()
|
||||
|
||||
def __exit__(self, exec_type, exec_value, trace):
|
||||
self.body_executed = True
|
||||
retval = False
|
||||
# SystemExit is ignored to properly handle forks that don't exec
|
||||
if exec_type not in (None, SystemExit):
|
||||
if exec_type in (None, SystemExit):
|
||||
return retval
|
||||
elif exec_type is errors.SignalExit:
|
||||
logger.debug("Encountered signals: %s", self.received_signals)
|
||||
retval = True
|
||||
else:
|
||||
logger.debug("Encountered exception:\n%s", "".join(
|
||||
traceback.format_exception(exec_type, exec_value, trace)))
|
||||
self.call_registered()
|
||||
self.reset_signal_handlers()
|
||||
|
||||
self._call_registered()
|
||||
self._reset_signal_handlers()
|
||||
self._call_signals()
|
||||
return retval
|
||||
|
||||
def register(self, func, *args, **kwargs):
|
||||
"""Sets func to be called with *args and **kwargs during cleanup
|
||||
@@ -66,7 +86,7 @@ class ErrorHandler(object):
|
||||
"""
|
||||
self.funcs.append(functools.partial(func, *args, **kwargs))
|
||||
|
||||
def call_registered(self):
|
||||
def _call_registered(self):
|
||||
"""Calls all registered functions"""
|
||||
logger.debug("Calling registered functions")
|
||||
while self.funcs:
|
||||
@@ -77,7 +97,7 @@ class ErrorHandler(object):
|
||||
logger.exception(error)
|
||||
self.funcs.pop()
|
||||
|
||||
def set_signal_handlers(self):
|
||||
def _set_signal_handlers(self):
|
||||
"""Sets signal handlers for signals in _SIGNALS."""
|
||||
for signum in _SIGNALS:
|
||||
prev_handler = signal.getsignal(signum)
|
||||
@@ -86,19 +106,27 @@ class ErrorHandler(object):
|
||||
self.prev_handlers[signum] = prev_handler
|
||||
signal.signal(signum, self._signal_handler)
|
||||
|
||||
def reset_signal_handlers(self):
|
||||
def _reset_signal_handlers(self):
|
||||
"""Resets signal handlers for signals in _SIGNALS."""
|
||||
for signum in self.prev_handlers:
|
||||
signal.signal(signum, self.prev_handlers[signum])
|
||||
self.prev_handlers.clear()
|
||||
|
||||
def _signal_handler(self, signum, unused_frame):
|
||||
"""Calls registered functions and the previous signal handler.
|
||||
"""Replacement function for handling recieved signals.
|
||||
|
||||
Store the recieved signal. If we are executing the code block in
|
||||
the body of the context manager, stop by raising signal exit.
|
||||
|
||||
:param int signum: number of current signal
|
||||
|
||||
"""
|
||||
logger.debug("Singal %s encountered", signum)
|
||||
self.call_registered()
|
||||
signal.signal(signum, self.prev_handlers[signum])
|
||||
os.kill(os.getpid(), signum)
|
||||
self.received_signals.append(signum)
|
||||
if not self.body_executed:
|
||||
raise errors.SignalExit
|
||||
|
||||
def _call_signals(self):
|
||||
"""Finally call the deferred signals."""
|
||||
for signum in self.received_signals:
|
||||
logger.debug("Calling signal %s", signum)
|
||||
os.kill(os.getpid(), signum)
|
||||
|
||||
@@ -29,6 +29,10 @@ class HookCommandNotFound(Error):
|
||||
"""Failed to find a hook command in the PATH."""
|
||||
|
||||
|
||||
class SignalExit(Error):
|
||||
"""A Unix signal was recieved while in the ErrorHandler context manager."""
|
||||
|
||||
|
||||
# Auth Handler Errors
|
||||
class AuthorizationError(Error):
|
||||
"""Authorization error."""
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ def post_hook(config, final=False):
|
||||
if not pre_hook.already:
|
||||
logger.info("No renewals attempted, so not running post-hook")
|
||||
if config.verb != "renew":
|
||||
logger.warn("Sanity failure in renewal hooks")
|
||||
logger.warning("Sanity failure in renewal hooks")
|
||||
return
|
||||
if final or config.verb != "renew":
|
||||
logger.info("Running post-hook command: %s", config.post_hook)
|
||||
|
||||
+1
-1
@@ -773,5 +773,5 @@ def main(cli_args=sys.argv[1:]):
|
||||
if __name__ == "__main__":
|
||||
err_string = main()
|
||||
if err_string:
|
||||
logger.warn("Exiting with message %s", err_string)
|
||||
logger.warning("Exiting with message %s", err_string)
|
||||
sys.exit(err_string) # pragma: no cover
|
||||
|
||||
@@ -174,7 +174,7 @@ def choose_configurator_plugins(config, plugins, verb):
|
||||
if verb == "install":
|
||||
need_inst = True
|
||||
if config.authenticator:
|
||||
logger.warn("Specifying an authenticator doesn't make sense in install mode")
|
||||
logger.warning("Specifying an authenticator doesn't make sense in install mode")
|
||||
|
||||
# Try to meet the user's request and/or ask them to pick plugins
|
||||
authenticator = installer = None
|
||||
|
||||
+61
-8
@@ -3,15 +3,27 @@ import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
import psutil
|
||||
import zope.component
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
|
||||
try:
|
||||
import psutil
|
||||
USE_PSUTIL = True
|
||||
except ImportError:
|
||||
USE_PSUTIL = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RENEWER_EXTRA_MSG = (
|
||||
" For automated renewal, you may want to use a script that stops"
|
||||
" and starts your webserver. You can find an example at"
|
||||
" https://certbot.eff.org/docs/using.html#renewal ."
|
||||
" Alternatively you can use the webroot plugin to renew without"
|
||||
" needing to stop and start your webserver.")
|
||||
|
||||
|
||||
def path_surgery(restart_cmd):
|
||||
"""Attempt to perform PATH surgery to find restart_cmd
|
||||
|
||||
@@ -38,9 +50,11 @@ def path_surgery(restart_cmd):
|
||||
return True
|
||||
else:
|
||||
expanded = " expanded" if any(added) else ""
|
||||
logger.warn("Failed to find %s in%s PATH: %s", restart_cmd, expanded, path)
|
||||
logger.warning("Failed to find %s in%s PATH: %s", restart_cmd,
|
||||
expanded, path)
|
||||
return False
|
||||
|
||||
|
||||
def already_listening(port, renewer=False):
|
||||
"""Check if a process is already listening on the port.
|
||||
|
||||
@@ -53,6 +67,50 @@ def already_listening(port, renewer=False):
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False.
|
||||
|
||||
"""
|
||||
|
||||
if USE_PSUTIL:
|
||||
return already_listening_psutil(port, renewer=renewer)
|
||||
else:
|
||||
logger.debug("Psutil not found, using simple socket check.")
|
||||
return already_listening_socket(port, renewer=renewer)
|
||||
|
||||
|
||||
def already_listening_socket(port, renewer=False):
|
||||
"""Simple socket based check to find out if port is already in use
|
||||
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False
|
||||
"""
|
||||
|
||||
try:
|
||||
testsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
|
||||
try:
|
||||
testsocket.bind(("", port))
|
||||
except socket.error:
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
extra = ""
|
||||
if renewer:
|
||||
extra = RENEWER_EXTRA_MSG
|
||||
display.notification(
|
||||
"Port {0} is already in use by another process. This will "
|
||||
"prevent us from binding to that port. Please stop the "
|
||||
"process that is populating the port in question and try "
|
||||
"again. {1}".format(port, extra), height=13)
|
||||
return True
|
||||
finally:
|
||||
testsocket.close()
|
||||
except socket.error:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def already_listening_psutil(port, renewer=False):
|
||||
"""Psutil variant of the open port check
|
||||
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False.
|
||||
|
||||
"""
|
||||
try:
|
||||
net_connections = psutil.net_connections()
|
||||
@@ -81,12 +139,7 @@ def already_listening(port, renewer=False):
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
extra = ""
|
||||
if renewer:
|
||||
extra = (
|
||||
" For automated renewal, you may want to use a script that stops"
|
||||
" and starts your webserver. You can find an example at"
|
||||
" https://letsencrypt.org/howitworks/#writing-your-own-renewal-script"
|
||||
". Alternatively you can use the webroot plugin to renew without"
|
||||
" needing to stop and start your webserver.")
|
||||
extra = RENEWER_EXTRA_MSG
|
||||
display.notification(
|
||||
"The program {0} (process ID {1}) is already listening "
|
||||
"on TCP port {2}. This will prevent us from binding to "
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
"""Tests for certbot.plugins.util."""
|
||||
import os
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
import mock
|
||||
import psutil
|
||||
|
||||
try:
|
||||
# Python 3.5+
|
||||
from importlib import reload as refresh # pylint: disable=no-name-in-module
|
||||
except ImportError:
|
||||
# Python 2-3.4
|
||||
from imp import reload as refresh
|
||||
|
||||
|
||||
class PathSurgeryTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.path_surgery."""
|
||||
|
||||
@mock.patch("certbot.plugins.util.logger.warn")
|
||||
@mock.patch("certbot.plugins.util.logger.warning")
|
||||
@mock.patch("certbot.plugins.util.logger.debug")
|
||||
def test_path_surgery(self, mock_debug, mock_warn):
|
||||
from certbot.plugins.util import path_surgery
|
||||
@@ -16,20 +24,64 @@ class PathSurgeryTest(unittest.TestCase):
|
||||
with mock.patch.dict('os.environ', all_path):
|
||||
with mock.patch('certbot.util.exe_exists') as mock_exists:
|
||||
mock_exists.return_value = True
|
||||
self.assertEquals(path_surgery("eg"), True)
|
||||
self.assertEquals(mock_debug.call_count, 0)
|
||||
self.assertEquals(mock_warn.call_count, 0)
|
||||
self.assertEquals(os.environ["PATH"], all_path["PATH"])
|
||||
self.assertEqual(path_surgery("eg"), True)
|
||||
self.assertEqual(mock_debug.call_count, 0)
|
||||
self.assertEqual(mock_warn.call_count, 0)
|
||||
self.assertEqual(os.environ["PATH"], all_path["PATH"])
|
||||
no_path = {"PATH": "/tmp/"}
|
||||
with mock.patch.dict('os.environ', no_path):
|
||||
path_surgery("thingy")
|
||||
self.assertEquals(mock_debug.call_count, 1)
|
||||
self.assertEquals(mock_warn.call_count, 1)
|
||||
self.assertEqual(mock_debug.call_count, 1)
|
||||
self.assertEqual(mock_warn.call_count, 1)
|
||||
self.assertTrue("Failed to find" in mock_warn.call_args[0][0])
|
||||
self.assertTrue("/usr/local/bin" in os.environ["PATH"])
|
||||
self.assertTrue("/tmp" in os.environ["PATH"])
|
||||
|
||||
class AlreadyListeningTest(unittest.TestCase):
|
||||
|
||||
class AlreadyListeningTestNoPsutil(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.already_listening when
|
||||
psutil is not available"""
|
||||
def setUp(self):
|
||||
import certbot.plugins.util
|
||||
# Ensure we get importerror
|
||||
self.psutil = None
|
||||
if "psutil" in sys.modules:
|
||||
self.psutil = sys.modules['psutil']
|
||||
sys.modules['psutil'] = None
|
||||
# Reload hackery to ensure getting non-psutil version
|
||||
# loaded to memory
|
||||
refresh(certbot.plugins.util)
|
||||
|
||||
def tearDown(self):
|
||||
# Need to reload the module to ensure
|
||||
# getting back to normal
|
||||
import certbot.plugins.util
|
||||
sys.modules["psutil"] = self.psutil
|
||||
refresh(certbot.plugins.util)
|
||||
|
||||
@mock.patch("certbot.plugins.util.zope.component.getUtility")
|
||||
def test_ports_available(self, mock_getutil):
|
||||
import certbot.plugins.util as plugins_util
|
||||
# Ensure we don't get error
|
||||
with mock.patch("socket._socketobject.bind"):
|
||||
self.assertFalse(plugins_util.already_listening(80))
|
||||
self.assertFalse(plugins_util.already_listening(80, True))
|
||||
self.assertEqual(mock_getutil.call_count, 0)
|
||||
|
||||
@mock.patch("certbot.plugins.util.zope.component.getUtility")
|
||||
def test_ports_blocked(self, mock_getutil):
|
||||
sys.modules["psutil"] = None
|
||||
import certbot.plugins.util as plugins_util
|
||||
import socket
|
||||
with mock.patch("socket._socketobject.bind", side_effect=socket.error):
|
||||
self.assertTrue(plugins_util.already_listening(80))
|
||||
self.assertTrue(plugins_util.already_listening(80, True))
|
||||
with mock.patch("socket.socket", side_effect=socket.error):
|
||||
self.assertFalse(plugins_util.already_listening(80))
|
||||
self.assertEqual(mock_getutil.call_count, 2)
|
||||
|
||||
|
||||
class AlreadyListeningTestPsutil(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.already_listening."""
|
||||
def _call(self, *args, **kwargs):
|
||||
from certbot.plugins.util import already_listening
|
||||
@@ -42,6 +94,7 @@ class AlreadyListeningTest(unittest.TestCase):
|
||||
# This tests a race condition, or permission problem, or OS
|
||||
# incompatibility in which, for some reason, no process name can be
|
||||
# found to match the identified listening PID.
|
||||
import psutil
|
||||
from psutil._common import sconn
|
||||
conns = [
|
||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||
@@ -94,7 +147,7 @@ class AlreadyListeningTest(unittest.TestCase):
|
||||
raddr=(), status="LISTEN", pid=4416)]
|
||||
mock_net.return_value = conns
|
||||
mock_process.name.return_value = "inetd"
|
||||
result = self._call(17)
|
||||
result = self._call(17, True)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
mock_process.assert_called_once_with(4416)
|
||||
@@ -124,6 +177,7 @@ class AlreadyListeningTest(unittest.TestCase):
|
||||
|
||||
@mock.patch("certbot.plugins.util.psutil.net_connections")
|
||||
def test_access_denied_exception(self, mock_net):
|
||||
import psutil
|
||||
mock_net.side_effect = psutil.AccessDenied("")
|
||||
self.assertFalse(self._call(12345))
|
||||
|
||||
|
||||
+1
-1
@@ -552,7 +552,7 @@ class Reverter(object):
|
||||
others.sort()
|
||||
if others[-1] != timestamp:
|
||||
timetravel = str(float(others[-1]) + 1)
|
||||
logger.warn("Current timestamp %s does not correspond to newest reverter "
|
||||
logger.warning("Current timestamp %s does not correspond to newest reverter "
|
||||
"checkpoint; your clock probably jumped. Time travelling to %s",
|
||||
timestamp, timetravel)
|
||||
timestamp = timetravel
|
||||
|
||||
@@ -82,7 +82,7 @@ class RegisterTest(unittest.TestCase):
|
||||
self.config.register_unsafely_without_email = True
|
||||
self.config.dry_run = False
|
||||
self._call()
|
||||
mock_logger.warn.assert_called_once_with(mock.ANY)
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test_unsupported_error(self):
|
||||
from acme import messages
|
||||
|
||||
@@ -1,10 +1,38 @@
|
||||
"""Tests for certbot.error_handler."""
|
||||
import contextlib
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
def get_signals(signums):
|
||||
"""Get the handlers for an iterable of signums."""
|
||||
return dict((s, signal.getsignal(s)) for s in signums)
|
||||
|
||||
|
||||
def set_signals(sig_handler_dict):
|
||||
"""Set the signal (keys) with the handler (values) from the input dict."""
|
||||
for s, h in sig_handler_dict.items():
|
||||
signal.signal(s, h)
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def signal_receiver(signums):
|
||||
"""Context manager to catch signals"""
|
||||
signals = []
|
||||
prev_handlers = {}
|
||||
prev_handlers = get_signals(signums)
|
||||
set_signals(dict((s, lambda s, _: signals.append(s)) for s in signums))
|
||||
yield signals
|
||||
set_signals(prev_handlers)
|
||||
|
||||
|
||||
def send_signal(signum):
|
||||
"""Send the given signal"""
|
||||
os.kill(os.getpid(), signum)
|
||||
|
||||
|
||||
class ErrorHandlerTest(unittest.TestCase):
|
||||
"""Tests for certbot.error_handler."""
|
||||
@@ -22,6 +50,38 @@ class ErrorHandlerTest(unittest.TestCase):
|
||||
self.signals = error_handler._SIGNALS
|
||||
|
||||
def test_context_manager(self):
|
||||
exception_raised = False
|
||||
try:
|
||||
with self.handler:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
exception_raised = True
|
||||
|
||||
self.assertTrue(exception_raised)
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
|
||||
def test_context_manager_with_signal(self):
|
||||
init_signals = get_signals(self.signals)
|
||||
with signal_receiver(self.signals) as signals_received:
|
||||
with self.handler:
|
||||
should_be_42 = 42
|
||||
send_signal(self.signals[0])
|
||||
should_be_42 *= 10
|
||||
|
||||
# check exectuion stoped when the signal was sent
|
||||
self.assertEqual(42, should_be_42)
|
||||
# assert signals were caught
|
||||
self.assertEqual([self.signals[0]], signals_received)
|
||||
# assert the error handling function was just called once
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
for signum in self.signals:
|
||||
self.assertEqual(init_signals[signum], signal.getsignal(signum))
|
||||
|
||||
def test_bad_recovery(self):
|
||||
bad_func = mock.MagicMock(side_effect=[ValueError])
|
||||
self.handler.register(bad_func)
|
||||
try:
|
||||
with self.handler:
|
||||
raise ValueError
|
||||
@@ -29,31 +89,17 @@ class ErrorHandlerTest(unittest.TestCase):
|
||||
pass
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
bad_func.assert_called_once_with()
|
||||
|
||||
@mock.patch('certbot.error_handler.os')
|
||||
@mock.patch('certbot.error_handler.signal')
|
||||
def test_signal_handler(self, mock_signal, mock_os):
|
||||
# pylint: disable=protected-access
|
||||
mock_signal.getsignal.return_value = signal.SIG_DFL
|
||||
self.handler.set_signal_handlers()
|
||||
signal_handler = self.handler._signal_handler
|
||||
for signum in self.signals:
|
||||
mock_signal.signal.assert_any_call(signum, signal_handler)
|
||||
|
||||
signum = self.signals[0]
|
||||
signal_handler(signum, None)
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
mock_os.kill.assert_called_once_with(mock_os.getpid(), signum)
|
||||
|
||||
self.handler.reset_signal_handlers()
|
||||
for signum in self.signals:
|
||||
mock_signal.signal.assert_any_call(signum, signal.SIG_DFL)
|
||||
|
||||
def test_bad_recovery(self):
|
||||
bad_func = mock.MagicMock(side_effect=[ValueError])
|
||||
def test_bad_recovery_with_signal(self):
|
||||
sig1 = self.signals[0]
|
||||
sig2 = self.signals[-1]
|
||||
bad_func = mock.MagicMock(side_effect=lambda: send_signal(sig1))
|
||||
self.handler.register(bad_func)
|
||||
self.handler.call_registered()
|
||||
with signal_receiver(self.signals) as signals_received:
|
||||
with self.handler:
|
||||
send_signal(sig2)
|
||||
self.assertEqual([sig2, sig1], signals_received)
|
||||
self.init_func.assert_called_once_with(*self.init_args,
|
||||
**self.init_kwargs)
|
||||
bad_func.assert_called_once_with()
|
||||
|
||||
@@ -362,8 +362,8 @@ class TestFullCheckpointsReverter(unittest.TestCase):
|
||||
self.assertEqual(mock_logger.warning.call_count, 1)
|
||||
|
||||
# Test Generic warning
|
||||
mock_logger.warning.call_count = 0
|
||||
self._setup_three_checkpoints()
|
||||
mock_logger.warning.call_count = 0
|
||||
self.reverter.rollback_checkpoints(4)
|
||||
self.assertEqual(mock_logger.warning.call_count, 1)
|
||||
|
||||
|
||||
+1
-1
@@ -349,7 +349,7 @@ def safe_email(email):
|
||||
if EMAIL_REGEX.match(email) is not None:
|
||||
return not email.startswith(".") and ".." not in email
|
||||
else:
|
||||
logger.warn("Invalid email address: %s.", email)
|
||||
logger.warning("Invalid email address: %s.", email)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
+135
-3
@@ -151,9 +151,7 @@ Resources for recommendations
|
||||
In the course of considering how to handle this issue, we received
|
||||
recommendations with sources of expert guidance on ciphersuites and other
|
||||
cryptographic parameters. We're grateful to everyone who contributed
|
||||
suggestions. The recommendations we received are available at
|
||||
|
||||
https://github.com/certbot/certbot/wiki/Ciphersuite-guidance
|
||||
suggestions. The recommendations we received are available under Feedback_.
|
||||
|
||||
Certbot users are welcome to review these authorities to
|
||||
better inform their own cryptographic parameter choices. We also
|
||||
@@ -205,3 +203,137 @@ so far is redirecting HTTP requests to HTTPS in web servers, the
|
||||
"redirect" enhancement). The changes here would probably be either a new
|
||||
"ciphersuite" enhancement in each plugin that provides an installer,
|
||||
or a family of enhancements, one per selectable ciphersuite configuration.
|
||||
|
||||
Feedback
|
||||
========
|
||||
We recieve lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some coallated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
|
||||
|
||||
Because of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions.
|
||||
|
||||
Some people provided rationale information mostly having to do with compatibility of particular user-agents (especially UAs that don't support ECC, or that don't support DH groups > 1024 bits). Some ciphersuite configurations have been chosen to try to increase compatibility with older UAs while allowing newer UAs to negotiate stronger crypto. For example, some configurations forego forward secrecy entirely for connections from old UAs, like by offering ECDHE and RSA key exchange, but no DHE at all. (There are UAs that can fail the negotiation completely if a DHE ciphersuite with prime > 1024 bits is offered.)
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
RFC 7575
|
||||
~~~~~~~~
|
||||
|
||||
IETF has published a BCP document, RFC 7525, "Recommendations for Secure Use of Transport Layer Security (TLS) and Datagram Transport Layer Security (DTLS)"
|
||||
|
||||
https://datatracker.ietf.org/doc/rfc7525/
|
||||
|
||||
BetterCrypto.org
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
BetterCrypto.org, a collaboration of mostly European IT security experts, has published a draft paper, "Applied Crypto Hardening"
|
||||
|
||||
https://bettercrypto.org/static/applied-crypto-hardening.pdf
|
||||
|
||||
FF-DHE Internet-Draft
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Gillmor's Internet-Draft "Negotiated Discrete Log Diffie-Hellman Ephemeral Parameters for TLS" is being developed at the IETF TLS WG. It advocates using *standardized* DH groups in all cases, not individually-chosen ones (mostly because of the Triple Handshake attack which can involve maliciously choosing invalid DH groups). The draft provides a list of recommended groups, with primes beginning at 2048 bits and going up from there. It also has a new protocol mechanism for agreeing to use these groups, with the possibility of backwards compatibility (and use of weaker DH groups) for older clients and servers that don't know about this mechanism.
|
||||
|
||||
https://tools.ietf.org/html/draft-ietf-tls-negotiated-ff-dhe-10
|
||||
|
||||
Mozilla
|
||||
~~~~~~~
|
||||
|
||||
Mozilla's general server configuration guidance is available at https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||
|
||||
Mozilla has also produced a configuration generator: https://mozilla.github.io/server-side-tls/ssl-config-generator/
|
||||
|
||||
Dutch National Cyber Security Centre
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The Dutch National Cyber Security Centre has published guidance on "ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)" ("IT Security Guidelines for Transport Layer Security (TLS)"). These are available only in Dutch at
|
||||
|
||||
https://www.ncsc.nl/dienstverlening/expertise-advies/kennisdeling/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html
|
||||
|
||||
I have access to an English-language summary of the recommendations.
|
||||
|
||||
Keylength.com
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Damien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels. The keylength recommendations of the various sources are summarized in a chart. This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2015.
|
||||
|
||||
http://www.keylength.com/
|
||||
|
||||
NIST
|
||||
~~~~
|
||||
NISA published its "NIST Special Publication 800-52 Revision 1: Guidelines for the Selection, Configuration, and Use of Transport Layer Security (TLS) Implementations"
|
||||
|
||||
http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-52r1.pdf
|
||||
|
||||
and its "NIST Special Publication 800-57: Recommendation for Key Management – Part 1: General (Revision 3)"
|
||||
|
||||
http://csrc.nist.gov/publications/nistpubs/800-57/sp800-57_part1_rev3_general.pdf
|
||||
|
||||
ENISA
|
||||
~~~~~
|
||||
|
||||
ENISA published its "Algorithms, Key Sizes and Parameters Report - 2013"
|
||||
|
||||
https://www.enisa.europa.eu/activities/identity-and-trust/library/deliverables/algorithms-key-sizes-and-parameters-report
|
||||
|
||||
WeakDH/Logjam
|
||||
-------------
|
||||
|
||||
The WeakDH/Logjam research has thrown into question the safety of some existing practice using DH ciphersuites, especially the use of standardized groups with a prime ≤ 1024 bits. The authors provided detailed guidance, including ciphersuite lists, at
|
||||
|
||||
https://weakdh.org/sysadmin.html
|
||||
|
||||
These lists may have been derived from Mozilla's recommendations.
|
||||
One of the authors clarified his view of the priorities for various changes as a result of the research at
|
||||
|
||||
https://www.ietf.org/mail-archive/web/tls/current/msg16496.html
|
||||
|
||||
In particular, he supports ECDHE and also supports the use of the standardized groups in the FF-DHE Internet-Draft mentioned above (which isn't clear from the group's original recommendations).
|
||||
|
||||
Particular sites' opinions or configurations
|
||||
--------------------------------------------
|
||||
|
||||
Amazon ELB
|
||||
~~~~~~~~~~
|
||||
|
||||
Amazon ELB explains its current ciphersuite choices at
|
||||
|
||||
https://docs.aws.amazon.com/ElasticLoadBalancing/latest/DeveloperGuide/elb-security-policy-table.html
|
||||
|
||||
U.S. Government 18F
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The 18F site (https://18f.gsa.gov/) is using
|
||||
|
||||
::
|
||||
|
||||
ssl_ciphers 'kEECDH+ECDSA+AES128 kEECDH+ECDSA+AES256 kEECDH+AES128 kEECDH+AES256 kEDH+AES128 kEDH+AES256 DES-CBC3-SHA +SHA !aNULL !eNULL !LOW !MD5 !EXP !DSS !PSK !SRP !kECDH !CAMELLIA !RC4 !SEED';
|
||||
|
||||
Duraconf
|
||||
~~~~~~~~
|
||||
|
||||
The Duraconf project collects particular configuration files, with an apparent focus on avoiding the use of obsolete symmetric ciphers and hash functions, and favoring forward secrecy while not requiring it.
|
||||
|
||||
https://github.com/ioerror/duraconf
|
||||
|
||||
Site scanning or rating tools
|
||||
-----------------------------
|
||||
|
||||
Qualys SSL Labs
|
||||
~~~~~~~~~~~~~~~
|
||||
|
||||
Qualys offers the best-known TLS security scanner, maintained by Ivan Ristić.
|
||||
|
||||
https://www.ssllabs.com/
|
||||
|
||||
Dutch NCSC
|
||||
~~~~~~~~~~
|
||||
|
||||
The Dutch NCSC, mentioned above, has also made available its own site security scanner which indicates how well sites comply with the recommendations.
|
||||
|
||||
https://en.internet.nl/
|
||||
|
||||
Java compatibility issue
|
||||
------------------------
|
||||
|
||||
A lot of backward-compatibility concerns have to do with Java hard-coding DHE primes to a 1024-bit limit, accepting DHE ciphersuites in negotiation, and then aborting the connection entirely if a prime > 1024 bits is presented. The simple summary is that servers offering a Java-compatible DHE ciphersuite in preference to other Java-compatible ciphersuites, and then presenting a DH group with a prime > 1024 bits, will be completely incompatible with clients running some versions of Java. (This may also be the case with very old MSIE versions...?) There are various strategies for dealing with this, and maybe we can document the options here.
|
||||
|
||||
@@ -323,11 +323,7 @@ Steps:
|
||||
should run the integration tests, see `integration`_. See `Known Issues`_
|
||||
for some common failures that have nothing to do with your code.
|
||||
7. Submit the PR.
|
||||
8. Did your tests pass on Travis? If they didn't, it might not be your fault!
|
||||
See `Known Issues`_. If it's not a known issue, fix any errors.
|
||||
|
||||
.. _Known Issues:
|
||||
https://github.com/certbot/certbot/wiki/Known-issues
|
||||
8. Did your tests pass on Travis? If they didn't, fix any errors.
|
||||
|
||||
Updating the documentation
|
||||
==========================
|
||||
|
||||
+80
-2
@@ -2,5 +2,83 @@
|
||||
Packaging Guide
|
||||
===============
|
||||
|
||||
Documentation can be found at
|
||||
https://github.com/certbot/certbot/wiki/Packaging.
|
||||
Releases
|
||||
========
|
||||
|
||||
We release packages and upload them to PyPI (wheels and source tarballs).
|
||||
|
||||
- https://pypi.python.org/pypi/acme
|
||||
- https://pypi.python.org/pypi/certbot
|
||||
- https://pypi.python.org/pypi/certbot-apache
|
||||
- https://pypi.python.org/pypi/certbot-nginx
|
||||
|
||||
The following scripts are used in the process:
|
||||
|
||||
- https://github.com/letsencrypt/letsencrypt/blob/master/tools/release.sh
|
||||
|
||||
We currently version with the following scheme:
|
||||
|
||||
- ``0.1.0``
|
||||
- ``0.2.0dev`` for developement in ``master``
|
||||
- ``0.2.0`` (only temporarily in ``master``)
|
||||
- ...
|
||||
|
||||
Notes for package maintainers
|
||||
=============================
|
||||
|
||||
0. Please use our releases, not ``master``!
|
||||
|
||||
1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally.
|
||||
|
||||
2. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer.
|
||||
|
||||
3. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``.
|
||||
|
||||
4. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here.
|
||||
|
||||
Already ongoing efforts
|
||||
=======================
|
||||
|
||||
|
||||
Arch
|
||||
----
|
||||
|
||||
From our official releases:
|
||||
- https://www.archlinux.org/packages/community/any/python2-acme
|
||||
- https://www.archlinux.org/packages/community/any/certbot
|
||||
- https://www.archlinux.org/packages/community/any/certbot-apache
|
||||
- https://www.archlinux.org/packages/community/any/certbot-nginx
|
||||
- https://www.archlinux.org/packages/community/any/letshelp-certbot
|
||||
|
||||
From ``master``: https://aur.archlinux.org/packages/certbot-git
|
||||
|
||||
Debian (and its derivatives, including Ubuntu)
|
||||
------
|
||||
|
||||
https://packages.debian.org/sid/certbot
|
||||
https://packages.debian.org/sid/python-certbot
|
||||
https://packages.debian.org/sid/python-certbot-apache
|
||||
|
||||
Fedora
|
||||
------
|
||||
|
||||
In Fedora 23+.
|
||||
|
||||
- https://admin.fedoraproject.org/pkgdb/package/letsencrypt/
|
||||
- https://admin.fedoraproject.org/pkgdb/package/certbot/
|
||||
- https://admin.fedoraproject.org/pkgdb/package/python-acme/
|
||||
|
||||
FreeBSD
|
||||
-------
|
||||
|
||||
https://svnweb.freebsd.org/ports/head/security/py-certbot/
|
||||
|
||||
GNU Guix
|
||||
--------
|
||||
|
||||
- https://www.gnu.org/software/guix/package-list.html#certbot
|
||||
|
||||
OpenBSD
|
||||
-------
|
||||
|
||||
- http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/security/letsencrypt/client/
|
||||
|
||||
+1
-1
@@ -406,7 +406,7 @@ good reason to do so.
|
||||
|
||||
You should definitely read the :ref:`where-certs` section, in order to
|
||||
know how to manage the certs
|
||||
manually. https://github.com/certbot/certbot/wiki/Ciphersuite-guidance
|
||||
manually. `Our ciphersuites page <ciphers.html>`__
|
||||
provides some information about recommended ciphersuites. If none of
|
||||
these make much sense to you, you should definitely use the
|
||||
certbot-auto_ method, which enables you to use installer plugins
|
||||
|
||||
@@ -126,7 +126,7 @@ ExperimentalBootstrap() {
|
||||
$2
|
||||
fi
|
||||
else
|
||||
echo "WARNING: $1 support is very experimental at present..."
|
||||
echo "FATAL: $1 support is very experimental at present..."
|
||||
echo "if you would like to work on improving it, please ensure you have backups"
|
||||
echo "and then run this script again with the --debug flag!"
|
||||
exit 1
|
||||
|
||||
@@ -126,7 +126,7 @@ ExperimentalBootstrap() {
|
||||
$2
|
||||
fi
|
||||
else
|
||||
echo "WARNING: $1 support is very experimental at present..."
|
||||
echo "FATAL: $1 support is very experimental at present..."
|
||||
echo "if you would like to work on improving it, please ensure you have backups"
|
||||
echo "and then run this script again with the --debug flag!"
|
||||
exit 1
|
||||
|
||||
@@ -40,7 +40,6 @@ install_requires = [
|
||||
'configobj',
|
||||
'cryptography>=0.7', # load_pem_x509_certificate
|
||||
'parsedatetime>=1.3', # Calendar.parseDT
|
||||
'psutil>=2.2.1', # 2.1.0 for net_connections and 2.2.1 resolves #1080
|
||||
'PyOpenSSL',
|
||||
'pyrfc3339',
|
||||
'python2-pythondialog>=3.2.2rc1', # Debian squeeze support, cf. #280
|
||||
@@ -70,6 +69,7 @@ dev_extras = [
|
||||
'coverage',
|
||||
'nose',
|
||||
'pep8',
|
||||
'psutil>=2.2.1', # for tests, optional
|
||||
'pylint==1.4.2', # upstream #248
|
||||
'tox',
|
||||
'twine',
|
||||
|
||||
Reference in New Issue
Block a user