Merge branch 'master' into ecdsa

This commit is contained in:
Osiris Inferi 2016-06-16 12:20:09 +02:00
commit 86ac394dae
No known key found for this signature in database
GPG key ID: 590297AD5FAE2134
88 changed files with 1518 additions and 1139 deletions

1
.gitattributes vendored
View file

@ -5,3 +5,4 @@
*.jpeg binary
*.jpg binary
*.png binary
*.gz binary

View file

@ -21,12 +21,6 @@ persistent=yes
# usually to register additional checkers.
load-plugins=linter_plugin
# DEPRECATED
include-ids=no
# DEPRECATED
symbols=no
# Use multiple processes to speed up Pylint.
jobs=1

View file

@ -500,7 +500,7 @@ class DNS(_TokenChallenge):
"""
return DNSResponse(validation=self.gen_validation(
self, account_key, **kwargs))
account_key, **kwargs))
def validation_domain_name(self, name):
"""Domain name for TXT validation record.

View file

@ -572,7 +572,7 @@ class ClientNetworkTest(unittest.TestCase):
sess = mock.MagicMock()
self.net.session = sess
del self.net
sess.close.assert_called_once()
sess.close.assert_called_once_with()
@mock.patch('acme.client.requests')
def test_requests_error_passthrough(self, mock_requests):

View file

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

View file

@ -1,7 +1,6 @@
"""Class of Augeas Configurators."""
import logging
import augeas
from certbot import errors
from certbot import reverter
@ -29,12 +28,9 @@ class AugeasConfigurator(common.Plugin):
def __init__(self, *args, **kwargs):
super(AugeasConfigurator, self).__init__(*args, **kwargs)
self.aug = augeas.Augeas(
# specify a directory to load our preferred lens from
loadpath=constants.AUGEAS_LENS_DIR,
# Do not save backup (we do it ourselves), do not load
# anything by default
flags=(augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD))
# Placeholder for augeas
self.aug = None
self.save_notes = ""
# See if any temporary changes need to be recovered
@ -42,6 +38,16 @@ class AugeasConfigurator(common.Plugin):
# because this will change the underlying configuration and potential
# vhosts
self.reverter = reverter.Reverter(self.config)
def init_augeas(self):
""" Initialize the actual Augeas instance """
import augeas
self.aug = augeas.Augeas(
# specify a directory to load our preferred lens from
loadpath=constants.AUGEAS_LENS_DIR,
# Do not save backup (we do it ourselves), do not load
# anything by default
flags=(augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD))
self.recovery_routine()
def check_parsing_errors(self, lens):

View file

@ -15,7 +15,7 @@ from acme import challenges
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot.plugins import common
@ -106,8 +106,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
add("handle-sites", default=constants.os_constant("handle_sites"),
help="Let installer handle enabling sites for you." +
"(Only Ubuntu/Debian currently)")
le_util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
le_util.add_deprecated_argument(
util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
util.add_deprecated_argument(
add, argument_name="init-script", nargs=1)
def __init__(self, *args, **kwargs):
@ -150,8 +150,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:raises .errors.PluginError: If there is any other error
"""
# Perform the actual Augeas initialization to be able to react
try:
self.init_augeas()
except ImportError:
raise errors.NoInstallationError("Problem in Augeas installation")
# Verify Apache is installed
if not le_util.exe_exists(constants.os_constant("restart_cmd")[0]):
if not util.exe_exists(constants.os_constant("restart_cmd")[0]):
raise errors.NoInstallationError
# Make sure configuration is valid
@ -1521,14 +1527,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Generate reversal command.
# Try to be safe here... check that we can probably reverse before
# applying enmod command
if not le_util.exe_exists(self.conf("dismod")):
if not util.exe_exists(self.conf("dismod")):
raise errors.MisconfigurationError(
"Unable to find a2dismod, please make sure a2enmod and "
"a2dismod are configured correctly for certbot.")
self.reverter.register_undo_command(
temp, [self.conf("dismod"), mod_name])
le_util.run_script([self.conf("enmod"), mod_name])
util.run_script([self.conf("enmod"), mod_name])
def restart(self):
"""Runs a config test and reloads the Apache server.
@ -1547,7 +1553,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
le_util.run_script(constants.os_constant("restart_cmd"))
util.run_script(constants.os_constant("restart_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@ -1558,7 +1564,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
le_util.run_script(constants.os_constant("conftest_cmd"))
util.run_script(constants.os_constant("conftest_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@ -1574,8 +1580,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
stdout, _ = le_util.run_script(
constants.os_constant("version_cmd"))
stdout, _ = util.run_script(constants.os_constant("version_cmd"))
except errors.SubprocessError:
raise errors.PluginError(
"Unable to run %s -v" %

View file

@ -1,6 +1,6 @@
"""Apache plugin constants."""
import pkg_resources
from certbot import le_util
from certbot import util
CLI_DEFAULTS_DEBIAN = dict(
@ -78,6 +78,9 @@ CLI_DEFAULTS = {
"centos linux": CLI_DEFAULTS_CENTOS,
"fedora": CLI_DEFAULTS_CENTOS,
"red hat enterprise linux server": CLI_DEFAULTS_CENTOS,
"rhel": CLI_DEFAULTS_CENTOS,
"amazon": CLI_DEFAULTS_CENTOS,
"gentoo": CLI_DEFAULTS_GENTOO,
"gentoo base system": CLI_DEFAULTS_GENTOO,
"darwin": CLI_DEFAULTS_DARWIN,
}
@ -116,7 +119,7 @@ def os_constant(key):
:param key: name of cli constant
:return: value of constant for active os
"""
os_info = le_util.get_os_info()
os_info = util.get_os_info()
try:
constants = CLI_DEFAULTS[os_info[0].lower()]
except KeyError:

View file

@ -49,14 +49,24 @@ class MultipleVhostsTest(util.ApacheTest):
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
@mock.patch("certbot_apache.configurator.le_util.exe_exists")
@mock.patch("certbot_apache.configurator.util.exe_exists")
def test_prepare_no_install(self, mock_exe_exists):
mock_exe_exists.return_value = False
self.assertRaises(
errors.NoInstallationError, self.config.prepare)
@mock.patch("certbot_apache.augeas_configurator.AugeasConfigurator.init_augeas")
def test_prepare_no_augeas(self, mock_init_augeas):
""" Test augeas initialization ImportError """
def side_effect_error():
""" Side effect error for the test """
raise ImportError
mock_init_augeas.side_effect = side_effect_error
self.assertRaises(
errors.NoInstallationError, self.config.prepare)
@mock.patch("certbot_apache.parser.ApacheParser")
@mock.patch("certbot_apache.configurator.le_util.exe_exists")
@mock.patch("certbot_apache.configurator.util.exe_exists")
def test_prepare_version(self, mock_exe_exists, _):
mock_exe_exists.return_value = True
self.config.version = None
@ -67,7 +77,7 @@ class MultipleVhostsTest(util.ApacheTest):
errors.NotSupportedError, self.config.prepare)
@mock.patch("certbot_apache.parser.ApacheParser")
@mock.patch("certbot_apache.configurator.le_util.exe_exists")
@mock.patch("certbot_apache.configurator.util.exe_exists")
def test_prepare_old_aug(self, mock_exe_exists, _):
mock_exe_exists.return_value = True
self.config.config_test = mock.Mock()
@ -268,8 +278,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.is_site_enabled,
"irrelevant")
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
@mock.patch("certbot_apache.parser.subprocess.Popen")
def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script):
mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "")
@ -287,7 +297,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertRaises(
errors.NotSupportedError, self.config.enable_mod, "ssl")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.exe_exists")
def test_enable_mod_no_disable(self, mock_exe_exists):
mock_exe_exists.return_value = False
self.assertRaises(
@ -695,7 +705,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.cleanup([achall1, achall2])
self.assertTrue(mock_restart.called)
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.run_script")
def test_get_version(self, mock_script):
mock_script.return_value = (
"Server Version: Apache/2.4.2 (Debian)", "")
@ -717,21 +727,21 @@ class MultipleVhostsTest(util.ApacheTest):
mock_script.side_effect = errors.SubprocessError("Can't find program")
self.assertRaises(errors.PluginError, self.config.get_version)
@mock.patch("certbot_apache.configurator.le_util.run_script")
@mock.patch("certbot_apache.configurator.util.run_script")
def test_restart(self, _):
self.config.restart()
@mock.patch("certbot_apache.configurator.le_util.run_script")
@mock.patch("certbot_apache.configurator.util.run_script")
def test_restart_bad_process(self, mock_run_script):
mock_run_script.side_effect = [None, errors.SubprocessError]
self.assertRaises(errors.MisconfigurationError, self.config.restart)
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.run_script")
def test_config_test(self, _):
self.config.config_test()
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.run_script")
def test_config_test_bad_process(self, mock_run_script):
mock_run_script.side_effect = errors.SubprocessError
@ -771,7 +781,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot_apache.configurator.ApacheConfigurator._get_http_vhost")
@mock.patch("certbot_apache.display_ops.select_vhost")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.exe_exists")
def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get):
self.config.parser.modules.add("rewrite_module")
mock_exe.return_value = True
@ -792,8 +802,8 @@ class MultipleVhostsTest(util.ApacheTest):
errors.PluginError,
self.config.enhance, "certbot.demo", "unknown_enhancement")
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_ocsp_stapling(self, mock_exe, mock_run_script):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
@ -821,7 +831,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(len(stapling_cache_aug_path), 1)
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.exe_exists")
def test_ocsp_stapling_twice(self, mock_exe):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
@ -848,7 +858,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(len(stapling_cache_aug_path), 1)
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.exe_exists")
def test_ocsp_unsupported_apache_version(self, mock_exe):
mock_exe.return_value = True
self.config.parser.update_runtime_variables = mock.Mock()
@ -871,8 +881,8 @@ class MultipleVhostsTest(util.ApacheTest):
http_vh = self.config._get_http_vhost(ssl_vh)
self.assertTrue(http_vh.ssl == False)
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_http_header_hsts(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
@ -909,8 +919,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.enhance, "encryption-example.demo",
"ensure-http-header", "Strict-Transport-Security")
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_http_header_uir(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
@ -947,8 +957,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.enhance, "encryption-example.demo",
"ensure-http-header", "Upgrade-Insecure-Requests")
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_well_formed_http(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
@ -991,8 +1001,8 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.assertTrue(self.config._is_rewrite_engine_on(self.vh_truth[3]))
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_with_existing_rewrite(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True

View file

@ -8,19 +8,19 @@ from certbot_apache import constants
class ConstantsTest(unittest.TestCase):
@mock.patch("certbot.le_util.get_os_info")
@mock.patch("certbot.util.get_os_info")
def test_get_debian_value(self, os_info):
os_info.return_value = ('Debian', '', '')
self.assertEqual(constants.os_constant("vhost_root"),
"/etc/apache2/sites-available")
@mock.patch("certbot.le_util.get_os_info")
@mock.patch("certbot.util.get_os_info")
def test_get_centos_value(self, os_info):
os_info.return_value = ('CentOS Linux', '', '')
self.assertEqual(constants.os_constant("vhost_root"),
"/etc/httpd/conf.d")
@mock.patch("certbot.le_util.get_os_info")
@mock.patch("certbot.util.get_os_info")
def test_get_default_value(self, os_info):
os_info.return_value = ('Nonexistent Linux', '', '')
self.assertEqual(constants.os_constant("vhost_root"),

View file

@ -38,8 +38,8 @@ class TlsSniPerformTest(util.ApacheTest):
resp = self.sni.perform()
self.assertEqual(len(resp), 0)
@mock.patch("certbot.le_util.exe_exists")
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.exe_exists")
@mock.patch("certbot.util.run_script")
def test_perform1(self, _, mock_exists):
mock_register = mock.Mock()
self.sni.configurator.reverter.register_undo_command = mock_register

View file

@ -95,8 +95,8 @@ def get_apache_configurator(
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
work_dir=work_dir)
with mock.patch("certbot_apache.configurator.le_util.run_script"):
with mock.patch("certbot_apache.configurator.le_util."
with mock.patch("certbot_apache.configurator.util.run_script"):
with mock.patch("certbot_apache.configurator.util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
with mock.patch("certbot_apache.parser.ApacheParser."

View file

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

View file

@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.6.0"
LE_AUTO_VERSION="0.8.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -38,17 +38,6 @@ Help for certbot itself cannot be provided until it is installed.
All arguments are accepted and forwarded to the Certbot client when run."
while getopts ":hnv" arg; do
case $arg in
h)
HELP=1;;
n)
ASSUME_YES=1;;
v)
VERBOSE=1;;
esac
done
for arg in "$@" ; do
case "$arg" in
--debug)
@ -65,9 +54,26 @@ for arg in "$@" ; do
ASSUME_YES=1;;
--verbose)
VERBOSE=1;;
-[!-]*)
while getopts ":hnv" short_arg $arg; do
case "$short_arg" in
h)
HELP=1;;
n)
ASSUME_YES=1;;
v)
VERBOSE=1;;
esac
done;;
esac
done
if [ $BASENAME = "letsencrypt-auto" ]; then
# letsencrypt-auto does not respect --help or --yes for backwards compatibility
ASSUME_YES=1
HELP=0
fi
# certbot-auto needs root access to bootstrap OS dependencies, and
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
@ -107,12 +113,6 @@ else
SUDO=
fi
if [ $BASENAME = "letsencrypt-auto" ]; then
# letsencrypt-auto does not respect --help or --yes for backwards compatibility
ASSUME_YES=1
HELP=0
fi
ExperimentalBootstrap() {
# Arguments: Platform name, bootstrap function name
if [ "$DEBUG" = 1 ]; then
@ -425,7 +425,8 @@ BootstrapMac() {
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" ]; then
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.
# python.org, MacPorts or HomeBrew Python installations should all be OK.
echo "Installing python..."
@ -435,7 +436,8 @@ BootstrapMac() {
# Workaround for _dlopen not finding augeas on OS X
if [ "$pkgman" = "port" ] && ! [ -e "/usr/local/lib/libaugeas.dylib" ] && [ -e "/opt/local/lib/libaugeas.dylib" ]; then
echo "Applying augeas workaround"
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib
$SUDO mkdir -p /usr/local/lib/
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib/
fi
if ! hash pip 2>/dev/null; then
@ -451,6 +453,11 @@ BootstrapMac() {
fi
}
BootstrapSmartOS() {
pkgin update
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
}
# Install required OS packages:
Bootstrap() {
@ -483,8 +490,10 @@ Bootstrap() {
ExperimentalBootstrap "FreeBSD" BootstrapFreeBsd
elif uname | grep -iq Darwin ; then
ExperimentalBootstrap "Mac OS X" BootstrapMac
elif grep -iq "Amazon Linux" /etc/issue ; then
elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then
ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
elif [ -f /etc/product ] && grep -q "Joyent Instance" /etc/product ; then
ExperimentalBootstrap "Joyent SmartOS Zone" BootstrapSmartOS
else
echo "Sorry, I don't know how to bootstrap Certbot on your operating system!"
echo
@ -523,6 +532,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
@ -703,24 +713,21 @@ zope.interface==4.1.3 \
mock==1.0.1 \
--hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f \
--hash=sha256:8f83080daa249d036cbccfb8ae5cc6ff007b88d6d937521371afabe7b19badbc
letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.6.0 \
--hash=sha256:cbe4e7a340a19725a8740ed86e30abdbe18fc22c4c6022b7a8e56642d502bcc3 \
--hash=sha256:ec4e6009dfbd629b58473eb06bbebfd9fb2a79fc8831c149e9205bc38a98ecc6
certbot==0.6.0 \
--hash=sha256:a893632d228864b0a751db9f3fdd93439ed34b988ea21b64fb0f0fa2ceded6a2 \
--hash=sha256:80b0b7dc5afeec2816ef638a61e7c628d73cd72666eebf4984be426d1c2b492d
certbot-apache==0.6.0 \
--hash=sha256:0ab077f0913b81ed5c1b141c3a7c4c0228ef3738d8d61a93db794d9a80718d43 \
--hash=sha256:1cfbe751209079a803758f472200816fac559f2a36fdd582d25e3ba5601423a1
letsencrypt==0.6.0 \
--hash=sha256:93196c7dcd57272a753e525d145c5a9987c8968c22ec954bcf83dcc9d2499a76 \
--hash=sha256:a16d6c395f1bf5fd61a28ef83dc78f42dbecbad9d00be6236f2ad8915645c154
letsencrypt-apache==0.6.0 \
--hash=sha256:02fadc52a0796e53978c508beec9c53e1fc047660240832b9bde5d53ab3a1379 \
--hash=sha256:1c5522d94d7750bdb9bfa6201d2c263e914f662c9d0079e673167233cf4364f1
acme==0.8.0 \
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
certbot==0.8.0 \
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
certbot-apache==0.8.0 \
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -880,7 +887,6 @@ UNLIKELY_EOF
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
PIP_STATUS=$?
set -e
rm -rf "$TEMP_DIR"
if [ "$PIP_STATUS" != 0 ]; then
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages:"
@ -890,14 +896,16 @@ UNLIKELY_EOF
fi
echo "Installation succeeded."
fi
echo "Requesting root privileges to run certbot..."
if [ -n "$SUDO" ]; then
# SUDO is su wrapper or sudo
echo "Requesting root privileges to run certbot..."
echo " $VENV_BIN/letsencrypt" "$@"
fi
if [ -z "$SUDO_ENV" ] ; then
# SUDO is su wrapper / noop
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
$SUDO "$VENV_BIN/letsencrypt" "$@"
else
# sudo
echo " " $SUDO "$SUDO_ENV" "$VENV_BIN/letsencrypt" "$@"
$SUDO "$SUDO_ENV" "$VENV_BIN/letsencrypt" "$@"
fi
@ -923,8 +931,8 @@ else
fi
if [ "$NO_SELF_UPGRADE" != 1 ]; then
echo "Checking for new version..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
"""Do downloading and JSON parsing without additional dependencies. ::
@ -997,7 +1005,7 @@ def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get(
environ.get('LE_AUTO_JSON_URL',
'https://pypi.python.org/pypi/letsencrypt/json')))
'https://pypi.python.org/pypi/certbot/json')))
# metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most
@ -1016,7 +1024,7 @@ def verified_new_le_auto(get, tag, temp_dir):
"""
le_auto_dir = environ.get(
'LE_AUTO_DIR_TEMPLATE',
'https://raw.githubusercontent.com/letsencrypt/letsencrypt/%s/'
'https://raw.githubusercontent.com/certbot/certbot/%s/'
'letsencrypt-auto-source/') % tag
write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto')
write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig')
@ -1079,8 +1087,6 @@ UNLIKELY_EOF
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.
fi # Self-upgrading is allowed.

View file

@ -1,14 +0,0 @@
#!/bin/bash
# An extremely simplified version of `a2enmod` for disabling modules in the
# httpd docker image. First argument is the server_root and the second is the
# module to be disabled.
apache_confdir=$1
module=$2
sed -i "/.*"$module".*/d" "$apache_confdir/test.conf"
enabled_conf="$apache_confdir/mods-enabled/"$module".conf"
if [ -e "$enabled_conf" ]
then
rm $enabled_conf
fi

View file

@ -1,18 +0,0 @@
#!/bin/bash
# An extremely simplified version of `a2enmod` for enabling modules in the
# httpd docker image. First argument is the Apache ServerRoot which should be
# an absolute path. The second is the module to be enabled, such as `ssl`.
confdir=$1
module=$2
echo "LoadModule ${module}_module " \
"/usr/local/apache2/modules/mod_${module}.so" >> "${confdir}/test.conf"
availbase="/mods-available/${module}.conf"
availconf=$confdir$availbase
enabldir="$confdir/mods-enabled"
enablconf="$enabldir/${module}.conf"
if [ -e $availconf -a -d $enabldir -a ! -e $enablconf ]
then
ln -s "..$availbase" $enablconf
fi

View file

@ -1,63 +0,0 @@
"""Proxies ApacheConfigurator for Apache 2.4 tests"""
import zope.interface
from certbot_compatibility_test import errors
from certbot_compatibility_test import interfaces
from certbot_compatibility_test.configurators.apache import common as apache_common
# The docker image doesn't actually have the watchdog module, but unless the
# config uses mod_heartbeat or mod_heartmonitor (which aren't installed and
# therefore the config won't be loaded), I believe this isn't a problem
# http://httpd.apache.org/docs/2.4/mod/mod_watchdog.html
STATIC_MODULES = set(["core", "so", "http", "mpm_event", "watchdog"])
SHARED_MODULES = {
"log_config", "logio", "version", "unixd", "access_compat", "actions",
"alias", "allowmethods", "auth_basic", "auth_digest", "auth_form",
"authn_anon", "authn_core", "authn_dbd", "authn_dbm", "authn_file",
"authn_socache", "authnz_ldap", "authz_core", "authz_dbd", "authz_dbm",
"authz_groupfile", "authz_host", "authz_owner", "authz_user", "autoindex",
"buffer", "cache", "cache_disk", "cache_socache", "cgid", "dav", "dav_fs",
"dbd", "deflate", "dir", "dumpio", "env", "expires", "ext_filter",
"file_cache", "filter", "headers", "include", "info", "lbmethod_bybusyness",
"lbmethod_byrequests", "lbmethod_bytraffic", "lbmethod_heartbeat", "ldap",
"log_debug", "macro", "mime", "negotiation", "proxy", "proxy_ajp",
"proxy_balancer", "proxy_connect", "proxy_express", "proxy_fcgi",
"proxy_ftp", "proxy_http", "proxy_scgi", "proxy_wstunnel", "ratelimit",
"remoteip", "reqtimeout", "request", "rewrite", "sed", "session",
"session_cookie", "session_crypto", "session_dbd", "setenvif",
"slotmem_shm", "socache_dbm", "socache_memcache", "socache_shmcb",
"speling", "ssl", "status", "substitute", "unique_id", "userdir",
"vhost_alias"}
@zope.interface.implementer(interfaces.IConfiguratorProxy)
class Proxy(apache_common.Proxy):
"""Wraps the ApacheConfigurator for Apache 2.4 tests"""
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
super(Proxy, self).__init__(args)
# Running init isn't ideal, but the Docker container needs to survive
# Apache restarts
self.start_docker("bradmw/apache2.4", "init")
def preprocess_config(self, server_root):
"""Prepares the configuration for use in the Docker"""
super(Proxy, self).preprocess_config(server_root)
if self.version[1] != 4:
raise errors.Error("Apache version not 2.4")
with open(self.test_conf, "a") as f:
for module in self.modules:
if module not in STATIC_MODULES:
if module in SHARED_MODULES:
f.write(
"LoadModule {0}_module /usr/local/apache2/modules/"
"mod_{0}.so\n".format(module))
else:
raise errors.Error(
"Unsupported module {0}".format(module))

View file

@ -1,6 +1,7 @@
"""Provides a common base for Apache proxies"""
import re
import os
import shutil
import subprocess
import mock
@ -9,6 +10,7 @@ import zope.interface
from certbot import configuration
from certbot import errors as le_errors
from certbot_apache import configurator
from certbot_apache import constants
from certbot_compatibility_test import errors
from certbot_compatibility_test import interfaces
from certbot_compatibility_test import util
@ -29,58 +31,14 @@ class Proxy(configurators_common.Proxy):
super(Proxy, self).__init__(args)
self.le_config.apache_le_vhost_ext = "-le-ssl.conf"
self._setup_mock()
self.modules = self.server_root = self.test_conf = self.version = None
self._apache_configurator = self._all_names = self._test_names = None
def _setup_mock(self):
"""Replaces specific modules with mock.MagicMock"""
mock_subprocess = mock.MagicMock()
mock_subprocess.check_call = self.check_call
mock_subprocess.Popen = self.popen
mock.patch(
"certbot_apache.configurator.subprocess",
mock_subprocess).start()
mock.patch(
"certbot_apache.parser.subprocess",
mock_subprocess).start()
mock.patch(
"certbot.le_util.subprocess",
mock_subprocess).start()
mock.patch(
"certbot_apache.configurator.le_util.exe_exists",
_is_apache_command).start()
patch = mock.patch(
"certbot_apache.configurator.display_ops.select_vhost")
mock_display = patch.start()
mock_display.side_effect = le_errors.PluginError(
"Unable to determine vhost")
def check_call(self, command, *args, **kwargs):
"""If command is an Apache command, command is executed in the
running docker image. Otherwise, subprocess.check_call is used.
"""
if _is_apache_command(command):
command = _modify_command(command)
return super(Proxy, self).check_call(command, *args, **kwargs)
else:
return subprocess.check_call(command, *args, **kwargs)
def popen(self, command, *args, **kwargs):
"""If command is an Apache command, command is executed in the
running docker image. Otherwise, subprocess.Popen is used.
"""
if _is_apache_command(command):
command = _modify_command(command)
return super(Proxy, self).popen(command, *args, **kwargs)
else:
return subprocess.Popen(command, *args, **kwargs)
def __getattr__(self, name):
"""Wraps the Apache Configurator methods"""
method = getattr(self._apache_configurator, name, None)
@ -91,29 +49,20 @@ class Proxy(configurators_common.Proxy):
def load_config(self):
"""Loads the next configuration for the plugin to test"""
if hasattr(self.le_config, "apache_init_script"):
try:
self.check_call([self.le_config.apache_init_script, "stop"])
except errors.Error:
raise errors.Error(
"Failed to stop previous apache config from running")
config = super(Proxy, self).load_config()
self.modules = _get_modules(config)
self.version = _get_version(config)
self._all_names, self._test_names = _get_names(config)
server_root = _get_server_root(config)
with open(os.path.join(config, "config_file")) as f:
config_file = os.path.join(server_root, f.readline().rstrip())
self.test_conf = _create_test_conf(server_root, config_file)
# with open(os.path.join(config, "config_file")) as f:
# config_file = os.path.join(server_root, f.readline().rstrip())
shutil.rmtree("/etc/apache2")
shutil.copytree(server_root, "/etc/apache2", symlinks=True)
self.preprocess_config(server_root)
self._prepare_configurator(server_root, config_file)
self._prepare_configurator()
try:
self.check_call("apachectl -d {0} -f {1} -k start".format(
server_root, config_file))
subprocess.check_call("apachectl -k start".split())
except errors.Error:
raise errors.Error(
"Apache failed to load {0} before tests started".format(
@ -121,34 +70,13 @@ class Proxy(configurators_common.Proxy):
return config
def preprocess_config(self, server_root):
# pylint: disable=anomalous-backslash-in-string, no-self-use
"""Prepares the configuration for use in the Docker"""
find = subprocess.Popen(
["find", server_root, "-type", "f"],
stdout=subprocess.PIPE)
subprocess.check_call([
"xargs", "sed", "-e", "s/DocumentRoot.*/DocumentRoot "
"\/usr\/local\/apache2\/htdocs/I",
"-e", "s/SSLPassPhraseDialog.*/SSLPassPhraseDialog builtin/I",
"-e", "s/TypesConfig.*/TypesConfig "
"\/usr\/local\/apache2\/conf\/mime.types/I",
"-e", "s/LoadModule/#LoadModule/I",
"-e", "s/SSLCertificateFile.*/SSLCertificateFile "
"\/usr\/local\/apache2\/conf\/empty_cert.pem/I",
"-e", "s/SSLCertificateKeyFile.*/SSLCertificateKeyFile "
"\/usr\/local\/apache2\/conf\/rsa1024_key2.pem/I",
"-i"], stdin=find.stdout)
def _prepare_configurator(self, server_root, config_file):
def _prepare_configurator(self):
"""Prepares the Apache plugin for testing"""
self.le_config.apache_server_root = server_root
self.le_config.apache_ctl = "apachectl -d {0} -f {1}".format(
server_root, config_file)
self.le_config.apache_enmod = "a2enmod.sh {0}".format(server_root)
self.le_config.apache_dismod = "a2dismod.sh {0}".format(server_root)
self.le_config.apache_init_script = self.le_config.apache_ctl + " -k"
for k in constants.CLI_DEFAULTS_DEBIAN.keys():
setattr(self.le_config, "apache_" + k, constants.os_constant(k))
# An alias
self.le_config.apache_handle_modules = self.le_config.apache_handle_mods
self._apache_configurator = configurator.ApacheConfigurator(
config=configuration.NamespaceConfig(self.le_config),
@ -183,39 +111,6 @@ class Proxy(configurators_common.Proxy):
domain, cert_path, key_path, chain_path, fullchain_path)
def _is_apache_command(command):
"""Returns true if command is an Apache command"""
if isinstance(command, list):
command = command[0]
for apache_command in APACHE_COMMANDS:
if command.startswith(apache_command):
return True
return False
def _modify_command(command):
"""Modifies command so configtest works inside the docker image"""
if isinstance(command, list):
for i in xrange(len(command)):
if command[i] == "configtest":
command[i] = "-t"
else:
command = command.replace("configtest", "-t")
return command
def _create_test_conf(server_root, apache_config):
"""Creates a test config file and adds it to the Apache config"""
test_conf = os.path.join(server_root, "test.conf")
open(test_conf, "w").close()
subprocess.check_call(
["sed", "-i", "1iInclude test.conf", apache_config])
return test_conf
def _get_server_root(config):
"""Returns the server root directory in config"""
subdirs = [
@ -223,7 +118,7 @@ def _get_server_root(config):
if os.path.isdir(os.path.join(config, name))]
if len(subdirs) != 1:
errors.Error("Malformed configuration directiory {0}".format(config))
errors.Error("Malformed configuration directory {0}".format(config))
return os.path.join(config, subdirs[0].rstrip())
@ -251,34 +146,3 @@ def _get_names(config):
words[1].find(".") != -1):
all_names.add(words[1])
return all_names, non_ip_names
def _get_modules(config):
"""Returns the list of modules found in module_list"""
modules = []
with open(os.path.join(config, "modules")) as f:
for line in f:
# Modules list is indented, everything else is headers/footers
if line[0].isspace():
words = line.split()
# Modules redundantly end in "_module" which we can discard
modules.append(words[0][:-7])
return modules
def _get_version(config):
"""Return version of Apache Server.
Version is returned as tuple. (ie. 2.4.7 = (2, 4, 7)). Code taken from
the Apache plugin.
"""
with open(os.path.join(config, "version")) as f:
# Should be on first line of input
matches = APACHE_VERSION_REGEX.findall(f.readline())
if len(matches) != 1:
raise errors.Error("Unable to find Apache version")
return tuple([int(i) for i in matches[0].split(".")])

View file

@ -4,10 +4,7 @@ import os
import shutil
import tempfile
import docker
from certbot import constants
from certbot_compatibility_test import errors
from certbot_compatibility_test import util
@ -18,20 +15,9 @@ class Proxy(object):
# pylint: disable=too-many-instance-attributes
"""A common base for compatibility test configurators"""
_NOT_ADDED_ARGS = True
@classmethod
def add_parser_arguments(cls, parser):
"""Adds command line arguments needed by the plugin"""
if Proxy._NOT_ADDED_ARGS:
group = parser.add_argument_group("docker")
group.add_argument(
"--docker-url", default="unix://var/run/docker.sock",
help="URL of the docker server")
group.add_argument(
"--no-remove", action="store_true",
help="do not delete container on program exit")
Proxy._NOT_ADDED_ARGS = False
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
@ -43,10 +29,8 @@ class Proxy(object):
for config in os.listdir(config_dir)]
self.args = args
self._docker_client = docker.Client(
base_url=self.args.docker_url, version="auto")
self.http_port, self.https_port = util.get_two_free_ports()
self._container_id = None
self.http_port = 80
self.https_port = 443
def has_more_configs(self):
"""Returns true if there are more configs to test"""
@ -54,9 +38,6 @@ class Proxy(object):
def cleanup_from_tests(self):
"""Performs any necessary cleanup from running plugin tests"""
self._docker_client.stop(self._container_id, 0)
if not self.args.no_remove:
self._docker_client.remove_container(self._container_id)
def load_config(self):
"""Returns the next config directory to be tested"""
@ -65,67 +46,6 @@ class Proxy(object):
os.makedirs(backup)
return self._configs.pop()
def start_docker(self, image_name, command):
"""Creates and runs a Docker container with the specified image"""
logger.warning("Pulling Docker image. This may take a minute.")
for line in self._docker_client.pull(image_name, stream=True):
logger.debug(line)
host_config = docker.utils.create_host_config(
binds={self._temp_dir: {"bind": self._temp_dir, "mode": "rw"}},
port_bindings={
80: ("127.0.0.1", self.http_port),
443: ("127.0.0.1", self.https_port)},)
container = self._docker_client.create_container(
image_name, command, ports=[80, 443], volumes=self._temp_dir,
host_config=host_config)
if container["Warnings"]:
logger.warning(container["Warnings"])
self._container_id = container["Id"]
self._docker_client.start(self._container_id)
def check_call(self, command, *args, **kwargs):
# pylint: disable=unused-argument
"""Simulates a call to check_call but executes the command in the
running docker image
"""
if self.popen(command).returncode:
raise errors.Error(
"{0} exited with a nonzero value".format(command))
def popen(self, command, *args, **kwargs):
# pylint: disable=unused-argument
"""Simulates a call to Popen but executes the command in the
running docker image
"""
class SimplePopen(object):
# pylint: disable=too-few-public-methods
"""Simplified Popen object"""
def __init__(self, returncode, output):
self.returncode = returncode
self._stdout = output
self._stderr = output
def communicate(self):
"""Returns stdout and stderr"""
return self._stdout, self._stderr
if isinstance(command, list):
command = " ".join(command)
returncode, output = self.execute_in_docker(command)
return SimplePopen(returncode, output)
def execute_in_docker(self, command):
"""Executes command inside the running docker image"""
logger.debug("Executing '%s'", command)
exec_id = self._docker_client.exec_create(self._container_id, command)
output = self._docker_client.exec_start(exec_id)
returncode = self._docker_client.exec_inspect(exec_id)["ExitCode"]
return returncode, output
def copy_certs_and_keys(self, cert_path, key_path, chain_path=None):
"""Copies certs and keys into the temporary directory"""
cert_and_key_dir = os.path.join(self._temp_dir, "certs_and_keys")

View file

@ -7,6 +7,7 @@ import os
import shutil
import tempfile
import time
import sys
import OpenSSL
@ -21,17 +22,17 @@ from certbot_compatibility_test import errors
from certbot_compatibility_test import util
from certbot_compatibility_test import validator
from certbot_compatibility_test.configurators.apache import apache24
from certbot_compatibility_test.configurators.apache import common
DESCRIPTION = """
Tests Certbot plugins against different server configuratons. It is
assumed that Docker is already installed. If no test types is specified, all
Tests Certbot plugins against different server configurations. It is
assumed that Docker is already installed. If no test type is specified, all
tests that the plugin supports are performed.
"""
PLUGINS = {"apache": apache24.Proxy}
PLUGINS = {"apache": common.Proxy}
logger = logging.getLogger(__name__)
@ -61,8 +62,8 @@ def test_authenticator(plugin, config, temp_dir):
"Plugin failed to complete %s for %s in %s",
type(achalls[i]), achalls[i].domain, config)
success = False
elif isinstance(responses[i], challenges.TLSSNI01):
verify = functools.partial(responses[i].simple_verify, achalls[i],
elif isinstance(responses[i], challenges.TLSSNI01Response):
verify = functools.partial(responses[i].simple_verify, achalls[i].chall,
achalls[i].domain,
util.JWK.public_key(),
host="127.0.0.1",
@ -142,7 +143,8 @@ def test_deploy_cert(plugin, temp_dir, domains):
for domain in domains:
try:
plugin.deploy_cert(domain, cert_path, util.KEY_PATH)
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path)
plugin.save() # Needed by the Apache plugin
except le_errors.Error as error:
logger.error("Plugin failed to deploy ceritificate for %s:", domain)
logger.exception(error)
@ -177,6 +179,7 @@ def test_enhancements(plugin, domains):
for domain in domains:
try:
plugin.enhance(domain, "redirect")
plugin.save() # Needed by the Apache plugin
except le_errors.PluginError as error:
# Don't immediately fail because a redirect may already be enabled
logger.warning("Plugin failed to enable redirect for %s:", domain)
@ -341,7 +344,7 @@ def main():
temp_dir = tempfile.mkdtemp()
plugin = PLUGINS[args.plugin](args)
try:
plugin.execute_in_docker("mkdir -p /var/log/apache2")
overall_success = True
while plugin.has_more_configs():
success = True
@ -360,10 +363,18 @@ def main():
if success:
logger.info("All tests on %s succeeded", config)
else:
overall_success = False
logger.error("Tests on %s failed", config)
finally:
plugin.cleanup_from_tests()
if overall_success:
logger.warn("All compatibility tests succeeded")
sys.exit(0)
else:
logger.warn("One or more compatibility tests failed")
sys.exit(1)
if __name__ == "__main__":
main()

View file

@ -1,11 +1,9 @@
"""Utility functions for Certbot plugin tests."""
import argparse
import copy
import contextlib
import os
import re
import shutil
import socket
import tarfile
from acme import jose
@ -52,13 +50,3 @@ def extract_configs(configs, parent_dir):
raise errors.Error("Unknown configurations file type")
return config_dir
def get_two_free_ports():
"""Returns two free ports to use for the tests"""
with contextlib.closing(socket.socket()) as sock1:
with contextlib.closing(socket.socket()) as sock2:
sock1.bind(("", 0))
sock2.bind(("", 0))
return sock1.getsockname()[1], sock2.getsockname()[1]

View file

@ -4,12 +4,11 @@ from setuptools import setup
from setuptools import find_packages
version = '0.7.0.dev0'
version = '0.9.0.dev0'
install_requires = [
'certbot=={0}'.format(version),
'certbot-apache=={0}'.format(version),
'docker-py',
'certbot',
'certbot-apache',
'requests',
'zope.interface',
]

View file

@ -17,7 +17,7 @@ from certbot import constants as core_constants
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot import reverter
from certbot.plugins import common
@ -111,7 +111,7 @@ class NginxConfigurator(common.Plugin):
:raises .errors.MisconfigurationError: If Nginx is misconfigured
"""
# Verify Nginx is installed
if not le_util.exe_exists(self.conf('ctl')):
if not util.exe_exists(self.conf('ctl')):
raise errors.NoInstallationError
# Make sure configuration is valid
@ -319,7 +319,7 @@ class NginxConfigurator(common.Plugin):
cert = acme_crypto_util.gen_ss_cert(key, domains=[socket.gethostname()])
cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, cert)
cert_file, cert_path = le_util.unique_file(os.path.join(tmp_dir, "cert.pem"))
cert_file, cert_path = util.unique_file(os.path.join(tmp_dir, "cert.pem"))
with cert_file:
cert_file.write(cert_pem)
return cert_path, le_key.file
@ -427,7 +427,7 @@ class NginxConfigurator(common.Plugin):
"""
try:
le_util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"])
util.run_script([self.conf('ctl'), "-c", self.nginx_conf, "-t"])
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@ -440,11 +440,11 @@ class NginxConfigurator(common.Plugin):
"""
uid = os.geteuid()
le_util.make_or_verify_dir(
util.make_or_verify_dir(
self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid)
le_util.make_or_verify_dir(
util.make_or_verify_dir(
self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid)
le_util.make_or_verify_dir(
util.make_or_verify_dir(
self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid)
def get_version(self):

View file

@ -30,7 +30,7 @@ class NginxConfiguratorTest(util.NginxTest):
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
@mock.patch("certbot_nginx.configurator.le_util.exe_exists")
@mock.patch("certbot_nginx.configurator.util.exe_exists")
def test_prepare_no_install(self, mock_exe_exists):
mock_exe_exists.return_value = False
self.assertRaises(
@ -40,7 +40,7 @@ class NginxConfiguratorTest(util.NginxTest):
self.assertEquals((1, 6, 2), self.config.version)
self.assertEquals(5, len(self.config.parser.parsed))
@mock.patch("certbot_nginx.configurator.le_util.exe_exists")
@mock.patch("certbot_nginx.configurator.util.exe_exists")
@mock.patch("certbot_nginx.configurator.subprocess.Popen")
def test_prepare_initializes_version(self, mock_popen, mock_exe_exists):
mock_popen().communicate.return_value = (
@ -362,11 +362,11 @@ class NginxConfiguratorTest(util.NginxTest):
mock_popen.side_effect = OSError("Can't find program")
self.assertRaises(errors.MisconfigurationError, self.config.restart)
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.run_script")
def test_config_test(self, _):
self.config.config_test()
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.run_script")
def test_config_test_bad_process(self, mock_run_script):
mock_run_script.side_effect = errors.SubprocessError
self.assertRaises(errors.MisconfigurationError, self.config.config_test)

View file

@ -51,7 +51,7 @@ def get_nginx_configurator(
with mock.patch("certbot_nginx.configurator.NginxConfigurator."
"config_test"):
with mock.patch("certbot_nginx.configurator.le_util."
with mock.patch("certbot_nginx.configurator.util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
config = configurator.NginxConfigurator(

View file

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

View file

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

View file

@ -16,7 +16,7 @@ from acme import messages
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
logger = logging.getLogger(__name__)
@ -130,7 +130,7 @@ class AccountFileStorage(interfaces.AccountStorage):
"""
def __init__(self, config):
self.config = config
le_util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid(),
util.make_or_verify_dir(config.accounts_dir, 0o700, os.geteuid(),
self.config.strict_permissions)
def _account_dir_path(self, account_id):
@ -186,16 +186,29 @@ class AccountFileStorage(interfaces.AccountStorage):
return acc
def save(self, account):
self._save(account, regr_only=False)
def save_regr(self, account):
"""Save the registration resource.
:param Account account: account whose regr should be saved
"""
self._save(account, regr_only=True)
def _save(self, account, regr_only):
account_dir_path = self._account_dir_path(account.id)
le_util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(),
self.config.strict_permissions)
util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(),
self.config.strict_permissions)
try:
with open(self._regr_path(account_dir_path), "w") as regr_file:
regr_file.write(account.regr.json_dumps())
with le_util.safe_open(self._key_path(account_dir_path),
"w", chmod=0o400) as key_file:
key_file.write(account.key.json_dumps())
with open(self._metadata_path(account_dir_path), "w") as metadata_file:
metadata_file.write(account.meta.json_dumps())
if not regr_only:
with util.safe_open(self._key_path(account_dir_path),
"w", chmod=0o400) as key_file:
key_file.write(account.key.json_dumps())
with open(self._metadata_path(
account_dir_path), "w") as metadata_file:
metadata_file.write(account.meta.json_dumps())
except IOError as error:
raise errors.AccountStorageError(error)

View file

@ -1,15 +1,14 @@
"""Certbot command line argument & config processing."""
from __future__ import print_function
import argparse
import copy
import glob
import logging
import logging.handlers
import os
import sys
import traceback
import configargparse
import OpenSSL
import six
import certbot
@ -19,7 +18,7 @@ from certbot import crypto_util
from certbot import errors
from certbot import hooks
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot.plugins import disco as plugins_disco
import certbot.plugins.selection as plugin_selection
@ -63,6 +62,7 @@ cert. Major SUBCOMMANDS are:
install Install a previously obtained cert in a server
renew Renew previously obtained certs that are near expiry
revoke Revoke a previously obtained certificate
register Perform tasks related to registering with the CA
rollback Rollback server configuration changes made during install
config_changes Show changes made to server config during installation
plugins Display information about installed plugins
@ -88,7 +88,8 @@ More detailed help:
the available topics are:
all, automation, paths, security, testing, or any of the subcommands or
plugins (certonly, install, nginx, apache, standalone, webroot, etc)
plugins (certonly, install, register, nginx, apache, standalone, webroot,
etc.)
"""
@ -211,6 +212,35 @@ def set_by_cli(var):
set_by_cli.detector = None
def has_default_value(option, value):
"""Does option have the default value?
If the default value of option is not known, False is returned.
:param str option: configuration variable being considered
:param value: value of the configuration variable named option
:returns: True if option has the default value, otherwise, False
:rtype: bool
"""
return (option in helpful_parser.defaults and
helpful_parser.defaults[option] == value)
def option_was_set(option, value):
"""Was option set by the user or does it differ from the default?
:param str option: configuration variable being considered
:param value: value of the configuration variable named option
:returns: True if the option was set, otherwise, False
:rtype: bool
"""
return set_by_cli(option) or not has_default_value(option, value)
def argparse_type(variable):
"Return our argparse type function for a config variable (default: str)"
# pylint: disable=protected-access
@ -287,8 +317,9 @@ class HelpfulArgumentParser(object):
self.VERBS = {"auth": main.obtain_cert, "certonly": main.obtain_cert,
"config_changes": main.config_changes, "run": main.run,
"install": main.install, "plugins": main.plugins_cmd,
"renew": main.renew, "revoke": main.revoke,
"rollback": main.rollback, "everything": main.run}
"register": main.register, "renew": main.renew,
"revoke": main.revoke, "rollback": main.rollback,
"everything": main.run}
# List of topics for which additional help can be provided
HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + list(self.VERBS)
@ -319,6 +350,7 @@ class HelpfulArgumentParser(object):
sys.exit(0)
self.visible_topics = self.determine_help_topics(self.help_arg)
self.groups = {} # elements are added by .add_group()
self.defaults = {} # elements are added by .parse_args()
def parse_args(self):
"""Parses command line arguments and returns the result.
@ -334,38 +366,60 @@ class HelpfulArgumentParser(object):
if self.detect_defaults:
return parsed_args
self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key)))
for key in vars(parsed_args))
# Do any post-parsing homework here
if self.verb == "renew" and not parsed_args.dialog_mode:
parsed_args.noninteractive_mode = True
if parsed_args.staging or parsed_args.dry_run:
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
conflicts = ["--staging"] if parsed_args.staging else []
conflicts += ["--dry-run"] if parsed_args.dry_run else []
raise errors.Error("--server value conflicts with {0}".format(
" and ".join(conflicts)))
parsed_args.server = constants.STAGING_URI
if parsed_args.dry_run:
if self.verb not in ["certonly", "renew"]:
raise errors.Error("--dry-run currently only works with the "
"'certonly' or 'renew' subcommands (%r)" % self.verb)
parsed_args.break_my_certs = parsed_args.staging = True
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
# The user has a prod account, but might not have a staging
# one; we don't want to start trying to perform interactive registration
parsed_args.tos = True
parsed_args.register_unsafely_without_email = True
self.set_test_server(parsed_args)
if parsed_args.csr:
if parsed_args.allow_subset_of_names:
raise errors.Error("--allow-subset-of-names "
"cannot be used with --csr")
self.handle_csr(parsed_args)
hooks.validate_hooks(parsed_args)
if parsed_args.must_staple:
parsed_args.staple = True
# Avoid conflicting args
conficting_args = ["quiet", "noninteractive_mode", "text_mode"]
if parsed_args.dialog_mode:
for arg in conficting_args:
if getattr(parsed_args, arg):
raise errors.Error(
("Conflicting values for displayer."
" {0} conflicts with dialog_mode").format(arg)
)
if parsed_args.validate_hooks:
hooks.validate_hooks(parsed_args)
return parsed_args
def set_test_server(self, parsed_args):
"""We have --staging/--dry-run; perform sanity check and set config.server"""
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
conflicts = ["--staging"] if parsed_args.staging else []
conflicts += ["--dry-run"] if parsed_args.dry_run else []
raise errors.Error("--server value conflicts with {0}".format(
" and ".join(conflicts)))
parsed_args.server = constants.STAGING_URI
if parsed_args.dry_run:
if self.verb not in ["certonly", "renew"]:
raise errors.Error("--dry-run currently only works with the "
"'certonly' or 'renew' subcommands (%r)" % self.verb)
parsed_args.break_my_certs = parsed_args.staging = True
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
# The user has a prod account, but might not have a staging
# one; we don't want to start trying to perform interactive registration
parsed_args.tos = True
parsed_args.register_unsafely_without_email = True
def handle_csr(self, parsed_args):
"""Process a --csr flag."""
if parsed_args.verb != "certonly":
@ -373,21 +427,11 @@ class HelpfulArgumentParser(object):
"when obtaining a new or replacement "
"via the certonly command. Please try the "
"certonly command instead.")
if parsed_args.allow_subset_of_names:
raise errors.Error("--allow-subset-of-names cannot be used with --csr")
try:
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
typ = OpenSSL.crypto.FILETYPE_ASN1
domains = crypto_util.get_sans_from_csr(csr.data, OpenSSL.crypto.FILETYPE_ASN1)
except OpenSSL.crypto.Error:
try:
e1 = traceback.format_exc()
typ = OpenSSL.crypto.FILETYPE_PEM
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="pem")
domains = crypto_util.get_sans_from_csr(csr.data, typ)
except OpenSSL.crypto.Error:
logger.debug("DER CSR parse error %s", e1)
logger.debug("PEM CSR parse error %s", traceback.format_exc())
raise errors.Error("Failed to parse CSR file: {0}".format(parsed_args.csr[0]))
csrfile, contents = parsed_args.csr[0:2]
typ, csr, domains = crypto_util.import_csr_file(csrfile, contents)
# This is not necessary for webroot to work, however,
# obtain_certificate_from_csr requires parsed_args.domains to be set
@ -509,7 +553,7 @@ class HelpfulArgumentParser(object):
:param int nargs: Number of arguments the option takes.
"""
le_util.add_deprecated_argument(
util.add_deprecated_argument(
self.parser.add_argument, argument_name, num_args)
def add_group(self, topic, **kwargs):
@ -564,7 +608,7 @@ class HelpfulArgumentParser(object):
return dict([(t, t == chosen_topic) for t in self.help_topics])
def prepare_and_parse_args(plugins, args, detect_defaults=False):
def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: disable=too-many-statements
"""Returns parsed command line arguments.
:param .PluginsRegistry plugins: available plugins
@ -574,6 +618,9 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
:rtype: argparse.Namespace
"""
# pylint: disable=too-many-statements
helpful = HelpfulArgumentParser(args, plugins, detect_defaults)
# --help is automatically provided by argparse
@ -591,6 +638,9 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
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, "--dialog", dest="dialog_mode", action="store_true",
help="Run using dialog")
helpful.add(
None, "--dry-run", action="store_true", dest="dry_run",
help="Perform a test run of the client, obtaining test (invalid) certs"
@ -613,6 +663,11 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
"certificates. Updates to the Subscriber Agreement will still "
"affect you, and will be effective 14 days after posting an "
"update to the web site.")
helpful.add(
"register", "--update-registration", action="store_true",
help="With the register verb, indicates that details associated "
"with an existing registration, such as the e-mail address, "
"should be updated, rather than registering a new account.")
helpful.add(None, "-m", "--email", help=config_help("email"))
# positional arg shadows --domains, instead of appending, and
# --domains is useful, because it can be stored in config
@ -786,6 +841,14 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
"For this command, the shell variable $RENEWED_LINEAGE will point to the"
"config live subdirectory containing the new certs and keys; the shell variable "
"$RENEWED_DOMAINS will contain a space-delimited list of renewed cert domains")
helpful.add(
"renew", "--disable-hook-validation",
action='store_false', dest='validate_hooks', default=True,
help="Ordinarily the commands specified for --pre-hook/--post-hook/--renew-hook"
" will be checked for validity, to see if the programs being run are in the $PATH,"
" so that mistakes can be caught early, even when the hooks aren't being run just yet."
" The validation is rather simplistic and fails if you use more advanced"
" shell constructs, so you can use this switch to disable it.")
helpful.add_deprecated_argument("--agree-dev-preview", 0)
@ -948,7 +1011,7 @@ def add_domains(args_or_config, domains):
"""
validated_domains = []
for domain in domains.split(","):
domain = le_util.enforce_domain_sanity(domain.strip())
domain = util.enforce_domain_sanity(domain.strip())
validated_domains.append(domain)
if domain not in args_or_config.domains:
args_or_config.domains.append(domain)

View file

@ -21,9 +21,10 @@ from certbot import crypto_util
from certbot import errors
from certbot import error_handler
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot import reverter
from certbot import storage
from certbot import cli
from certbot.display import ops as display_ops
from certbot.display import enhancements
@ -52,7 +53,7 @@ def _determine_user_agent(config):
if config.user_agent is None:
ua = "CertbotACMEClient/{0} ({1}) Authenticator/{2} Installer/{3}"
ua = ua.format(certbot.__version__, " ".join(le_util.get_os_info()),
ua = ua.format(certbot.__version__, util.get_os_info_ua(),
config.authenticator, config.installer)
else:
ua = config.user_agent
@ -149,7 +150,7 @@ def perform_registration(acme, config):
return acme.register(messages.NewRegistration.from_data(email=config.email))
except messages.Error as e:
if e.typ == "urn:acme:error:invalidEmail":
config.namespace.email = display_ops.get_email(more=True, invalid=True)
config.namespace.email = display_ops.get_email(invalid=True)
return perform_registration(acme, config)
else:
raise
@ -197,7 +198,7 @@ class Client(object):
consistent with identifiers present in the `csr`.
:param list domains: Domain names.
:param .le_util.CSR csr: DER-encoded Certificate Signing
:param .util.CSR csr: DER-encoded Certificate Signing
Request. The key used to generate this CSR can be different
than `authkey`.
:param list authzr: List of
@ -236,8 +237,8 @@ class Client(object):
:returns: `.CertificateResource`, certificate chain (as
returned by `.fetch_chain`), and newly generated private key
(`.le_util.Key`) and DER-encoded Certificate Signing Request
(`.le_util.CSR`).
(`.util.Key`) and DER-encoded Certificate Signing Request
(`.util.CSR`).
:rtype: tuple
:raises ValueError: If unable to generate the key.
@ -345,29 +346,36 @@ class Client(object):
"""
for path in cert_path, chain_path, fullchain_path:
le_util.make_or_verify_dir(
util.make_or_verify_dir(
os.path.dirname(path), 0o755, os.geteuid(),
self.config.strict_permissions)
cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)
try:
cert_file.write(cert_pem)
finally:
cert_file.close()
logger.info("Server issued certificate; certificate written to %s",
act_cert_path)
abs_cert_path)
cert_chain_abspath = None
fullchain_abspath = None
if chain_cert:
if not chain_cert:
return abs_cert_path, None, None
else:
chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert)
cert_chain_abspath = _save_chain(chain_pem, chain_path)
fullchain_abspath = _save_chain(cert_pem + chain_pem,
fullchain_path)
return os.path.abspath(act_cert_path), cert_chain_abspath, fullchain_abspath
chain_file, abs_chain_path =\
_open_pem_file('chain_path', chain_path)
fullchain_file, abs_fullchain_path =\
_open_pem_file('fullchain_path', fullchain_path)
_save_chain(chain_pem, chain_file)
_save_chain(cert_pem + chain_pem, fullchain_file)
return abs_cert_path, abs_chain_path, abs_fullchain_path
def deploy_certificate(self, domains, privkey_path,
cert_path, chain_path, fullchain_path):
@ -530,9 +538,9 @@ def validate_key_csr(privkey, csr=None):
If csr is left as None, only the key will be validated.
:param privkey: Key associated with CSR
:type privkey: :class:`certbot.le_util.Key`
:type privkey: :class:`certbot.util.Key`
:param .le_util.CSR csr: CSR
:param .util.CSR csr: CSR
:raises .errors.Error: when validation fails
@ -549,7 +557,7 @@ def validate_key_csr(privkey, csr=None):
if csr.form == "der":
csr_obj = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr.data)
csr = le_util.CSR(csr.file, OpenSSL.crypto.dump_certificate(
csr = util.CSR(csr.file, OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, csr_obj), "pem")
# If CSR is provided, it must be readable and valid.
@ -599,24 +607,35 @@ def view_config_changes(config, num=None):
rev.recovery_routine()
rev.view_config_changes(num)
def _open_pem_file(cli_arg_path, pem_path):
"""Open a pem file.
def _save_chain(chain_pem, chain_path):
If cli_arg_path was set by the client, open that.
Otherwise, uniquify the file path.
:param str cli_arg_path: the cli arg name, e.g. cert_path
:param str pem_path: the pem file path to open
:returns: a tuple of file object and its absolute file path
"""
if cli.set_by_cli(cli_arg_path):
return util.safe_open(pem_path, chmod=0o644),\
os.path.abspath(pem_path)
else:
uniq = util.unique_file(pem_path, 0o644)
return uniq[0], os.path.abspath(uniq[1])
def _save_chain(chain_pem, chain_file):
"""Saves chain_pem at a unique path based on chain_path.
:param str chain_pem: certificate chain in PEM format
:param str chain_path: candidate path for the cert chain
:returns: absolute path to saved cert chain
:rtype: str
:param str chain_file: chain file object
"""
chain_file, act_chain_path = le_util.unique_file(chain_path, 0o644)
try:
chain_file.write(chain_pem)
finally:
chain_file.close()
logger.info("Cert chain written to %s", act_chain_path)
# This expects a valid chain file
return os.path.abspath(act_chain_path)
logger.info("Cert chain written to %s", chain_file.name)

View file

@ -2,7 +2,7 @@
import logging
import sys
from certbot import le_util
from certbot import util
class StreamHandler(logging.StreamHandler):
@ -40,6 +40,6 @@ class StreamHandler(logging.StreamHandler):
if sys.version_info < (2, 7)
else super(StreamHandler, self).format(record))
if self.colored and record.levelno >= self.red_level:
return ''.join((le_util.ANSI_SGR_RED, out, le_util.ANSI_SGR_RESET))
return ''.join((util.ANSI_SGR_RED, out, util.ANSI_SGR_RESET))
else:
return out

View file

@ -8,7 +8,7 @@ import zope.interface
from certbot import constants
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
@zope.interface.implementer(interfaces.IConfig)
@ -132,4 +132,4 @@ def check_config_sanity(config):
if config.namespace.domains is not None:
for domain in config.namespace.domains:
# This may be redundant, but let's be paranoid
le_util.enforce_domain_sanity(domain)
util.enforce_domain_sanity(domain)

View file

@ -6,6 +6,7 @@
"""
import logging
import os
import traceback
import OpenSSL
import pyrfc3339
@ -16,7 +17,7 @@ from acme import jose
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
@ -39,35 +40,34 @@ def save_key(key_pem, key_dir, keyname="key-certbot.pem"):
:param str keyname: Filename of key
:returns: Key
:rtype: :class:`certbot.le_util.Key`
:rtype: :class:`certbot.util.Key`
"""
config = zope.component.getUtility(interfaces.IConfig)
# Save file
le_util.make_or_verify_dir(key_dir, 0o700, os.geteuid(),
config.strict_permissions)
key_f, key_path = le_util.unique_file(
os.path.join(key_dir, keyname), 0o600)
util.make_or_verify_dir(key_dir, 0o700, os.geteuid(),
config.strict_permissions)
key_f, key_path = util.unique_file(os.path.join(key_dir, keyname), 0o600)
with key_f:
logger.info("Saving private key to: %s", key_path)
logger.info("Generating key (%d bits): %s", key_size, key_path)
key_f.write(key_pem)
return le_util.Key(key_path, key_pem)
return util.Key(key_path, key_pem)
def init_save_csr(privkey, names, path, csrname="csr-certbot.pem"):
"""Initialize a CSR with the given private key.
:param privkey: Key to include in the CSR
:type privkey: :class:`certbot.le_util.Key`
:type privkey: :class:`certbot.util.Key`
:param set names: `str` names to include in the CSR
:param str path: Certificate save directory.
:returns: CSR
:rtype: :class:`certbot.le_util.CSR`
:rtype: :class:`certbot.util.CSR`
"""
config = zope.component.getUtility(interfaces.IConfig)
@ -76,16 +76,16 @@ def init_save_csr(privkey, names, path, csrname="csr-certbot.pem"):
must_staple=config.must_staple)
# Save CSR
le_util.make_or_verify_dir(path, 0o755, os.geteuid(),
util.make_or_verify_dir(path, 0o755, os.geteuid(),
config.strict_permissions)
csr_f, csr_filename = le_util.unique_file(
csr_f, csr_filename = util.unique_file(
os.path.join(path, csrname), 0o644)
csr_f.write(csr_pem)
csr_f.close()
logger.info("Creating CSR: %s", csr_filename)
return le_util.CSR(csr_filename, csr_der, "der")
return util.CSR(csr_filename, csr_der, "der")
# Lower level functions
@ -174,6 +174,30 @@ def csr_matches_pubkey(csr, privkey):
return False
def import_csr_file(csrfile, data):
"""Import a CSR file, which can be either PEM or DER.
:param str csrfile: CSR filename
:param str data: contents of the CSR file
:returns: (`OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`,
util.CSR object representing the CSR,
list of domains requested in the CSR)
:rtype: tuple
"""
for form, typ in (("der", OpenSSL.crypto.FILETYPE_ASN1,),
("pem", OpenSSL.crypto.FILETYPE_PEM,),):
try:
domains = get_names_from_csr(data, typ)
except OpenSSL.crypto.Error:
logger.debug("CSR parse error (form=%s, typ=%s):", form, typ)
logger.debug(traceback.format_exc())
continue
return typ, util.CSR(file=csrfile, data=data, form=form), domains
raise errors.Error("Failed to parse CSR file: {0}".format(csrfile))
def make_key_rsa(bits):
"""Generate PEM encoded RSA key.
@ -247,15 +271,20 @@ def pyopenssl_load_certificate(data):
str(error) for error in openssl_errors)))
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
def _load_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
try:
cert_or_req = load_func(typ, cert_or_req_str)
return load_func(typ, cert_or_req_str)
except OpenSSL.crypto.Error as error:
logger.exception(error)
raise
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
# pylint: disable=protected-access
return acme_crypto_util._pyopenssl_cert_or_req_san(cert_or_req)
return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req(
cert_or_req_str, load_func, typ))
def get_sans_from_cert(cert, typ=OpenSSL.crypto.FILETYPE_PEM):
@ -286,6 +315,46 @@ def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
csr, OpenSSL.crypto.load_certificate_request, typ)
def _get_names_from_cert_or_req(cert_or_req, load_func, typ):
loaded_cert_or_req = _load_cert_or_req(cert_or_req, load_func, typ)
common_name = loaded_cert_or_req.get_subject().CN
# pylint: disable=protected-access
sans = acme_crypto_util._pyopenssl_cert_or_req_san(loaded_cert_or_req)
if common_name is None:
return sans
else:
return [common_name] + [d for d in sans if d != common_name]
def get_names_from_cert(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
"""Get a list of domains from a cert, including the CN if it is set.
:param str cert: Certificate (encoded).
:param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`
:returns: A list of domain names.
:rtype: list
"""
return _get_names_from_cert_or_req(
csr, OpenSSL.crypto.load_certificate, typ)
def get_names_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
"""Get a list of domains from a CSR, including the CN if it is set.
:param str csr: CSR (encoded).
:param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`
:returns: A list of domain names.
:rtype: list
"""
return _get_names_from_cert_or_req(
csr, OpenSSL.crypto.load_certificate_request, typ)
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle.

View file

@ -6,7 +6,7 @@ import zope.component
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot.display import util as display_util
logger = logging.getLogger(__name__)
@ -15,41 +15,56 @@ logger = logging.getLogger(__name__)
z_util = zope.component.getUtility
def get_email(more=False, invalid=False):
def get_email(invalid=False, optional=True):
"""Prompt for valid email address.
:param bool more: explain why the email is strongly advisable, but how to
skip it
:param bool invalid: true if the user just typed something, but it wasn't
a valid-looking email
:param bool invalid: True if an invalid address was provided by the user
:param bool optional: True if the user can use
--register-unsafely-without-email to avoid providing an e-mail
:returns: Email or ``None`` if cancelled by user.
:returns: e-mail address
:rtype: str
"""
msg = "Enter email address (used for urgent notices and lost key recovery)"
if invalid:
msg = "There seem to be problems with that address. " + msg
if more:
msg += ('\n\nIf you really want to skip this, you can run the client with '
'--register-unsafely-without-email but make sure you backup your '
'account key from /etc/letsencrypt/accounts\n\n')
try:
code, email = zope.component.getUtility(interfaces.IDisplay).input(msg)
except errors.MissingCommandlineFlag:
msg = ("You should register before running non-interactively, or provide --agree-tos"
" and --email <email_address> flags")
raise errors.MissingCommandlineFlag(msg)
:raises errors.Error: if the user cancels
if code == display_util.OK:
if le_util.safe_email(email):
return email
"""
invalid_prefix = "There seem to be problems with that address. "
msg = "Enter email address (used for urgent notices and lost key recovery)"
unsafe_suggestion = ("\n\nIf you really want to skip this, you can run "
"the client with --register-unsafely-without-email "
"but make sure you then backup your account key from "
"/etc/letsencrypt/accounts\n\n")
if optional:
if invalid:
msg += unsafe_suggestion
else:
# TODO catch the server's ACME invalid email address error, and
# make a similar call when that happens
return get_email(more=True, invalid=(email != ""))
suggest_unsafe = True
else:
return None
suggest_unsafe = False
while True:
try:
code, email = z_util(interfaces.IDisplay).input(
invalid_prefix + msg if invalid else msg)
except errors.MissingCommandlineFlag:
msg = ("You should register before running non-interactively, "
"or provide --agree-tos and --email <email_address> flags")
raise errors.MissingCommandlineFlag(msg)
if code != display_util.OK:
if optional:
raise errors.Error(
"An e-mail address or "
"--register-unsafely-without-email must be provided.")
else:
raise errors.Error("An e-mail address must be provided.")
elif util.safe_email(email):
return email
elif suggest_unsafe:
msg += unsafe_suggestion
suggest_unsafe = False # add this message at most once
invalid = bool(email)
def choose_account(accounts):
@ -119,7 +134,7 @@ def get_valid_domains(domains):
valid_domains = []
for domain in domains:
try:
valid_domains.append(le_util.enforce_domain_sanity(domain))
valid_domains.append(util.enforce_domain_sanity(domain))
except errors.ConfigurationError:
continue
return valid_domains
@ -163,7 +178,7 @@ def _choose_names_manually():
for i, domain in enumerate(domain_list):
try:
domain_list[i] = le_util.enforce_domain_sanity(domain)
domain_list[i] = util.enforce_domain_sanity(domain)
except errors.ConfigurationError as e:
invalid_domains[domain] = e.message

View file

@ -41,10 +41,15 @@ def _wrap_lines(msg):
"""
lines = msg.splitlines()
fixed_l = []
for line in lines:
fixed_l.append(textwrap.fill(line, 80))
return os.linesep.join(fixed_l)
for line in lines:
fixed_l.append(textwrap.fill(
line,
80,
break_long_words=False,
break_on_hyphens=False))
return os.linesep.join(fixed_l)
@zope.interface.implementer(interfaces.IDisplay)
class NcursesDisplay(object):
@ -265,7 +270,11 @@ class FileDisplay(object):
"""
ans = raw_input(
textwrap.fill("%s (Enter 'c' to cancel): " % message, 80))
textwrap.fill(
"%s (Enter 'c' to cancel): " % message,
80,
break_long_words=False,
break_on_hyphens=False))
if ans == "c" or ans == "C":
return CANCEL, "-1"
@ -402,7 +411,11 @@ class FileDisplay(object):
# Write out the menu choices
for i, desc in enumerate(choices, 1):
self.outfile.write(
textwrap.fill("{num}: {desc}".format(num=i, desc=desc), 80))
textwrap.fill(
"{num}: {desc}".format(num=i, desc=desc),
80,
break_long_words=False,
break_on_hyphens=False))
# Keep this outside of the textwrap
self.outfile.write(os.linesep)

View file

@ -209,9 +209,9 @@ class IConfig(zope.interface.Interface):
"Note: at the moment it's only possible to request one of the key "
"types listed above. This is set to change in the future.")
must_staple = zope.interface.Attribute(
"Whether to request the OCSP Must Staple certificate extension. "
"Additional setup may be required after issuance. This does not "
"currently autoconfigure web servers for OCSP stapling. ")
"Adds the OCSP Must Staple extension to the certificate. "
"Autoconfigures OCSP Stapling for supported setups "
"(Apache version >= 2.3.3 ).")
config_dir = zope.interface.Attribute("Configuration directory.")
work_dir = zope.interface.Attribute("Working directory.")

View file

@ -1,6 +1,7 @@
"""Certbot main entry point."""
from __future__ import print_function
import atexit
import errno
import functools
import logging.handlers
import os
@ -24,7 +25,7 @@ from certbot import constants
from certbot import errors
from certbot import hooks
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot import log
from certbot import reporter
from certbot import renewal
@ -229,7 +230,7 @@ def _find_duplicative_certs(config, domains):
cli_config = configuration.RenewerConfiguration(config)
configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there
le_util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid())
util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid())
for renewal_file in renewal.renewal_conf_files(cli_config):
try:
@ -292,7 +293,7 @@ def _report_new_cert(config, cert_path, fullchain_path):
msg = ('Congratulations! Your certificate {0} been saved at {1}.'
' Your cert will expire on {2}. To obtain a new or tweaked version of this '
'certificate in the future, simply run {3} again{4}. '
'To non-interactively renew *all* of your ceriticates, run "{3} renew"'
'To non-interactively renew *all* of your certificates, run "{3} renew"'
.format(and_chain, path, expiry, cli.cli_command, verbswitch))
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
@ -366,6 +367,48 @@ def _init_le_client(config, authenticator, installer):
return client.Client(config, acc, authenticator, installer, acme=acme)
def register(config, unused_plugins):
"""Create or modify accounts on the server."""
# Portion of _determine_account logic to see whether accounts already
# exist or not.
account_storage = account.AccountFileStorage(config)
accounts = account_storage.find_all()
# registering a new account
if not config.update_registration:
if len(accounts) > 0:
# TODO: add a flag to register a duplicate account (this will
# also require extending _determine_account's behavior
# or else extracting the registration code from there)
return ("There is an existing account; registration of a "
"duplicate account with this command is currently "
"unsupported.")
# _determine_account will register an account
_determine_account(config)
return
# --update-registration
if len(accounts) == 0:
return "Could not find an existing account to update."
if config.email is None:
if config.register_unsafely_without_email:
return ("--register-unsafely-without-email provided, however, a "
"new e-mail address must\ncurrently be provided when "
"updating a registration.")
config.namespace.email = display_ops.get_email(optional=False)
acc, acme = _determine_account(config)
acme_client = client.Client(config, acc, None, None, acme=acme)
# We rely on an exception to interrupt this process if it didn't work.
acc.regr = acme_client.acme.update_registration(acc.regr.update(
body=acc.regr.body.update(contact=('mailto:' + config.email,))))
account_storage.save_regr(acc)
reporter_util = zope.component.getUtility(interfaces.IReporter)
msg = "Your e-mail address was updated to {0}.".format(config.email)
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
def install(config, plugins):
"""Install a previously obtained cert in a server."""
# XXX: Update for renewer/RenewableCert
@ -546,8 +589,16 @@ def renew(config, unused_plugins):
def setup_log_file_handler(config, logfile, fmt):
"""Setup file debug logging."""
log_file_path = os.path.join(config.logs_dir, logfile)
handler = logging.handlers.RotatingFileHandler(
log_file_path, maxBytes=2 ** 20, backupCount=10)
try:
handler = logging.handlers.RotatingFileHandler(
log_file_path, maxBytes=2 ** 20, backupCount=10)
except IOError as e:
if e.errno == errno.EACCES:
msg = ("Access denied writing to {0}. To run as non-root, set " +
"--logs-dir, --config-dir, --work-dir to writable paths.")
raise errors.Error(msg.format(log_file_path))
else:
raise
# rotate on each invocation, rollover only possible when maxBytes
# is nonzero and backupCount is nonzero, so we set maxBytes as big
# as possible not to overrun in single CLI invocation (1MB).
@ -656,12 +707,12 @@ def main(cli_args=sys.argv[1:]):
# Setup logging ASAP, otherwise "No handlers could be found for
# logger ..." TODO: this should be done before plugins discovery
for directory in config.config_dir, config.work_dir:
le_util.make_or_verify_dir(
util.make_or_verify_dir(
directory, constants.CONFIG_DIRS_MODE, os.geteuid(),
"--strict-permissions" in cli_args)
# TODO: logs might contain sensitive data such as contents of the
# private key! #525
le_util.make_or_verify_dir(
util.make_or_verify_dir(
config.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args)
setup_logging(config, _cli_log_handler, logfile='letsencrypt.log')
cli.possible_deprecation_warning(config)
@ -681,9 +732,6 @@ def main(cli_args=sys.argv[1:]):
displayer = display_util.NoninteractiveDisplay(sys.stdout)
elif config.text_mode:
displayer = display_util.FileDisplay(sys.stdout)
elif config.verb == "renew":
config.noninteractive_mode = True
displayer = display_util.NoninteractiveDisplay(sys.stdout)
else:
displayer = display_util.NcursesDisplay()
zope.component.provideUtility(displayer)

View file

@ -12,7 +12,7 @@ from acme.jose import util as jose_util
from certbot import constants
from certbot import interfaces
from certbot import le_util
from certbot import util
def option_namespace(name):
@ -255,7 +255,7 @@ class TLSSNI01(object):
# Write out challenge cert and key
with open(cert_path, "wb") as cert_chall_fd:
cert_chall_fd.write(cert_pem)
with le_util.safe_open(key_path, 'wb', chmod=0o400) as key_file:
with util.safe_open(key_path, 'wb', chmod=0o400) as key_file:
key_file.write(key_pem)
return response

View file

@ -193,7 +193,7 @@ class TLSSNI01Test(unittest.TestCase):
with mock.patch("certbot.plugins.common.open",
mock_open, create=True):
with mock.patch("certbot.plugins.common.le_util.safe_open",
with mock.patch("certbot.plugins.common.util.safe_open",
mock_safe_open):
# pylint: disable=protected-access
self.assertEqual(response, self.sni._setup_challenge_cert(

View file

@ -181,12 +181,8 @@ to serve all files under specified web root ({0})."""
os.chown(self.full_roots[name], stat_path.st_uid,
stat_path.st_gid)
except OSError as exception:
if exception.errno == errno.EACCES:
logger.debug("Insufficient permissions to change owner and uid - ignoring")
else:
raise errors.PluginError(
"Couldn't create root for {0} http-01 "
"challenge responses: {1}", name, exception)
logger.info("Unable to change owner and uid of webroot directory")
logger.debug("Error was: %s", exception)
except OSError as exception:
if exception.errno != errno.EEXIST:
@ -235,17 +231,9 @@ to serve all files under specified web root ({0})."""
logger.debug("All challenges cleaned up, removing %s",
root_path)
except OSError as exc:
if exc.errno == errno.ENOTEMPTY:
logger.debug("Challenges cleaned up but %s not empty",
root_path)
elif exc.errno == errno.EACCES:
logger.debug("Challenges cleaned up but no permissions for %s",
root_path)
elif exc.errno == errno.ENOENT:
logger.debug("Challenges cleaned up, %s does not exists",
root_path)
else:
raise
logger.info(
"Unable to clean up challenge directory %s", root_path)
logger.debug("Error was: %s", exc)
class _WebrootMapAction(argparse.Action):

View file

@ -138,15 +138,10 @@ class AuthenticatorTest(unittest.TestCase):
os.chmod(self.path, 0o700)
@mock.patch("certbot.plugins.webroot.os.chown")
def test_failed_chown_eacces(self, mock_chown):
def test_failed_chown(self, mock_chown):
mock_chown.side_effect = OSError(errno.EACCES, "msg")
self.auth.perform([self.achall]) # exception caught and logged
@mock.patch("certbot.plugins.webroot.os.chown")
def test_failed_chown_not_eacces(self, mock_chown):
mock_chown.side_effect = OSError()
self.assertRaises(errors.PluginError, self.auth.perform, [])
def test_perform_permissions(self):
self.auth.prepare()
@ -200,7 +195,7 @@ class AuthenticatorTest(unittest.TestCase):
os.rmdir(leftover_path)
@mock.patch('os.rmdir')
def test_cleanup_permission_denied(self, mock_rmdir):
def test_cleanup_failure(self, mock_rmdir):
self.auth.prepare()
self.auth.perform([self.achall])
@ -212,32 +207,6 @@ class AuthenticatorTest(unittest.TestCase):
self.assertFalse(os.path.exists(self.validation_path))
self.assertTrue(os.path.exists(self.root_challenge_path))
@mock.patch('os.rmdir')
def test_cleanup_oserror(self, mock_rmdir):
self.auth.prepare()
self.auth.perform([self.achall])
os_error = OSError()
os_error.errno = errno.EPERM
mock_rmdir.side_effect = os_error
self.assertRaises(OSError, self.auth.cleanup, [self.achall])
self.assertFalse(os.path.exists(self.validation_path))
self.assertTrue(os.path.exists(self.root_challenge_path))
@mock.patch('os.rmdir')
def test_cleanup_file_not_exists(self, mock_rmdir):
self.auth.prepare()
self.auth.perform([self.achall])
os_error = OSError()
os_error.errno = errno.ENOENT
mock_rmdir.side_effect = os_error
self.auth.cleanup([self.achall])
self.assertFalse(os.path.exists(self.validation_path))
self.assertTrue(os.path.exists(self.root_challenge_path))
class WebrootActionTest(unittest.TestCase):
"""Tests for webroot argparse actions."""

View file

@ -18,7 +18,7 @@ from certbot import constants
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot import hooks
from certbot import storage
from certbot.plugins import disco as plugins_disco
@ -62,7 +62,8 @@ def _reconstitute(config, full_path):
try:
renewal_candidate = storage.RenewableCert(
full_path, configuration.RenewerConfiguration(config))
except (errors.CertStorageError, IOError):
except (errors.CertStorageError, IOError) as exc:
logger.warning(exc)
logger.warning("Renewal configuration file %s is broken. Skipping.", full_path)
logger.debug("Traceback was:\n%s", traceback.format_exc())
return None
@ -88,7 +89,7 @@ def _reconstitute(config, full_path):
return None
try:
config.domains = [le_util.enforce_domain_sanity(d)
config.domains = [util.enforce_domain_sanity(d)
for d in renewal_candidate.names()]
except errors.ConfigurationError as error:
logger.warning("Renewal configuration file %s references a cert "

View file

@ -11,11 +11,16 @@ from six.moves import queue # pylint: disable=import-error
import zope.interface
from certbot import interfaces
from certbot import le_util
from certbot import util
logger = logging.getLogger(__name__)
# Store the pid of the process that first imported this module so that
# atexit_print_messages side-effects such as error reporting can be limited to
# this process and not any fork()'d children.
INITIAL_PID = os.getpid()
@zope.interface.implementer(interfaces.IReporter)
class Reporter(object):
@ -55,12 +60,14 @@ class Reporter(object):
self.messages.put(self._msg_type(priority, msg, on_crash))
logger.info("Reporting to user: %s", msg)
def atexit_print_messages(self, pid=os.getpid()):
def atexit_print_messages(self, pid=None):
"""Function to be registered with atexit to print messages.
:param int pid: Process ID
"""
if pid is None:
pid = INITIAL_PID
# This ensures that messages are only printed from the process that
# created the Reporter.
if pid == os.getpid():
@ -79,13 +86,18 @@ class Reporter(object):
bold_on = sys.stdout.isatty()
if not self.config.quiet:
if bold_on:
print(le_util.ANSI_SGR_BOLD)
print(util.ANSI_SGR_BOLD)
print('IMPORTANT NOTES:')
first_wrapper = textwrap.TextWrapper(
initial_indent=' - ', subsequent_indent=(' ' * 3))
initial_indent=' - ',
subsequent_indent=(' ' * 3),
break_long_words=False,
break_on_hyphens=False)
next_wrapper = textwrap.TextWrapper(
initial_indent=first_wrapper.subsequent_indent,
subsequent_indent=first_wrapper.subsequent_indent)
subsequent_indent=first_wrapper.subsequent_indent,
break_long_words=False,
break_on_hyphens=False)
while not self.messages.empty():
msg = self.messages.get()
if self.config.quiet:
@ -96,7 +108,7 @@ class Reporter(object):
if no_exception or msg.on_crash:
if bold_on and msg.priority > self.HIGH_PRIORITY:
if not self.config.quiet:
sys.stdout.write(le_util.ANSI_SGR_RESET)
sys.stdout.write(util.ANSI_SGR_RESET)
bold_on = False
lines = msg.text.splitlines()
print(first_wrapper.fill(lines[0]))
@ -104,4 +116,4 @@ class Reporter(object):
print("\n".join(
next_wrapper.fill(line) for line in lines[1:]))
if bold_on and not self.config.quiet:
sys.stdout.write(le_util.ANSI_SGR_RESET)
sys.stdout.write(util.ANSI_SGR_RESET)

View file

@ -7,12 +7,13 @@ import shutil
import time
import traceback
import zope.component
from certbot import constants
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot import util
from certbot.display import util as display_util
@ -32,7 +33,7 @@ class Reverter(object):
def __init__(self, config):
self.config = config
le_util.make_or_verify_dir(
util.make_or_verify_dir(
config.backup_dir, constants.CONFIG_DIRS_MODE, os.geteuid(),
self.config.strict_permissions)
@ -184,7 +185,7 @@ class Reverter(object):
:raises .ReverterError: if unable to add checkpoint
"""
le_util.make_or_verify_dir(
util.make_or_verify_dir(
cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(),
self.config.strict_permissions)
@ -280,7 +281,7 @@ class Reverter(object):
csvreader = csv.reader(csvfile)
for command in reversed(list(csvreader)):
try:
le_util.run_script(command)
util.run_script(command)
except errors.SubprocessError:
logger.error(
"Unable to run undo command: %s", " ".join(command))
@ -396,7 +397,7 @@ class Reverter(object):
else:
cp_dir = self.config.in_progress_dir
le_util.make_or_verify_dir(
util.make_or_verify_dir(
cp_dir, constants.CONFIG_DIRS_MODE, os.geteuid(),
self.config.strict_permissions)
@ -489,7 +490,7 @@ class Reverter(object):
if not os.path.exists(changes_since_path):
logger.info("Rollback checkpoint is empty (no changes made?)")
with open(self.config.changes_since_path) as f:
with open(changes_since_path, 'w') as f:
f.write("No changes\n")
# Add title to self.config.in_progress_dir CHANGES_SINCE

View file

@ -7,16 +7,20 @@ import re
import configobj
import parsedatetime
import pytz
import six
import certbot
from certbot import cli
from certbot import constants
from certbot import crypto_util
from certbot import errors
from certbot import error_handler
from certbot import le_util
from certbot import util
logger = logging.getLogger(__name__)
ALL_FOUR = ("cert", "privkey", "chain", "fullchain")
CURRENT_VERSION = util.get_strict_version(certbot.__version__)
def config_with_defaults(config=None):
@ -63,6 +67,7 @@ def write_renewal_config(o_filename, n_filename, target, relevant_data):
"""
config = configobj.ConfigObj(o_filename)
config["version"] = certbot.__version__
for kind in ALL_FOUR:
config[kind] = target[kind]
@ -155,36 +160,13 @@ def relevant_values(all_values):
:param dict all_values: The original values.
:returns: A new dictionary containing items that can be used in renewal.
:rtype dict:"""
:rtype dict:
from certbot import cli
def _is_cli_default(option, value):
# Look through the CLI parser defaults and see if this option is
# both present and equal to the specified value. If not, return
# False.
# pylint: disable=protected-access
for x in cli.helpful_parser.parser._actions:
if x.dest == option:
if x.default == value:
return True
else:
break
return False
values = dict()
for option, value in all_values.iteritems():
# Try to find reasons to store this item in the
# renewal config. It can be stored if it is relevant and
# (it is set_by_cli() or flag_default() is different
# from the value or flag_default() doesn't exist).
if _relevant(option):
if (cli.set_by_cli(option)
or not _is_cli_default(option, value)):
# or option not in constants.CLI_DEFAULTS
# or constants.CLI_DEFAULTS[option] != value):
values[option] = value
return values
"""
return dict(
(option, value)
for option, value in six.iteritems(all_values)
if _relevant(option) and cli.option_was_set(option, value))
class RenewableCert(object): # pylint: disable=too-many-instance-attributes
@ -259,6 +241,14 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
"renewal config file {0} is missing a required "
"file reference".format(self.configfile))
conf_version = self.configuration.get("version")
if (conf_version is not None and
util.get_strict_version(conf_version) > CURRENT_VERSION):
logger.warning(
"Attempting to parse the version %s renewal configuration "
"file found at %s with version %s of Certbot. This might not "
"work.", conf_version, config_filename, certbot.__version__)
self.cert = self.configuration["cert"]
self.privkey = self.configuration["privkey"]
self.chain = self.configuration["chain"]
@ -605,7 +595,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
if target is None:
raise errors.CertStorageError("could not find cert file")
with open(target) as f:
return crypto_util.get_sans_from_cert(f.read())
return crypto_util.get_names_from_cert(f.read())
def autodeployment_is_enabled(self):
"""Is automatic deployment enabled for this cert?
@ -758,7 +748,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
if not os.path.exists(i):
os.makedirs(i, 0o700)
logger.debug("Creating directory %s.", i)
config_file, config_filename = le_util.unique_lineage_name(
config_file, config_filename = util.unique_lineage_name(
cli_config.renewal_configs_dir, lineagename)
if not config_filename.endswith(".conf"):
raise errors.CertStorageError(

View file

@ -137,6 +137,16 @@ class AccountFileStorageTest(unittest.TestCase):
# restore
self.assertEqual(self.acc, self.storage.load(self.acc.id))
def test_save_regr(self):
self.storage.save_regr(self.acc)
account_path = os.path.join(self.config.accounts_dir, self.acc.id)
self.assertTrue(os.path.exists(account_path))
self.assertTrue(os.path.exists(os.path.join(
account_path, "regr.json")))
for file_name in "meta.json", "private_key.json":
self.assertFalse(os.path.exists(
os.path.join(account_path, file_name)))
def test_find_all(self):
self.storage.save(self.acc)
self.assertEqual([self.acc], self.storage.find_all())

View file

@ -11,7 +11,7 @@ from acme import messages
from certbot import achallenges
from certbot import errors
from certbot import le_util
from certbot import util
from certbot.tests import acme_util
@ -69,7 +69,7 @@ class GetAuthorizationsTest(unittest.TestCase):
self.mock_auth.perform.side_effect = gen_auth_resp
self.mock_account = mock.Mock(key=le_util.Key("file_path", "PEM"))
self.mock_account = mock.Mock(key=util.Key("file_path", "PEM"))
self.mock_net = mock.MagicMock(spec=acme_client.Client)
self.handler = AuthHandler(

View file

@ -22,7 +22,7 @@ from certbot import configuration
from certbot import constants
from certbot import crypto_util
from certbot import errors
from certbot import le_util
from certbot import util
from certbot import main
from certbot import renewal
from certbot import storage
@ -149,6 +149,14 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
args.extend(['--email', 'io@io.is'])
self._cli_missing_flag(args, "--agree-tos")
@mock.patch('certbot.main.renew')
def test_gui(self, renew):
args = ['renew', '--dialog']
# --text conflicts with --dialog
self.standard_args.remove('--text')
self._call(args)
self.assertFalse(renew.call_args[0][0].noninteractive_mode)
@mock.patch('certbot.main.client.acme_client.Client')
@mock.patch('certbot.main._determine_account')
@mock.patch('certbot.main.client.Client.obtain_and_enroll_certificate')
@ -163,13 +171,13 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net:
self._call_no_clientmock(args)
os_ver = " ".join(le_util.get_os_info())
os_ver = util.get_os_info_ua()
ua = acme_net.call_args[1]["user_agent"]
self.assertTrue(os_ver in ua)
import platform
plat = platform.platform()
if "linux" in plat.lower():
self.assertTrue(platform.linux_distribution()[0] in ua)
self.assertTrue(util.get_os_info_ua() in ua)
with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net:
ua = "bandersnatch"
@ -201,7 +209,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
'--key-path', 'key', '--chain-path', 'chain'])
self.assertEqual(mock_pick_installer.call_count, 1)
@mock.patch('certbot.le_util.exe_exists')
@mock.patch('certbot.util.exe_exists')
def test_configurator_selection(self, mock_exe_exists):
mock_exe_exists.return_value = True
real_plugins = disco.PluginsRegistry.find_all()
@ -349,8 +357,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
['-d', '204.11.231.35'])
def test_csr_with_besteffort(self):
args = ["--csr", CSR, "--allow-subset-of-names"]
self.assertRaises(errors.Error, self._call, args)
self.assertRaises(
errors.Error, self._call,
'certonly --csr {0} --allow-subset-of-names'.format(CSR).split())
def test_run_with_csr(self):
# This is an error because you can only use --csr with certonly
@ -361,6 +370,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
return
assert False, "Expected supplying --csr to fail with default verb"
def test_csr_with_no_domains(self):
self.assertRaises(
errors.Error, self._call,
'certonly --csr {0}'.format(
test_util.vector_path('csr-nonames.pem')).split())
def test_csr_with_inconsistent_domains(self):
self.assertRaises(
errors.Error, self._call,
'certonly -d example.org --csr {0}'.format(CSR).split())
def _get_argument_parser(self):
plugins = disco.PluginsRegistry.find_all()
return functools.partial(cli.prepare_and_parse_args, plugins)
@ -410,6 +430,13 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
for arg in conflicting_args:
self.assertTrue(arg in error.message)
def test_must_staple_flag(self):
parse = self._get_argument_parser()
short_args = ['--must-staple']
namespace = parse(short_args)
self.assertTrue(namespace.must_staple)
self.assertTrue(namespace.staple)
def test_staging_flag(self):
parse = self._get_argument_parser()
short_args = ['--staging']
@ -420,6 +447,19 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
short_args += '--server example.com'.split()
self._check_server_conflict_message(short_args, '--staging')
def test_option_was_set(self):
key_size_option = 'rsa_key_size'
key_size_value = cli.flag_default(key_size_option)
self._get_argument_parser()(
'--rsa-key-size {0}'.format(key_size_value).split())
self.assertTrue(cli.option_was_set(key_size_option, key_size_value))
self.assertTrue(cli.option_was_set('no_verify_ssl', True))
config_dir_option = 'config_dir'
self.assertFalse(cli.option_was_set(
config_dir_option, cli.flag_default(config_dir_option)))
def _assert_dry_run_flag_worked(self, namespace, existing_account):
self.assertTrue(namespace.dry_run)
self.assertTrue(namespace.break_my_certs)
@ -624,6 +664,18 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
out = stdout.getvalue()
self.assertEqual("", out)
def test_renew_hook_validation(self):
self._make_test_renewal_conf('sample-renewal.conf')
args = ["renew", "--dry-run", "--post-hook=no-such-command"]
self._test_renewal_common(True, [], args=args, should_renew=False,
error_expected=True)
def test_renew_no_hook_validation(self):
self._make_test_renewal_conf('sample-renewal.conf')
args = ["renew", "--dry-run", "--post-hook=no-such-command",
"--disable-hook-validation"]
self._test_renewal_common(True, [], args=args, should_renew=True,
error_expected=False)
@mock.patch("certbot.cli.set_by_cli")
def test_ancient_webroot_renewal_conf(self, mock_set_by_cli):
@ -888,6 +940,78 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._call(['-c', test_util.vector_path('cli.ini')])
self.assertTrue(mocked_run.called)
def test_register(self):
with mock.patch('certbot.main.client') as mocked_client:
acc = mock.MagicMock()
acc.id = "imaginary_account"
mocked_client.register.return_value = (acc, "worked")
self._call_no_clientmock(["register", "--email", "user@example.org"])
# TODO: It would be more correct to explicitly check that
# _determine_account() gets called in the above case,
# but coverage statistics should also show that it did.
with mock.patch('certbot.main.account') as mocked_account:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
mocked_storage.find_all.return_value = ["an account"]
x = self._call_no_clientmock(["register", "--email", "user@example.org"])
self.assertTrue("There is an existing account" in x[0])
def test_update_registration_no_existing_accounts(self):
# with mock.patch('certbot.main.client') as mocked_client:
with mock.patch('certbot.main.account') as mocked_account:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
mocked_storage.find_all.return_value = []
x = self._call_no_clientmock(
["register", "--update-registration", "--email",
"user@example.org"])
self.assertTrue("Could not find an existing account" in x[0])
def test_update_registration_unsafely(self):
# This test will become obsolete when register --update-registration
# supports removing an e-mail address from the account
with mock.patch('certbot.main.account') as mocked_account:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
mocked_storage.find_all.return_value = ["an account"]
x = self._call_no_clientmock(
"register --update-registration "
"--register-unsafely-without-email".split())
self.assertTrue("--register-unsafely-without-email" in x[0])
@mock.patch('certbot.main.display_ops.get_email')
@mock.patch('certbot.main.zope.component.getUtility')
def test_update_registration_with_email(self, mock_utility, mock_email):
email = "user@example.com"
mock_email.return_value = email
with mock.patch('certbot.main.client') as mocked_client:
with mock.patch('certbot.main.account') as mocked_account:
with mock.patch('certbot.main._determine_account') as mocked_det:
with mock.patch('certbot.main.client') as mocked_client:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
mocked_storage.find_all.return_value = ["an account"]
mocked_det.return_value = (mock.MagicMock(), "foo")
acme_client = mock.MagicMock()
mocked_client.Client.return_value = acme_client
x = self._call_no_clientmock(
["register", "--update-registration"])
# When registration change succeeds, the return value
# of register() is None
self.assertTrue(x[0] is None)
# and we got supposedly did update the registration from
# the server
self.assertTrue(
acme_client.acme.update_registration.called)
# and we saved the updated registration on disk
self.assertTrue(mocked_storage.save_regr.called)
self.assertTrue(
email in mock_utility().add_message.call_args[0][0])
def test_conflicting_args(self):
args = ['renew', '--dialog', '--text']
self.assertRaises(errors.Error, self._call, args)
class DetermineAccountTest(unittest.TestCase):
"""Tests for certbot.cli._determine_account."""
@ -964,7 +1088,7 @@ class DuplicativeCertsTest(storage_test.BaseRenewableCertTest):
def tearDown(self):
shutil.rmtree(self.tempdir)
@mock.patch('certbot.le_util.make_or_verify_dir')
@mock.patch('certbot.util.make_or_verify_dir')
def test_find_duplicative_names(self, unused_makedir):
from certbot.main import _find_duplicative_certs
test_cert = test_util.load_vector('cert-san.pem')

View file

@ -11,7 +11,7 @@ from acme import jose
from certbot import account
from certbot import errors
from certbot import le_util
from certbot import util
from certbot.tests import test_util
@ -138,57 +138,39 @@ class ClientTest(unittest.TestCase):
self.acme.fetch_chain.assert_called_once_with(mock.sentinel.certr)
# FIXME move parts of this to test_cli.py...
@mock.patch("certbot.client.logger")
def test_obtain_certificate_from_csr(self, mock_logger):
self._mock_obtain_certificate()
from certbot import cli
test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
mock_parsed_args = mock.MagicMock()
# The CLI should believe that this is a certonly request, because
# a CSR would not be allowed with other kinds of requests!
mock_parsed_args.verb = "certonly"
with mock.patch("certbot.client.le_util.CSR") as mock_CSR:
mock_CSR.return_value = test_csr
mock_parsed_args.domains = self.eg_domains[:]
mock_parser = mock.MagicMock(cli.HelpfulArgumentParser)
cli.HelpfulArgumentParser.handle_csr(mock_parser, mock_parsed_args)
test_csr = util.CSR(form="der", file=None, data=CSR_SAN)
auth_handler = self.client.auth_handler
# Now provoke an inconsistent domains error...
mock_parsed_args.domains.append("hippopotamus.io")
self.assertRaises(errors.ConfigurationError,
cli.HelpfulArgumentParser.handle_csr, mock_parser, mock_parsed_args)
authzr = self.client.auth_handler.get_authorizations(self.eg_domains, False)
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr,
authzr=authzr))
# and that the cert was obtained correctly
self._check_obtain_certificate()
# Test for authzr=None
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr,
authzr=None))
self.client.auth_handler.get_authorizations.assert_called_with(
self.eg_domains)
# Test for no auth_handler
self.client.auth_handler = None
self.assertRaises(
errors.Error,
self.client.obtain_certificate_from_csr,
authzr = auth_handler.get_authorizations(self.eg_domains, False)
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr)
mock_logger.warning.assert_called_once_with(mock.ANY)
test_csr,
authzr=authzr))
# and that the cert was obtained correctly
self._check_obtain_certificate()
# Test for authzr=None
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr,
authzr=None))
auth_handler.get_authorizations.assert_called_with(self.eg_domains)
# Test for no auth_handler
self.client.auth_handler = None
self.assertRaises(
errors.Error,
self.client.obtain_certificate_from_csr,
self.eg_domains,
test_csr)
mock_logger.warning.assert_called_once_with(mock.ANY)
@mock.patch("certbot.client.crypto_util")
def test_obtain_certificate_rsa(self, mock_crypto_util):
@ -266,7 +248,7 @@ class ClientTest(unittest.TestCase):
self.config.key_types = "ecdsa"
self.config.ecdsa_curve = "p-384"
csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
csr = util.CSR(form="der", file=None, data=CSR_SAN)
mock_crypto_util.init_save_csr.return_value = csr
mock_crypto_util.save_key.return_value = mock.sentinel.key
domains = ["example.com", "www.example.com"]
@ -353,7 +335,9 @@ class ClientTest(unittest.TestCase):
self.assertRaises(errors.Error,
self.client.obtain_certificate, domains)
def test_save_certificate(self):
@mock.patch("certbot.cli.helpful_parser")
def test_save_certificate(self, mock_parser):
# pylint: disable=too-many-locals
certs = ["matching_cert.pem", "cert.pem", "cert-san.pem"]
tmp_path = tempfile.mkdtemp()
os.chmod(tmp_path, 0o755) # TODO: really??
@ -364,6 +348,10 @@ class ClientTest(unittest.TestCase):
candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem")
candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem")
candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem")
mock_parser.verb = "certonly"
mock_parser.args = ["--cert-path", candidate_cert_path,
"--chain-path", candidate_chain_path,
"--fullchain-path", candidate_fullchain_path]
cert_path, chain_path, fullchain_path = self.client.save_certificate(
certr, chain_cert, candidate_cert_path, candidate_chain_path,

View file

@ -4,7 +4,7 @@ import unittest
import six
from certbot import le_util
from certbot import util
class StreamHandlerTest(unittest.TestCase):
@ -32,9 +32,9 @@ class StreamHandlerTest(unittest.TestCase):
self.logger.debug(msg)
self.assertEqual(self.stream.getvalue(),
'{0}{1}{2}\n'.format(le_util.ANSI_SGR_RED,
'{0}{1}{2}\n'.format(util.ANSI_SGR_RED,
msg,
le_util.ANSI_SGR_RESET))
util.ANSI_SGR_RESET))
if __name__ == "__main__":

View file

@ -10,6 +10,7 @@ import zope.component
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.tests import test_util
RSA256_KEY = test_util.load_vector('rsa256_key.pem')
@ -54,7 +55,7 @@ class InitSaveCSRTest(unittest.TestCase):
shutil.rmtree(self.csr_dir)
@mock.patch('certbot.crypto_util.make_csr')
@mock.patch('certbot.crypto_util.le_util.make_or_verify_dir')
@mock.patch('certbot.crypto_util.util.make_or_verify_dir')
def test_it(self, unused_mock_verify, mock_csr):
from certbot.crypto_util import init_save_csr
@ -151,6 +152,44 @@ class CSRMatchesPubkeyTest(unittest.TestCase):
test_util.load_vector('csr.pem'), RSA256_KEY))
class ImportCSRFileTest(unittest.TestCase):
"""Tests for certbot.certbot_util.import_csr_file."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.crypto_util import import_csr_file
return import_csr_file(*args, **kwargs)
def test_der_csr(self):
csrfile = test_util.vector_path('csr.der')
data = test_util.load_vector('csr.der')
self.assertEqual(
(OpenSSL.crypto.FILETYPE_ASN1,
util.CSR(file=csrfile,
data=data,
form="der"),
["example.com"],),
self._call(csrfile, data))
def test_pem_csr(self):
csrfile = test_util.vector_path('csr.pem')
data = test_util.load_vector('csr.pem')
self.assertEqual(
(OpenSSL.crypto.FILETYPE_PEM,
util.CSR(file=csrfile,
data=data,
form="pem"),
["example.com"],),
self._call(csrfile, data))
def test_bad_csr(self):
self.assertRaises(errors.Error, self._call,
test_util.vector_path('cert.pem'),
test_util.load_vector('cert.pem'))
class MakeKeyRSATest(unittest.TestCase): # pylint: disable=too-few-public-methods
"""Tests for certbot.crypto_util.make_key_rsa."""
@ -243,6 +282,62 @@ class GetSANsFromCSRTest(unittest.TestCase):
[], self._call(test_util.load_vector('csr-nosans.pem')))
class GetNamesFromCertTest(unittest.TestCase):
"""Tests for certbot.crypto_util.get_names_from_cert."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.crypto_util import get_names_from_cert
return get_names_from_cert(*args, **kwargs)
def test_single(self):
self.assertEqual(
['example.com'],
self._call(test_util.load_vector('cert.pem')))
def test_san(self):
self.assertEqual(
['example.com', 'www.example.com'],
self._call(test_util.load_vector('cert-san.pem')))
def test_common_name_sans_order(self):
# Tests that the common name comes first
# followed by the SANS in alphabetical order
self.assertEqual(
['example.com'] + ['{0}.example.com'.format(c) for c in 'abcd'],
self._call(test_util.load_vector('cert-5sans.pem')))
class GetNamesFromCSRTest(unittest.TestCase):
"""Tests for certbot.crypto_util.get_names_from_csr."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.crypto_util import get_names_from_csr
return get_names_from_csr(*args, **kwargs)
def test_extract_one_san(self):
self.assertEqual(['example.com'], self._call(
test_util.load_vector('csr.pem')))
def test_extract_two_sans(self):
self.assertEqual(set(('example.com', 'www.example.com',)), set(
self._call(test_util.load_vector('csr-san.pem'))))
def test_extract_six_sans(self):
self.assertEqual(
set(self._call(test_util.load_vector('csr-6sans.pem'))),
set(("example.com", "example.org", "example.net",
"example.info", "subdomain.example.com",
"other.subdomain.example.com",)))
def test_parse_non_csr(self):
self.assertRaises(OpenSSL.crypto.Error, self._call, "hello there")
def test_parse_no_sans(self):
self.assertEqual(["example.org"],
self._call(test_util.load_vector('csr-nosans.pem')))
class CertLoaderTest(unittest.TestCase):
"""Tests for certbot.crypto_util.pyopenssl_load_certificate"""

View file

@ -12,6 +12,7 @@ from acme import jose
from acme import messages
from certbot import account
from certbot import errors
from certbot import interfaces
from certbot.display import util as display_util
@ -37,41 +38,39 @@ class GetEmailTest(unittest.TestCase):
def test_cancel_none(self):
self.input.return_value = (display_util.CANCEL, "foo@bar.baz")
self.assertTrue(self._call() is None)
self.assertRaises(errors.Error, self._call)
self.assertRaises(errors.Error, self._call, optional=False)
def test_ok_safe(self):
self.input.return_value = (display_util.OK, "foo@bar.baz")
with mock.patch("certbot.display.ops.le_util.safe_email") as mock_safe_email:
with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email:
mock_safe_email.return_value = True
self.assertTrue(self._call() is "foo@bar.baz")
def test_ok_not_safe(self):
self.input.return_value = (display_util.OK, "foo@bar.baz")
with mock.patch("certbot.display.ops.le_util.safe_email") as mock_safe_email:
with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email:
mock_safe_email.side_effect = [False, True]
self.assertTrue(self._call() is "foo@bar.baz")
def test_more_and_invalid_flags(self):
more_txt = "--register-unsafely-without-email"
def test_invalid_flag(self):
invalid_txt = "There seem to be problems"
base_txt = "Enter email"
self.input.return_value = (display_util.OK, "foo@bar.baz")
with mock.patch("certbot.display.ops.le_util.safe_email") as mock_safe_email:
with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email:
mock_safe_email.return_value = True
self._call()
msg = self.input.call_args[0][0]
self.assertTrue(more_txt not in msg)
self.assertTrue(invalid_txt not in msg)
self.assertTrue(base_txt in msg)
self._call(more=True)
msg = self.input.call_args[0][0]
self.assertTrue(more_txt in msg)
self.assertTrue(invalid_txt not in msg)
self._call(more=True, invalid=True)
msg = self.input.call_args[0][0]
self.assertTrue(more_txt in msg)
self.assertTrue(invalid_txt in msg)
self.assertTrue(base_txt in msg)
self.assertTrue(invalid_txt not in self.input.call_args[0][0])
self._call(invalid=True)
self.assertTrue(invalid_txt in self.input.call_args[0][0])
def test_optional_flag(self):
self.input.return_value = (display_util.OK, "foo@bar.baz")
with mock.patch("certbot.display.ops.util.safe_email") as mock_safe_email:
mock_safe_email.side_effect = [False, True]
self._call(optional=False)
for call in self.input.call_args_list:
self.assertTrue(
"--register-unsafely-without-email" not in call[0][0])
class ChooseAccountTest(unittest.TestCase):

View file

@ -34,6 +34,20 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
logging.disable(logging.NOTSET)
@mock.patch("certbot.reverter.Reverter._read_and_append")
def test_no_change(self, mock_read):
mock_read.side_effect = OSError("cannot even")
try:
self.reverter.add_to_checkpoint(self.sets[0], "save1")
except OSError:
pass
self.reverter.finalize_checkpoint("blah")
path = os.listdir(self.reverter.config.backup_dir)[0]
no_change = os.path.join(self.reverter.config.backup_dir, path, "CHANGES_SINCE")
with open(no_change, "r") as f:
x = f.read()
self.assertTrue("No changes" in x)
def test_basic_add_to_temp_checkpoint(self):
# These shouldn't conflict even though they are both named config.txt
self.reverter.add_to_temp_checkpoint(self.sets[0], "save1")
@ -150,7 +164,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
errors.ReverterError, self.reverter.register_undo_command,
True, ["command"])
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.util.run_script")
def test_run_undo_commands(self, mock_run):
mock_run.side_effect = ["", errors.SubprocessError]
coms = [

View file

@ -10,6 +10,8 @@ import configobj
import mock
import pytz
import certbot
from certbot import cli
from certbot import configuration
from certbot import errors
from certbot.storage import ALL_FOUR
@ -83,18 +85,20 @@ class BaseRenewableCertTest(unittest.TestCase):
def tearDown(self):
shutil.rmtree(self.tempdir)
def _write_out_kind(self, kind, ver, value=None):
link = getattr(self.test_rc, kind)
if os.path.lexists(link):
os.unlink(link)
os.symlink(os.path.join(os.path.pardir, os.path.pardir, "archive",
"example.org", "{0}{1}.pem".format(kind, ver)),
link)
with open(link, "w") as f:
f.write(kind if value is None else value)
def _write_out_ex_kinds(self):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}12.pem".format(kind)), where)
with open(where, "w") as f:
f.write(kind)
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}11.pem".format(kind)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, 12)
self._write_out_kind(kind, 11)
class RenewableCertTests(BaseRenewableCertTest):
@ -137,6 +141,28 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertRaises(errors.CertStorageError, storage.RenewableCert,
config.filename, self.cli_config)
def test_no_renewal_version(self):
from certbot import storage
self._write_out_ex_kinds()
self.assertTrue("version" not in self.config)
with mock.patch("certbot.storage.logger") as mock_logger:
storage.RenewableCert(self.config.filename, self.cli_config)
self.assertFalse(mock_logger.warning.called)
def test_renewal_newer_version(self):
from certbot import storage
self._write_out_ex_kinds()
self.config["version"] = "99.99.99"
self.config.write()
with mock.patch("certbot.storage.logger") as mock_logger:
storage.RenewableCert(self.config.filename, self.cli_config)
self.assertTrue(mock_logger.warning.called)
self.assertTrue("version" in mock_logger.warning.call_args[0][0])
def test_consistent(self):
# pylint: disable=too-many-statements,protected-access
oldcert = self.test_rc.cert
@ -181,10 +207,7 @@ class RenewableCertTests(BaseRenewableCertTest):
def test_current_target(self):
# Relative path logic
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert17.pem"), self.test_rc.cert)
with open(self.test_rc.cert, "w") as f:
f.write("cert")
self._write_out_kind("cert", 17)
self.assertTrue(os.path.samefile(self.test_rc.current_target("cert"),
os.path.join(self.tempdir, "archive",
"example.org",
@ -202,12 +225,8 @@ class RenewableCertTests(BaseRenewableCertTest):
def test_current_version(self):
for ver in (1, 5, 10, 20):
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert{0}.pem".format(ver)),
self.test_rc.cert)
with open(self.test_rc.cert, "w") as f:
f.write("cert")
os.unlink(self.test_rc.cert)
self._write_out_kind("cert", ver)
os.unlink(self.test_rc.cert)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert10.pem"), self.test_rc.cert)
self.assertEqual(self.test_rc.current_version("cert"), 10)
@ -218,61 +237,30 @@ class RenewableCertTests(BaseRenewableCertTest):
def test_latest_and_next_versions(self):
for ver in xrange(1, 6):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, ver)
self.assertEqual(self.test_rc.latest_common_version(), 5)
self.assertEqual(self.test_rc.next_free_version(), 6)
# Having one kind of file of a later version doesn't change the
# result
os.unlink(self.test_rc.privkey)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"privkey7.pem"), self.test_rc.privkey)
with open(self.test_rc.privkey, "w") as f:
f.write("privkey")
self._write_out_kind("privkey", 7)
self.assertEqual(self.test_rc.latest_common_version(), 5)
# ... although it does change the next free version
self.assertEqual(self.test_rc.next_free_version(), 8)
# Nor does having three out of four change the result
os.unlink(self.test_rc.cert)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert7.pem"), self.test_rc.cert)
with open(self.test_rc.cert, "w") as f:
f.write("cert")
os.unlink(self.test_rc.fullchain)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"fullchain7.pem"), self.test_rc.fullchain)
with open(self.test_rc.fullchain, "w") as f:
f.write("fullchain")
self._write_out_kind("cert", 7)
self._write_out_kind("fullchain", 7)
self.assertEqual(self.test_rc.latest_common_version(), 5)
# If we have everything from a much later version, it does change
# the result
ver = 17
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, 17)
self.assertEqual(self.test_rc.latest_common_version(), 17)
self.assertEqual(self.test_rc.next_free_version(), 18)
def test_update_link_to(self):
for ver in xrange(1, 6):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, ver)
self.assertEqual(ver, self.test_rc.current_version(kind))
# pylint: disable=protected-access
self.test_rc._update_link_to("cert", 3)
@ -289,10 +277,7 @@ class RenewableCertTests(BaseRenewableCertTest):
"chain3000.pem")
def test_version(self):
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert12.pem"), self.test_rc.cert)
with open(self.test_rc.cert, "w") as f:
f.write("cert")
self._write_out_kind("cert", 12)
# TODO: We should probably test that the directory is still the
# same, but it's tricky because we can get an absolute
# path out when we put a relative path in.
@ -302,13 +287,7 @@ class RenewableCertTests(BaseRenewableCertTest):
def test_update_all_links_to_success(self):
for ver in xrange(1, 6):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, ver)
self.assertEqual(ver, self.test_rc.current_version(kind))
self.assertEqual(self.test_rc.latest_common_version(), 5)
for ver in xrange(1, 6):
@ -353,13 +332,7 @@ class RenewableCertTests(BaseRenewableCertTest):
def test_has_pending_deployment(self):
for ver in xrange(1, 6):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, ver)
self.assertEqual(ver, self.test_rc.current_version(kind))
for ver in xrange(1, 6):
self.test_rc.update_all_links_to(ver)
@ -372,24 +345,22 @@ class RenewableCertTests(BaseRenewableCertTest):
def test_names(self):
# Trying the current version
test_cert = test_util.load_vector("cert-san.pem")
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert12.pem"), self.test_rc.cert)
with open(self.test_rc.cert, "w") as f:
f.write(test_cert)
self._write_out_kind("cert", 12, test_util.load_vector("cert-san.pem"))
self.assertEqual(self.test_rc.names(),
["example.com", "www.example.com"])
# Trying a non-current version
test_cert = test_util.load_vector("cert.pem")
os.unlink(self.test_rc.cert)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"cert15.pem"), self.test_rc.cert)
with open(self.test_rc.cert, "w") as f:
f.write(test_cert)
self._write_out_kind("cert", 15, test_util.load_vector("cert.pem"))
self.assertEqual(self.test_rc.names(12),
["example.com", "www.example.com"])
# Testing common name is listed first
self._write_out_kind(
"cert", 12, test_util.load_vector("cert-5sans.pem"))
self.assertEqual(
self.test_rc.names(12),
["example.com"] + ["{0}.example.com".format(c) for c in "abcd"])
# Trying missing cert
os.unlink(self.test_rc.cert)
self.assertRaises(errors.CertStorageError, self.test_rc.names)
@ -457,13 +428,7 @@ class RenewableCertTests(BaseRenewableCertTest):
# No pending deployment
for ver in xrange(1, 6):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, ver)
self.assertFalse(self.test_rc.should_autodeploy())
def test_autorenewal_is_enabled(self):
@ -484,11 +449,7 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertFalse(self.test_rc.should_autorenew())
self.test_rc.configuration["autorenew"] = "1"
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}12.pem".format(kind)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, 12)
# Mandatory renewal on the basis of OCSP revocation
mock_ocsp.return_value = True
self.assertTrue(self.test_rc.should_autorenew())
@ -502,13 +463,7 @@ class RenewableCertTests(BaseRenewableCertTest):
for ver in xrange(1, 6):
for kind in ALL_FOUR:
where = getattr(self.test_rc, kind)
if os.path.islink(where):
os.unlink(where)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"{0}{1}.pem".format(kind, ver)), where)
with open(where, "w") as f:
f.write(kind)
self._write_out_kind(kind, ver)
self.test_rc.update_all_links_to(3)
self.assertEqual(
6, self.test_rc.save_successor(3, "new cert", None,
@ -563,39 +518,33 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10)))
self.assertFalse(os.path.exists(temp_config_file))
@mock.patch("certbot.cli.helpful_parser")
def test_relevant_values(self, mock_parser):
def _test_relevant_values_common(self, values):
option = "rsa_key_size"
mock_parser = mock.Mock(args=["--standalone"], verb="certonly",
defaults={option: cli.flag_default(option)})
from certbot.storage import relevant_values
with mock.patch("certbot.cli.helpful_parser", mock_parser):
return relevant_values(values)
def test_relevant_values(self):
"""Test that relevant_values() can reject an irrelevant value."""
# pylint: disable=protected-access
from certbot import storage
mock_parser.verb = "certonly"
mock_parser.args = ["--standalone"]
mock_action = mock.Mock(dest="rsa_key_size", default=2048)
mock_parser.parser._actions = [mock_action]
self.assertEqual(storage.relevant_values({"hello": "there"}), {})
self.assertEqual(
self._test_relevant_values_common({"hello": "there"}), {})
@mock.patch("certbot.cli.helpful_parser")
def test_relevant_values_default(self, mock_parser):
def test_relevant_values_default(self):
"""Test that relevant_values() can reject a default value."""
# pylint: disable=protected-access
from certbot import storage
mock_parser.verb = "certonly"
mock_parser.args = ["--standalone"]
mock_action = mock.Mock(dest="rsa_key_size", default=2048)
mock_parser.parser._actions = [mock_action]
self.assertEqual(storage.relevant_values({"rsa_key_size": 2048}), {})
option = "rsa_key_size"
values = {option: cli.flag_default(option)}
self.assertEqual(self._test_relevant_values_common(values), {})
@mock.patch("certbot.cli.helpful_parser")
def test_relevant_values_nondefault(self, mock_parser):
def test_relevant_values_nondefault(self):
"""Test that relevant_values() can retain a non-default value."""
# pylint: disable=protected-access
from certbot import storage
mock_parser.verb = "certonly"
mock_parser.args = ["--standalone"]
mock_action = mock.Mock(dest="rsa_key_size", default=2048)
mock_parser.parser._actions = [mock_action]
self.assertEqual(storage.relevant_values({"rsa_key_size": 12}),
{"rsa_key_size": 12})
values = {"rsa_key_size": 12}
# A copy is given to _test_relevant_values_common
# to make sure values isn't modified by the method
self.assertEqual(
self._test_relevant_values_common(values.copy()), values)
@mock.patch("certbot.storage.relevant_values")
def test_new_lineage(self, mock_rv):
@ -659,7 +608,7 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertTrue(os.path.exists(os.path.join(
self.cli_config.archive_dir, "the-lineage.com", "privkey1.pem")))
@mock.patch("certbot.storage.le_util.unique_lineage_name")
@mock.patch("certbot.storage.util.unique_lineage_name")
def test_invalid_config_filename(self, mock_uln):
from certbot import storage
mock_uln.return_value = "this_does_not_end_with_dot_conf", "yikes"
@ -760,11 +709,14 @@ class RenewableCertTests(BaseRenewableCertTest):
with open(temp2, "r") as f:
content = f.read()
# useful value was updated
assert "useful = new_value" in content
self.assertTrue("useful = new_value" in content)
# associated comment was preserved
assert "A useful value" in content
self.assertTrue("A useful value" in content)
# useless value was deleted
assert "useless" not in content
self.assertTrue("useless" not in content)
# check version was stored
self.assertTrue("version = {0}".format(certbot.__version__) in content)
if __name__ == "__main__":
unittest.main() # pragma: no cover

16
certbot/tests/testdata/cert-5sans.pem vendored Normal file
View file

@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICkTCCAjugAwIBAgIJAJNbfABWQ8bbMA0GCSqGSIb3DQEBCwUAMHkxCzAJBgNV
BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNp
c2NvMScwJQYDVQQKDB5FbGVjdHJvbmljIEZyb250aWVyIEZvdW5kYXRpb24xFDAS
BgNVBAMMC2V4YW1wbGUuY29tMB4XDTE2MDYwOTIzMDEzNloXDTE2MDcwOTIzMDEz
NloweTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcM
DVNhbiBGcmFuY2lzY28xJzAlBgNVBAoMHkVsZWN0cm9uaWMgRnJvbnRpZXIgRm91
bmRhdGlvbjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANL
ADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+6QWE
30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABo4GlMIGiMB0GA1UdDgQWBBQmz8jt
S9eUsuQlA1gkjwTAdNWXijAfBgNVHSMEGDAWgBQmz8jtS9eUsuQlA1gkjwTAdNWX
ijAMBgNVHRMEBTADAQH/MFIGA1UdEQRLMEmCDWEuZXhhbXBsZS5jb22CDWIuZXhh
bXBsZS5jb22CDWMuZXhhbXBsZS5jb22CDWQuZXhhbXBsZS5jb22CC2V4YW1wbGUu
Y29tMA0GCSqGSIb3DQEBCwUAA0EAVXmZxB+IJdgFvY2InOYeytTD1QmouDZRtj/T
H/HIpSdsfO7qr4d/ZprI2IhLRxp2S4BiU5Qc5HUkeADcpNd06A==
-----END CERTIFICATE-----

View file

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF
AANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+
6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD
QQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH
lKWVQ8+xwYMscGWK0NApHGco
-----END CERTIFICATE REQUEST-----

7
certbot/tests/testdata/os-release vendored Normal file
View file

@ -0,0 +1,7 @@
NAME="SystemdOS"
VERSION="42.42.42 LTS, Unreal"
ID=systemdos
ID_LIKE=debian
VERSION_ID="42"
HOME_URL="http://www.example.com/"
SUPPORT_URL="http://help.example.com/"

View file

@ -1,4 +1,4 @@
"""Tests for certbot.le_util."""
"""Tests for certbot.util."""
import argparse
import errno
import os
@ -11,16 +11,17 @@ import mock
import six
from certbot import errors
from certbot.tests import test_util
class RunScriptTest(unittest.TestCase):
"""Tests for certbot.le_util.run_script."""
"""Tests for certbot.util.run_script."""
@classmethod
def _call(cls, params):
from certbot.le_util import run_script
from certbot.util import run_script
return run_script(params)
@mock.patch("certbot.le_util.subprocess.Popen")
@mock.patch("certbot.util.subprocess.Popen")
def test_default(self, mock_popen):
"""These will be changed soon enough with reload."""
mock_popen().returncode = 0
@ -30,13 +31,13 @@ class RunScriptTest(unittest.TestCase):
self.assertEqual(out, "stdout")
self.assertEqual(err, "stderr")
@mock.patch("certbot.le_util.subprocess.Popen")
@mock.patch("certbot.util.subprocess.Popen")
def test_bad_process(self, mock_popen):
mock_popen.side_effect = OSError
self.assertRaises(errors.SubprocessError, self._call, ["test"])
@mock.patch("certbot.le_util.subprocess.Popen")
@mock.patch("certbot.util.subprocess.Popen")
def test_failure(self, mock_popen):
mock_popen().communicate.return_value = ("", "")
mock_popen().returncode = 1
@ -45,29 +46,29 @@ class RunScriptTest(unittest.TestCase):
class ExeExistsTest(unittest.TestCase):
"""Tests for certbot.le_util.exe_exists."""
"""Tests for certbot.util.exe_exists."""
@classmethod
def _call(cls, exe):
from certbot.le_util import exe_exists
from certbot.util import exe_exists
return exe_exists(exe)
@mock.patch("certbot.le_util.os.path.isfile")
@mock.patch("certbot.le_util.os.access")
@mock.patch("certbot.util.os.path.isfile")
@mock.patch("certbot.util.os.access")
def test_full_path(self, mock_access, mock_isfile):
mock_access.return_value = True
mock_isfile.return_value = True
self.assertTrue(self._call("/path/to/exe"))
@mock.patch("certbot.le_util.os.path.isfile")
@mock.patch("certbot.le_util.os.access")
@mock.patch("certbot.util.os.path.isfile")
@mock.patch("certbot.util.os.access")
def test_on_path(self, mock_access, mock_isfile):
mock_access.return_value = True
mock_isfile.return_value = True
self.assertTrue(self._call("exe"))
@mock.patch("certbot.le_util.os.path.isfile")
@mock.patch("certbot.le_util.os.access")
@mock.patch("certbot.util.os.path.isfile")
@mock.patch("certbot.util.os.access")
def test_not_found(self, mock_access, mock_isfile):
mock_access.return_value = False
mock_isfile.return_value = True
@ -75,7 +76,7 @@ class ExeExistsTest(unittest.TestCase):
class MakeOrVerifyDirTest(unittest.TestCase):
"""Tests for certbot.le_util.make_or_verify_dir.
"""Tests for certbot.util.make_or_verify_dir.
Note that it is not possible to test for a wrong directory owner,
as this testing script would have to be run as root.
@ -93,7 +94,7 @@ class MakeOrVerifyDirTest(unittest.TestCase):
shutil.rmtree(self.root_path, ignore_errors=True)
def _call(self, directory, mode):
from certbot.le_util import make_or_verify_dir
from certbot.util import make_or_verify_dir
return make_or_verify_dir(directory, mode, self.uid, strict=True)
def test_creates_dir_when_missing(self):
@ -116,7 +117,7 @@ class MakeOrVerifyDirTest(unittest.TestCase):
class CheckPermissionsTest(unittest.TestCase):
"""Tests for certbot.le_util.check_permissions.
"""Tests for certbot.util.check_permissions.
Note that it is not possible to test for a wrong file owner,
as this testing script would have to be run as root.
@ -131,7 +132,7 @@ class CheckPermissionsTest(unittest.TestCase):
os.remove(self.path)
def _call(self, mode):
from certbot.le_util import check_permissions
from certbot.util import check_permissions
return check_permissions(self.path, mode, self.uid)
def test_ok_mode(self):
@ -144,7 +145,7 @@ class CheckPermissionsTest(unittest.TestCase):
class UniqueFileTest(unittest.TestCase):
"""Tests for certbot.le_util.unique_file."""
"""Tests for certbot.util.unique_file."""
def setUp(self):
self.root_path = tempfile.mkdtemp()
@ -154,7 +155,7 @@ class UniqueFileTest(unittest.TestCase):
shutil.rmtree(self.root_path, ignore_errors=True)
def _call(self, mode=0o600):
from certbot.le_util import unique_file
from certbot.util import unique_file
return unique_file(self.default_name, mode)
def test_returns_fd_for_writing(self):
@ -189,7 +190,7 @@ class UniqueFileTest(unittest.TestCase):
class UniqueLineageNameTest(unittest.TestCase):
"""Tests for certbot.le_util.unique_lineage_name."""
"""Tests for certbot.util.unique_lineage_name."""
def setUp(self):
self.root_path = tempfile.mkdtemp()
@ -198,7 +199,7 @@ class UniqueLineageNameTest(unittest.TestCase):
shutil.rmtree(self.root_path, ignore_errors=True)
def _call(self, filename, mode=0o777):
from certbot.le_util import unique_lineage_name
from certbot.util import unique_lineage_name
return unique_lineage_name(self.root_path, filename, mode)
def test_basic(self):
@ -213,14 +214,14 @@ class UniqueLineageNameTest(unittest.TestCase):
self.assertTrue(isinstance(name, str))
self.assertTrue("wow-0009.conf" in name)
@mock.patch("certbot.le_util.os.fdopen")
@mock.patch("certbot.util.os.fdopen")
def test_failure(self, mock_fdopen):
err = OSError("whoops")
err.errno = errno.EIO
mock_fdopen.side_effect = err
self.assertRaises(OSError, self._call, "wow")
@mock.patch("certbot.le_util.os.fdopen")
@mock.patch("certbot.util.os.fdopen")
def test_subsequent_failure(self, mock_fdopen):
self._call("wow")
err = OSError("whoops")
@ -230,7 +231,7 @@ class UniqueLineageNameTest(unittest.TestCase):
class SafelyRemoveTest(unittest.TestCase):
"""Tests for certbot.le_util.safely_remove."""
"""Tests for certbot.util.safely_remove."""
def setUp(self):
self.tmp = tempfile.mkdtemp()
@ -240,7 +241,7 @@ class SafelyRemoveTest(unittest.TestCase):
shutil.rmtree(self.tmp)
def _call(self):
from certbot.le_util import safely_remove
from certbot.util import safely_remove
return safely_remove(self.path)
def test_exists(self):
@ -254,7 +255,7 @@ class SafelyRemoveTest(unittest.TestCase):
# no error, yay!
self.assertFalse(os.path.exists(self.path))
@mock.patch("certbot.le_util.os.remove")
@mock.patch("certbot.util.os.remove")
def test_other_error_passthrough(self, mock_remove):
mock_remove.side_effect = OSError
self.assertRaises(OSError, self._call)
@ -264,7 +265,7 @@ class SafeEmailTest(unittest.TestCase):
"""Test safe_email."""
@classmethod
def _call(cls, addr):
from certbot.le_util import safe_email
from certbot.util import safe_email
return safe_email(addr)
def test_valid_emails(self):
@ -292,7 +293,7 @@ class AddDeprecatedArgumentTest(unittest.TestCase):
self.parser = argparse.ArgumentParser()
def _call(self, argument_name, nargs):
from certbot.le_util import add_deprecated_argument
from certbot.util import add_deprecated_argument
add_deprecated_argument(self.parser.add_argument, argument_name, nargs)
@ -308,14 +309,14 @@ class AddDeprecatedArgumentTest(unittest.TestCase):
def _get_argparse_warnings(self, args):
stderr = six.StringIO()
with mock.patch("certbot.le_util.sys.stderr", new=stderr):
with mock.patch("certbot.util.sys.stderr", new=stderr):
self.parser.parse_args(args)
return stderr.getvalue()
def test_help(self):
self._call("--old-option", 2)
stdout = six.StringIO()
with mock.patch("certbot.le_util.sys.stdout", new=stdout):
with mock.patch("certbot.util.sys.stdout", new=stdout):
try:
self.parser.parse_args(["-h"])
except SystemExit:
@ -327,7 +328,7 @@ class EnforceDomainSanityTest(unittest.TestCase):
"""Test enforce_domain_sanity."""
def _call(self, domain):
from certbot.le_util import enforce_domain_sanity
from certbot.util import enforce_domain_sanity
return enforce_domain_sanity(domain)
def test_nonascii_str(self):
@ -339,5 +340,67 @@ class EnforceDomainSanityTest(unittest.TestCase):
u"eichh\u00f6rnchen.example.com")
class OsInfoTest(unittest.TestCase):
"""Test OS / distribution detection"""
def test_systemd_os_release(self):
from certbot.util import (get_os_info, get_systemd_os_info,
get_os_info_ua)
with mock.patch('os.path.isfile', return_value=True):
self.assertEqual(get_os_info(
test_util.vector_path("os-release"))[0], 'systemdos')
self.assertEqual(get_os_info(
test_util.vector_path("os-release"))[1], '42')
self.assertEqual(get_systemd_os_info("/dev/null"), ("", ""))
self.assertEqual(get_os_info_ua(
test_util.vector_path("os-release")),
"SystemdOS")
with mock.patch('os.path.isfile', return_value=False):
self.assertEqual(get_systemd_os_info(), ("", ""))
@mock.patch("certbot.util.subprocess.Popen")
def test_non_systemd_os_info(self, popen_mock):
from certbot.util import (get_os_info, get_python_os_info,
get_os_info_ua)
with mock.patch('os.path.isfile', return_value=False):
with mock.patch('platform.system_alias',
return_value=('NonSystemD', '42', '42')):
self.assertEqual(get_os_info()[0], 'nonsystemd')
self.assertEqual(get_os_info_ua(),
" ".join(get_python_os_info()))
with mock.patch('platform.system_alias',
return_value=('darwin', '', '')):
comm_mock = mock.Mock()
comm_attrs = {'communicate.return_value':
('42.42.42', 'error')}
comm_mock.configure_mock(**comm_attrs) # pylint: disable=star-args
popen_mock.return_value = comm_mock
self.assertEqual(get_os_info()[0], 'darwin')
self.assertEqual(get_os_info()[1], '42.42.42')
with mock.patch('platform.system_alias',
return_value=('linux', '', '')):
with mock.patch('platform.linux_distribution',
return_value=('', '', '')):
self.assertEqual(get_python_os_info(), ("linux", ""))
with mock.patch('platform.linux_distribution',
return_value=('testdist', '42', '')):
self.assertEqual(get_python_os_info(), ("testdist", "42"))
with mock.patch('platform.system_alias',
return_value=('freebsd', '9.3-RC3-p1', '')):
self.assertEqual(get_python_os_info(), ("freebsd", "9"))
with mock.patch('platform.system_alias',
return_value=('windows', '', '')):
with mock.patch('platform.win32_ver',
return_value=('4242', '95', '2', '')):
self.assertEqual(get_python_os_info(),
("windows", "95"))
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -1,6 +1,9 @@
"""Utilities for all Certbot."""
import argparse
import collections
# distutils.version under virtualenv confuses pylint
# For more info, see: https://github.com/PyCQA/pylint/issues/73
import distutils.version # pylint: disable=import-error,no-name-in-module
import errno
import logging
import os
@ -151,7 +154,8 @@ def _unique_file(path, filename_pat, count, mode):
while True:
current_path = os.path.join(path, filename_pat(count))
try:
return safe_open(current_path, chmod=mode), current_path
return safe_open(current_path, chmod=mode),\
os.path.abspath(current_path)
except OSError as err:
# "File exists," is okay, try a different name.
if err.errno != errno.EEXIST:
@ -209,9 +213,95 @@ def safely_remove(path):
raise
def get_os_info():
def get_os_info(filepath="/etc/os-release"):
"""
Get OS name and version
:param str filepath: File path of os-release file
:returns: (os_name, os_version)
:rtype: `tuple` of `str`
"""
if os.path.isfile(filepath):
# Systemd os-release parsing might be viable
os_name, os_version = get_systemd_os_info(filepath=filepath)
if os_name:
return (os_name, os_version)
# Fallback to platform module
return get_python_os_info()
def get_os_info_ua(filepath="/etc/os-release"):
"""
Get OS name and version string for User Agent
:param str filepath: File path of os-release file
:returns: os_ua
:rtype: `str`
"""
if os.path.isfile(filepath):
os_ua = _get_systemd_os_release_var("PRETTY_NAME", filepath=filepath)
if not os_ua:
os_ua = _get_systemd_os_release_var("NAME", filepath=filepath)
if os_ua:
return os_ua
# Fallback
return " ".join(get_python_os_info())
def get_systemd_os_info(filepath="/etc/os-release"):
"""
Parse systemd /etc/os-release for distribution information
:param str filepath: File path of os-release file
:returns: (os_name, os_version)
:rtype: `tuple` of `str`
"""
os_name = _get_systemd_os_release_var("ID", filepath=filepath)
os_version = _get_systemd_os_release_var("VERSION_ID", filepath=filepath)
return (os_name, os_version)
def _get_systemd_os_release_var(varname, filepath="/etc/os-release"):
"""
Get single value from systemd /etc/os-release
:param str varname: Name of variable to fetch
:param str filepath: File path of os-release file
:returns: requested value
:rtype: `str`
"""
var_string = varname+"="
if not os.path.isfile(filepath):
return ""
with open(filepath, 'r') as fh:
contents = fh.readlines()
for line in contents:
if line.strip().startswith(var_string):
# Return the value of var, normalized
return _normalize_string(line.strip()[len(var_string):])
return ""
def _normalize_string(orig):
"""
Helper function for _get_systemd_os_release_var() to remove quotes
and whitespaces
"""
return orig.replace('"', '').replace("'", "").strip()
def get_python_os_info():
"""
Get Operating System type/distribution and major version
using python platform module
:returns: (os_name, os_version)
:rtype: `tuple` of `str`
@ -235,8 +325,7 @@ def get_os_info():
os_ver = subprocess.Popen(
["sw_vers", "-productVersion"],
stdout=subprocess.PIPE
).communicate()[0]
os_ver = os_ver.partition(".")[0]
).communicate()[0].rstrip('\n')
elif os_type.startswith('freebsd'):
# eg "9.3-RC3-p1"
os_ver = os_ver.partition("-")[0]
@ -313,7 +402,7 @@ def enforce_domain_sanity(domain):
domain = domain.encode('ascii').lower()
except UnicodeError:
error_fmt = (u"Internationalized domain names "
"are not presently supported: {0}")
"are not presently supported: {0}")
if isinstance(domain, six.text_type):
raise errors.ConfigurationError(error_fmt.format(domain))
else:
@ -340,5 +429,20 @@ def enforce_domain_sanity(domain):
# first and last char is not "-"
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,63}$")
if not fqdn.match(domain):
raise errors.ConfigurationError("Requested domain {0} is not a FQDN".format(domain))
raise errors.ConfigurationError("Requested domain {0} is not a FQDN"
.format(domain))
return domain
def get_strict_version(normalized):
"""Converts a normalized version to a strict version.
:param str normalized: normalized version string
:returns: An equivalent strict version
:rtype: distutils.version.StrictVersion
"""
# strict version ending with "a" and a number designates a pre-release
# pylint: disable=no-member
return distutils.version.StrictVersion(normalized.replace(".dev", "a"))

View file

@ -1,5 +0,0 @@
:mod:`certbot.le_util`
--------------------------
.. automodule:: certbot.le_util
:members:

5
docs/api/util.rst Normal file
View file

@ -0,0 +1,5 @@
:mod:`certbot.util`
--------------------------
.. automodule:: certbot.util
:members:

View file

@ -10,6 +10,7 @@ cert. Major SUBCOMMANDS are:
install Install a previously obtained cert in a server
renew Renew previously obtained certs that are near expiry
revoke Revoke a previously obtained certificate
register Perform tasks related to registering with the CA
rollback Rollback server configuration changes made during install
config_changes Show changes made to server config during installation
plugins Display information about installed plugins
@ -28,6 +29,7 @@ optional arguments:
require additional command line flags; the client will
try to explain which ones are required if it finds one
missing (default: False)
--dialog Run using dialog (default: False)
--dry-run Perform a test run of the client, obtaining test
(invalid) certs but not saving them to disk. This can
currently only be used with the 'certonly' and 'renew'
@ -52,6 +54,11 @@ optional arguments:
to the Subscriber Agreement will still affect you, and
will be effective 14 days after posting an update to
the web site. (default: False)
--update-registration
With the register verb, indicates that details
associated with an existing registration, such as the
e-mail address, should be updated, rather than
registering a new account. (default: False)
-m EMAIL, --email EMAIL
Email used for registration and recovery contact.
(default: None)
@ -130,6 +137,10 @@ security:
Security parameters & server settings
--rsa-key-size N Size of the RSA key. (default: 2048)
--must-staple Adds the OCSP Must Staple extension to the
certificate. Autoconfigures OCSP Stapling for
supported setups (Apache version >= 2.3.3 ). (default:
False)
--redirect Automatically redirect all HTTP traffic to HTTPS for
the newly authenticated vhost. (default: None)
--no-redirect Do not automatically redirect all HTTP traffic to
@ -148,6 +159,11 @@ security:
--no-uir Do not automatically set the "Content-Security-Policy:
upgrade-insecure-requests" header to every HTTP
response. (default: None)
--staple-ocsp Enables OCSP Stapling. A valid OCSP response is
stapled to the certificate that the server offers
during TLS. (default: None)
--no-staple-ocsp Do not automatically enable OCSP Stapling. (default:
None)
--strict-permissions Require that all configuration files are owned by the
current user; only needed if your config is somewhere
unsafe like /tmp/ (default: False)
@ -173,7 +189,9 @@ renew:
Command to be run in a shell after attempting to
obtain/renew certificates. Can be used to deploy
renewed certificates, or to restart any servers that
were stopped by --pre-hook. (default: None)
were stopped by --pre-hook. This is only run if an
attempt was made to obtain/renew a certificate.
(default: None)
--renew-hook RENEW_HOOK
Command to be run in a shell once for each
successfully renewed certificate.For this command, the
@ -263,15 +281,6 @@ plugins:
--webroot Obtain certs by placing files in a webroot directory.
(default: False)
nginx:
Nginx Web Server - currently doesn't work
--nginx-server-root NGINX_SERVER_ROOT
Nginx server root directory. (default: /etc/nginx)
--nginx-ctl NGINX_CTL
Path to the 'nginx' binary, used for 'configtest' and
retrieving nginx version number. (default: nginx)
standalone:
Automatically use a temporary webserver
@ -288,6 +297,15 @@ manual:
Automatically allows public IP logging. (default:
False)
nginx:
Nginx Web Server - currently doesn't work
--nginx-server-root NGINX_SERVER_ROOT
Nginx server root directory. (default: /etc/nginx)
--nginx-ctl NGINX_CTL
Path to the 'nginx' binary, used for 'configtest' and
retrieving nginx version number. (default: nginx)
webroot:
Place files in webroot directory

View file

@ -266,8 +266,7 @@ with the core upstream source code. An example is provided in
it with any necessary API changes.
.. _`setuptools entry points`:
https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins
http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points
.. _coding-style:

View file

@ -1 +1 @@
.. literalinclude:: cli-help.txt
.. literalinclude:: ../cli-help.txt

View file

@ -440,7 +440,7 @@ Operating System Packages
.. code-block:: shell
sudo pacman -S letsencrypt
sudo pacman -S certbot
**Debian**

View file

@ -16,7 +16,7 @@ here = os.path.abspath(os.path.dirname(__file__))
readme = read_file(os.path.join(here, 'README.rst'))
version = '0.7.0.dev0'
version = '0.8.0.dev0'
# This package is a simple shim around certbot-apache

View file

@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.6.0"
LE_AUTO_VERSION="0.8.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -38,17 +38,6 @@ Help for certbot itself cannot be provided until it is installed.
All arguments are accepted and forwarded to the Certbot client when run."
while getopts ":hnv" arg; do
case $arg in
h)
HELP=1;;
n)
ASSUME_YES=1;;
v)
VERBOSE=1;;
esac
done
for arg in "$@" ; do
case "$arg" in
--debug)
@ -65,9 +54,26 @@ for arg in "$@" ; do
ASSUME_YES=1;;
--verbose)
VERBOSE=1;;
-[!-]*)
while getopts ":hnv" short_arg $arg; do
case "$short_arg" in
h)
HELP=1;;
n)
ASSUME_YES=1;;
v)
VERBOSE=1;;
esac
done;;
esac
done
if [ $BASENAME = "letsencrypt-auto" ]; then
# letsencrypt-auto does not respect --help or --yes for backwards compatibility
ASSUME_YES=1
HELP=0
fi
# certbot-auto needs root access to bootstrap OS dependencies, and
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
@ -107,12 +113,6 @@ else
SUDO=
fi
if [ $BASENAME = "letsencrypt-auto" ]; then
# letsencrypt-auto does not respect --help or --yes for backwards compatibility
ASSUME_YES=1
HELP=0
fi
ExperimentalBootstrap() {
# Arguments: Platform name, bootstrap function name
if [ "$DEBUG" = 1 ]; then
@ -425,7 +425,8 @@ BootstrapMac() {
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" ]; then
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.
# python.org, MacPorts or HomeBrew Python installations should all be OK.
echo "Installing python..."
@ -435,7 +436,8 @@ BootstrapMac() {
# Workaround for _dlopen not finding augeas on OS X
if [ "$pkgman" = "port" ] && ! [ -e "/usr/local/lib/libaugeas.dylib" ] && [ -e "/opt/local/lib/libaugeas.dylib" ]; then
echo "Applying augeas workaround"
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib
$SUDO mkdir -p /usr/local/lib/
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib/
fi
if ! hash pip 2>/dev/null; then
@ -451,6 +453,11 @@ BootstrapMac() {
fi
}
BootstrapSmartOS() {
pkgin update
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
}
# Install required OS packages:
Bootstrap() {
@ -483,8 +490,10 @@ Bootstrap() {
ExperimentalBootstrap "FreeBSD" BootstrapFreeBsd
elif uname | grep -iq Darwin ; then
ExperimentalBootstrap "Mac OS X" BootstrapMac
elif grep -iq "Amazon Linux" /etc/issue ; then
elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then
ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
elif [ -f /etc/product ] && grep -q "Joyent Instance" /etc/product ; then
ExperimentalBootstrap "Joyent SmartOS Zone" BootstrapSmartOS
else
echo "Sorry, I don't know how to bootstrap Certbot on your operating system!"
echo
@ -523,6 +532,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
@ -703,24 +713,21 @@ zope.interface==4.1.3 \
mock==1.0.1 \
--hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f \
--hash=sha256:8f83080daa249d036cbccfb8ae5cc6ff007b88d6d937521371afabe7b19badbc
letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.6.0 \
--hash=sha256:cbe4e7a340a19725a8740ed86e30abdbe18fc22c4c6022b7a8e56642d502bcc3 \
--hash=sha256:ec4e6009dfbd629b58473eb06bbebfd9fb2a79fc8831c149e9205bc38a98ecc6
certbot==0.6.0 \
--hash=sha256:a893632d228864b0a751db9f3fdd93439ed34b988ea21b64fb0f0fa2ceded6a2 \
--hash=sha256:80b0b7dc5afeec2816ef638a61e7c628d73cd72666eebf4984be426d1c2b492d
certbot-apache==0.6.0 \
--hash=sha256:0ab077f0913b81ed5c1b141c3a7c4c0228ef3738d8d61a93db794d9a80718d43 \
--hash=sha256:1cfbe751209079a803758f472200816fac559f2a36fdd582d25e3ba5601423a1
letsencrypt==0.6.0 \
--hash=sha256:93196c7dcd57272a753e525d145c5a9987c8968c22ec954bcf83dcc9d2499a76 \
--hash=sha256:a16d6c395f1bf5fd61a28ef83dc78f42dbecbad9d00be6236f2ad8915645c154
letsencrypt-apache==0.6.0 \
--hash=sha256:02fadc52a0796e53978c508beec9c53e1fc047660240832b9bde5d53ab3a1379 \
--hash=sha256:1c5522d94d7750bdb9bfa6201d2c263e914f662c9d0079e673167233cf4364f1
acme==0.8.0 \
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
certbot==0.8.0 \
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
certbot-apache==0.8.0 \
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -880,7 +887,6 @@ UNLIKELY_EOF
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
PIP_STATUS=$?
set -e
rm -rf "$TEMP_DIR"
if [ "$PIP_STATUS" != 0 ]; then
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages:"
@ -890,14 +896,16 @@ UNLIKELY_EOF
fi
echo "Installation succeeded."
fi
echo "Requesting root privileges to run certbot..."
if [ -n "$SUDO" ]; then
# SUDO is su wrapper or sudo
echo "Requesting root privileges to run certbot..."
echo " $VENV_BIN/letsencrypt" "$@"
fi
if [ -z "$SUDO_ENV" ] ; then
# SUDO is su wrapper / noop
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
$SUDO "$VENV_BIN/letsencrypt" "$@"
else
# sudo
echo " " $SUDO "$SUDO_ENV" "$VENV_BIN/letsencrypt" "$@"
$SUDO "$SUDO_ENV" "$VENV_BIN/letsencrypt" "$@"
fi
@ -923,8 +931,8 @@ else
fi
if [ "$NO_SELF_UPGRADE" != 1 ]; then
echo "Checking for new version..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
"""Do downloading and JSON parsing without additional dependencies. ::
@ -997,7 +1005,7 @@ def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get(
environ.get('LE_AUTO_JSON_URL',
'https://pypi.python.org/pypi/letsencrypt/json')))
'https://pypi.python.org/pypi/certbot/json')))
# metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most
@ -1016,7 +1024,7 @@ def verified_new_le_auto(get, tag, temp_dir):
"""
le_auto_dir = environ.get(
'LE_AUTO_DIR_TEMPLATE',
'https://raw.githubusercontent.com/letsencrypt/letsencrypt/%s/'
'https://raw.githubusercontent.com/certbot/certbot/%s/'
'letsencrypt-auto-source/') % tag
write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto')
write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig')
@ -1079,8 +1087,6 @@ UNLIKELY_EOF
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.
fi # Self-upgrading is allowed.

View file

@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQEcBAABAgAGBQJXM9ZDAAoJEE0XyZXNl3XyzGkH/2KeR0jYxXKlvwfCkxU6hSC0
eXcxZVQk59hCSvkNGE6Mj6rwQcyjSqmRp14MaJpq7NZADN6F+HWb6VB/Wq6moMQs
PJtthqwhF767Qg+Py9Hp6XmlKscjXB6AKCVxq5TBwEIOTtj0rhQRLF9/+GW6jFuf
kT6aUcDWNjOyWWUtp9vOVprDtegrltp0/2DNitlvPu263pKC+7I3GyLTq4fKP4EE
auZSAhFry9SNR3Usf2wD3kzhvLSrT3h9Yh5oA04oaX9H6e86EHwt6RJJRHpg8s6b
e0CBIIuaRJEmdiMUWlV/gAfH6M2PbG1wtJdxc0ThNEoWAjTsopr61BoHJ3cpCy4=
=+e7/
iQEcBAABAgAGBQJXUJvwAAoJEE0XyZXNl3XyvKsH/3qn7Xa/GQx3HvB6Io/Csn/E
v1nbUg5RPwvrTyyol8BJ6UrHiJw+gTbUgCAnBkZ7DYKaC8AQmQXVRcWXNALMMTzB
6LpBXjQQ2xrBYamGj70N7KnTM1QmxI96GUQouiHMJVugV4uihKJDjtR8/f2JWKok
ZSox6E4LqC45HzqLWiOqc13TrHbti32Mo8DyC63PBnSwMnypGLK6XcqM0L9Re62W
smoKu1VWKwWZYRYXIQr0dvK4JmVTrIsdASdZkhTC/vc8y4tGkdN0DcF2EHzci6OA
Tx0W+Ao+HM1ZcaaH3BJ1y3kYfT+mlt6o4OaK3UB/wtUzMmVih7l1UeiNkVL0oYk=
=t3L6
-----END PGP SIGNATURE-----

View file

@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.7.0.dev0"
LE_AUTO_VERSION="0.9.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -172,7 +172,7 @@ BootstrapDebCommon() {
# distro version (#346)
virtualenv=
if apt-cache show virtualenv > /dev/null 2>&1; then
if apt-cache show virtualenv > /dev/null 2>&1 && ! apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi
@ -425,7 +425,8 @@ BootstrapMac() {
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" ]; then
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.
# python.org, MacPorts or HomeBrew Python installations should all be OK.
echo "Installing python..."
@ -457,12 +458,39 @@ BootstrapSmartOS() {
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
}
BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
python \
libpython-devel \
python-virtualenv
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
if ! $SUDO urpmi --force \
git \
gcc \
cdialog \
python-augeas \
libopenssl-devel \
libffi-devel \
rootcerts
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
}
# Install required OS packages:
Bootstrap() {
if [ -f /etc/debian_version ]; then
echo "Bootstrapping dependencies for Debian-based OSes..."
BootstrapDebCommon
elif [ -f /etc/mageia-release ] ; then
# Mageia has both /etc/mageia-release and /etc/redhat-release
ExperimentalBootstrap "Mageia" BootstrapMageiaCommon
elif [ -f /etc/redhat-release ]; then
echo "Bootstrapping dependencies for RedHat-based OSes..."
BootstrapRpmCommon
@ -475,7 +503,7 @@ Bootstrap() {
BootstrapArchCommon
else
echo "Please use pacman to install letsencrypt packages:"
echo "# pacman -S letsencrypt letsencrypt-apache"
echo "# pacman -S certbot certbot-apache"
echo
echo "If you would like to use the virtualenv way, please run the script again with the"
echo "--debug flag."
@ -499,6 +527,7 @@ Bootstrap() {
echo "You will need to bootstrap, configure virtualenv, and run pip install manually."
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
echo "for more info."
exit 1
fi
}
@ -531,6 +560,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
@ -711,24 +741,21 @@ zope.interface==4.1.3 \
mock==1.0.1 \
--hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f \
--hash=sha256:8f83080daa249d036cbccfb8ae5cc6ff007b88d6d937521371afabe7b19badbc
letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.6.0 \
--hash=sha256:cbe4e7a340a19725a8740ed86e30abdbe18fc22c4c6022b7a8e56642d502bcc3 \
--hash=sha256:ec4e6009dfbd629b58473eb06bbebfd9fb2a79fc8831c149e9205bc38a98ecc6
certbot==0.6.0 \
--hash=sha256:a893632d228864b0a751db9f3fdd93439ed34b988ea21b64fb0f0fa2ceded6a2 \
--hash=sha256:80b0b7dc5afeec2816ef638a61e7c628d73cd72666eebf4984be426d1c2b492d
certbot-apache==0.6.0 \
--hash=sha256:0ab077f0913b81ed5c1b141c3a7c4c0228ef3738d8d61a93db794d9a80718d43 \
--hash=sha256:1cfbe751209079a803758f472200816fac559f2a36fdd582d25e3ba5601423a1
letsencrypt==0.6.0 \
--hash=sha256:93196c7dcd57272a753e525d145c5a9987c8968c22ec954bcf83dcc9d2499a76 \
--hash=sha256:a16d6c395f1bf5fd61a28ef83dc78f42dbecbad9d00be6236f2ad8915645c154
letsencrypt-apache==0.6.0 \
--hash=sha256:02fadc52a0796e53978c508beec9c53e1fc047660240832b9bde5d53ab3a1379 \
--hash=sha256:1c5522d94d7750bdb9bfa6201d2c263e914f662c9d0079e673167233cf4364f1
acme==0.8.0 \
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
certbot==0.8.0 \
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
certbot-apache==0.8.0 \
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -888,7 +915,6 @@ UNLIKELY_EOF
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
PIP_STATUS=$?
set -e
rm -rf "$TEMP_DIR"
if [ "$PIP_STATUS" != 0 ]; then
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages:"
@ -934,6 +960,7 @@ else
if [ "$NO_SELF_UPGRADE" != 1 ]; then
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
"""Do downloading and JSON parsing without additional dependencies. ::
@ -1006,7 +1033,7 @@ def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get(
environ.get('LE_AUTO_JSON_URL',
'https://pypi.python.org/pypi/letsencrypt/json')))
'https://pypi.python.org/pypi/certbot/json')))
# metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most
@ -1088,8 +1115,6 @@ UNLIKELY_EOF
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.
fi # Self-upgrading is allowed.

View file

@ -155,12 +155,16 @@ DeterminePythonVersion() {
{{ bootstrappers/free_bsd.sh }}
{{ bootstrappers/mac.sh }}
{{ bootstrappers/smartos.sh }}
{{ bootstrappers/mageia_common.sh }}
# Install required OS packages:
Bootstrap() {
if [ -f /etc/debian_version ]; then
echo "Bootstrapping dependencies for Debian-based OSes..."
BootstrapDebCommon
elif [ -f /etc/mageia-release ] ; then
# Mageia has both /etc/mageia-release and /etc/redhat-release
ExperimentalBootstrap "Mageia" BootstrapMageiaCommon
elif [ -f /etc/redhat-release ]; then
echo "Bootstrapping dependencies for RedHat-based OSes..."
BootstrapRpmCommon
@ -173,7 +177,7 @@ Bootstrap() {
BootstrapArchCommon
else
echo "Please use pacman to install letsencrypt packages:"
echo "# pacman -S letsencrypt letsencrypt-apache"
echo "# pacman -S certbot certbot-apache"
echo
echo "If you would like to use the virtualenv way, please run the script again with the"
echo "--debug flag."
@ -197,6 +201,7 @@ Bootstrap() {
echo "You will need to bootstrap, configure virtualenv, and run pip install manually."
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
echo "for more info."
exit 1
fi
}
@ -229,6 +234,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
@ -245,7 +251,6 @@ UNLIKELY_EOF
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
PIP_STATUS=$?
set -e
rm -rf "$TEMP_DIR"
if [ "$PIP_STATUS" != 0 ]; then
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages:"
@ -291,6 +296,7 @@ else
if [ "$NO_SELF_UPGRADE" != 1 ]; then
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
{{ fetch.py }}
@ -319,8 +325,6 @@ UNLIKELY_EOF
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.
fi # Self-upgrading is allowed.

View file

@ -23,7 +23,7 @@ BootstrapDebCommon() {
# distro version (#346)
virtualenv=
if apt-cache show virtualenv > /dev/null 2>&1; then
if apt-cache show virtualenv > /dev/null 2>&1 && ! apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi

View file

@ -0,0 +1,23 @@
BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
python \
libpython-devel \
python-virtualenv
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
if ! $SUDO urpmi --force \
git \
gcc \
cdialog \
python-augeas \
libopenssl-devel \
libffi-devel \
rootcerts
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
}

View file

@ -68,7 +68,7 @@ def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get(
environ.get('LE_AUTO_JSON_URL',
'https://pypi.python.org/pypi/letsencrypt/json')))
'https://pypi.python.org/pypi/certbot/json')))
# metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most

View file

@ -175,21 +175,18 @@ zope.interface==4.1.3 \
mock==1.0.1 \
--hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f \
--hash=sha256:8f83080daa249d036cbccfb8ae5cc6ff007b88d6d937521371afabe7b19badbc
letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.6.0 \
--hash=sha256:cbe4e7a340a19725a8740ed86e30abdbe18fc22c4c6022b7a8e56642d502bcc3 \
--hash=sha256:ec4e6009dfbd629b58473eb06bbebfd9fb2a79fc8831c149e9205bc38a98ecc6
certbot==0.6.0 \
--hash=sha256:a893632d228864b0a751db9f3fdd93439ed34b988ea21b64fb0f0fa2ceded6a2 \
--hash=sha256:80b0b7dc5afeec2816ef638a61e7c628d73cd72666eebf4984be426d1c2b492d
certbot-apache==0.6.0 \
--hash=sha256:0ab077f0913b81ed5c1b141c3a7c4c0228ef3738d8d61a93db794d9a80718d43 \
--hash=sha256:1cfbe751209079a803758f472200816fac559f2a36fdd582d25e3ba5601423a1
letsencrypt==0.6.0 \
--hash=sha256:93196c7dcd57272a753e525d145c5a9987c8968c22ec954bcf83dcc9d2499a76 \
--hash=sha256:a16d6c395f1bf5fd61a28ef83dc78f42dbecbad9d00be6236f2ad8915645c154
letsencrypt-apache==0.6.0 \
--hash=sha256:02fadc52a0796e53978c508beec9c53e1fc047660240832b9bde5d53ab3a1379 \
--hash=sha256:1c5522d94d7750bdb9bfa6201d2c263e914f662c9d0079e673167233cf4364f1
acme==0.8.0 \
--hash=sha256:8561d590e496afb41a8ff2dac389199661d9cd785b1636ae08325771511189af \
--hash=sha256:dfa86b547628b231f275c7e0efc7a09bec5dfaec866f89f5c5b59b78c14564da
certbot==0.8.0 \
--hash=sha256:395c5840ff6b75aa51ee6449c86d016c14c5f65a71281e7bcef5feecac6a3293 \
--hash=sha256:3c3c70b484fb3243a166515adc81ae0401c5d687a2763c75b40df9d8241a4314
certbot-apache==0.8.0 \
--hash=sha256:f4d4fc962ecc19646f6745d49c62a265d26e5b2df3acf34ef4865351594156e3 \
--hash=sha256:cfb211debbcb0d0645c88d7e8bb38c591fca263bfdb5337242c023956055e268

View file

@ -11,7 +11,7 @@ from shutil import copy, rmtree
import socket
import ssl
from stat import S_IRUSR, S_IXUSR
from subprocess import CalledProcessError, check_output, Popen, PIPE
from subprocess import CalledProcessError, Popen, PIPE
import sys
from tempfile import mkdtemp
from threading import Thread
@ -146,7 +146,9 @@ def out_and_err(command, input=None, shell=False, env=None):
out, err = process.communicate(input=input)
status = process.poll() # same as in check_output(), though wait() sounds better
if status:
raise CalledProcessError(status, command, output=out)
error = CalledProcessError(status, command)
error.output = out
raise error
return out, err
@ -183,7 +185,7 @@ def run_le_auto(venv_dir, base_url, **kwargs):
d = dict(XDG_DATA_HOME=venv_dir,
# URL to PyPI-style JSON that tell us the latest released version
# of LE:
LE_AUTO_JSON_URL=base_url + 'letsencrypt/json',
LE_AUTO_JSON_URL=base_url + 'certbot/json',
# URL to dir containing letsencrypt-auto and letsencrypt-auto.sig:
LE_AUTO_DIR_TEMPLATE=base_url + '%s/',
# The public key corresponding to signing.key:
@ -258,7 +260,7 @@ class AutoTests(TestCase):
with ephemeral_dir() as venv_dir:
# This serves a PyPI page with a higher version, a GitHub-alike
# with a corresponding le-auto script, and a matching signature.
resources = {'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}),
'v99.9.9/letsencrypt-auto': NEW_LE_AUTO,
'v99.9.9/letsencrypt-auto.sig': NEW_LE_AUTO_SIG}
with serving(resources) as base_url:
@ -301,8 +303,8 @@ class AutoTests(TestCase):
with ephemeral_dir() as venv_dir:
# Serve an unrelated hash signed with the good key (easier than
# making a bad key, and a mismatch is a mismatch):
resources = {'': '<a href="letsencrypt/">letsencrypt/</a>',
'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
resources = {'': '<a href="certbot/">certbot/</a>',
'certbot/json': dumps({'releases': {'99.9.9': None}}),
'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'),
'v99.9.9/letsencrypt-auto.sig': signed('something else')}
with serving(resources) as base_url:
@ -320,8 +322,8 @@ class AutoTests(TestCase):
def test_pip_failure(self):
"""Make sure pip stops us if there is a hash mismatch."""
with ephemeral_dir() as venv_dir:
resources = {'': '<a href="letsencrypt/">letsencrypt/</a>',
'letsencrypt/json': dumps({'releases': {'99.9.9': None}})}
resources = {'': '<a href="certbot/">certbot/</a>',
'certbot/json': dumps({'releases': {'99.9.9': None}})}
with serving(resources) as base_url:
# Build a le-auto script embedding a bad requirements file:
install_le_auto(

View file

@ -16,7 +16,7 @@ here = os.path.abspath(os.path.dirname(__file__))
readme = read_file(os.path.join(here, 'README.rst'))
version = '0.7.0.dev0'
version = '0.8.0.dev0'
# This package is a simple shim around certbot-nginx

View file

@ -20,7 +20,7 @@ readme = read_file(os.path.join(here, 'README.rst'))
install_requires = ['certbot']
version = '0.7.0.dev0'
version = '0.8.0.dev0'
setup(

8
letsencrypt/tests/testdata/os-release vendored Normal file
View file

@ -0,0 +1,8 @@
NAME="SystemdOS"
VERSION="42.42.42 LTS, Unreal"
ID=systemdos
ID_LIKE=debian
PRETTY_NAME="SystemdOS 42.42.42 Unreal"
VERSION_ID="42"
HOME_URL="http://www.example.com/"
SUPPORT_URL="http://help.example.com/"

View file

@ -2,16 +2,4 @@
set -e # Fail fast
# PEP8 is not ignored in ACME
pep8 --config=acme/.pep8 acme
pep8 \
setup.py \
certbot \
certbot-apache \
certbot-nginx \
certbot-compatibility-test \
letshelp-certbot \
|| echo "PEP8 checking failed, but it's ignored in Travis"
# echo exits with 0

View file

@ -40,7 +40,7 @@ install_requires = [
'configobj',
'cryptography>=0.7', # load_pem_x509_certificate
'parsedatetime>=1.3', # Calendar.parseDT
'psutil>=2.1.0', # net_connections introduced in 2.1.0
'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
@ -71,7 +71,6 @@ dev_extras = [
'nose',
'nosexcover',
'pep8',
'pkginfo<=1.2.1',
'pylint==1.4.2', # upstream #248
'tox',
'twine',

View file

@ -3,7 +3,7 @@
set -xe
# Check out special branch until latest docker changes land in Boulder master.
git clone -b rev-rev https://github.com/letsencrypt/boulder $BOULDERPATH
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
cd $BOULDERPATH
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
docker-compose up -d

View file

@ -43,8 +43,8 @@ export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
common auth --csr "$CSR_PATH" \
--cert-path "${root}/csr/cert.pem" \
--chain-path "${root}/csr/chain.pem"
openssl x509 -in "${root}/csr/0000_cert.pem" -text
openssl x509 -in "${root}/csr/0000_chain.pem" -text
openssl x509 -in "${root}/csr/cert.pem" -text
openssl x509 -in "${root}/csr/chain.pem" -text
common --domains le3.wtf install \
--cert-path "${root}/csr/cert.pem" \
@ -84,6 +84,24 @@ if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; the
exit 1
fi
# ECDSA
openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem"
SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \
-config "${OPENSSL_CNF:-openssl.cnf}" \
-key "${root}/privkey-p384.pem" \
-subj "/" \
-reqexts san \
-outform der \
-out "${root}/csr-p384.der"
common auth --csr "${root}/csr-p384.der" \
--cert-path "${root}/csr/cert-p384.pem" \
--chain-path "${root}/csr/chain-p384.pem"
openssl x509 -in "${root}/csr/cert-p384.pem" -text | grep 'ASN1 OID: secp384r1'
# OCSP Must Staple
common auth --must-staple --domains "must-staple.le.wtf"
openssl x509 -in "${root}/conf/live/must-staple.le.wtf/cert.pem" -text | grep '1.3.6.1.5.5.7.1.24'
# revoke by account key
common revoke --cert-path "$root/conf/live/le.wtf/cert.pem"
# revoke renewed

View file

@ -3,7 +3,7 @@
# >>>> only tested on Ubuntu 14.04LTS <<<<
# Check out special branch until latest docker changes land in Boulder master.
git clone -b rev-rev https://github.com/letsencrypt/boulder $BOULDERPATH
git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH
cd $BOULDERPATH
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
docker-compose up -d

View file

@ -18,8 +18,7 @@ virtualenv --no-site-packages $VENV_NAME $VENV_ARGS
# Separately install setuptools and pip to make sure following
# invocations use latest
pip install -U setuptools
# --force-reinstall used to fix broken pip installation on some systems
pip install --force-reinstall -U pip
pip install -U pip
pip install "$@"
set +x

View file

@ -45,7 +45,7 @@ export GPG_TTY=$(tty)
PORT=${PORT:-1234}
# subpackages to be released
SUBPKGS=${SUBPKGS:-"acme certbot-apache certbot-nginx letshelp-certbot letsencrypt letsencrypt-apache letsencrypt-nginx letshelp-letsencrypt"}
SUBPKGS=${SUBPKGS:-"acme certbot-apache certbot-nginx"}
subpkgs_modules="$(echo $SUBPKGS | sed s/-/_/g)"
# certbot_compatibility_test is not packaged because:
# - it is not meant to be used by anyone else than Certbot devs
@ -162,15 +162,15 @@ for module in certbot $subpkgs_modules ; do
echo testing $module
nosetests $module
done
deactivate
# pin pip hashes of the things we just built
for pkg in acme certbot certbot-apache letsencrypt letsencrypt-apache ; do
for pkg in acme certbot certbot-apache ; do
echo $pkg==$version \\
pip hash dist."$version/$pkg"/*.{whl,gz} | grep "^--hash" | python2 -c 'from sys import stdin; input = stdin.read(); print " ", input.replace("\n--hash", " \\\n --hash"),'
done > /tmp/hashes.$$
deactivate
if ! wc -l /tmp/hashes.$$ | grep -qE "^\s*15 " ; then
if ! wc -l /tmp/hashes.$$ | grep -qE "^\s*9 " ; then
echo Unexpected pip hash output
exit 1
fi

14
tox.ini
View file

@ -64,14 +64,14 @@ basepython = python2.7
# duplicate code checking; if one of the commands fails, others will
# continue, but tox return code will reflect previous error
commands =
pip install -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
pip install -q -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
./pep8.travis.sh
pylint --rcfile=.pylintrc certbot
pylint --rcfile=acme/.pylintrc acme/acme
pylint --rcfile=.pylintrc certbot-apache/certbot_apache
pylint --rcfile=.pylintrc certbot-nginx/certbot_nginx
pylint --rcfile=.pylintrc certbot-compatibility-test/certbot_compatibility_test
pylint --rcfile=.pylintrc letshelp-certbot/letshelp_certbot
pylint --reports=n --rcfile=.pylintrc certbot
pylint --reports=n --rcfile=acme/.pylintrc acme/acme
pylint --reports=n --rcfile=.pylintrc certbot-apache/certbot_apache
pylint --reports=n --rcfile=.pylintrc certbot-nginx/certbot_nginx
pylint --reports=n --rcfile=.pylintrc certbot-compatibility-test/certbot_compatibility_test
pylint --reports=n --rcfile=.pylintrc letshelp-certbot/letshelp_certbot
[testenv:apacheconftest]
#basepython = python2.7