1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-26 07:41:33 +03:00

Merge remote-tracking branch 'upstream/master' into centos_listen

This commit is contained in:
Joona Hoikkala
2016-06-18 23:50:58 +03:00
46 changed files with 559 additions and 660 deletions

3
.coveragerc Normal file
View File

@@ -0,0 +1,3 @@
[report]
# show lines missing coverage in output
show_missing = True

11
.gitattributes vendored
View File

@@ -1,7 +1,16 @@
* text=auto eol=lf
#Default, normalize CRLF into LF in non-binary files
# Files identified as binary by Git are not changed
* crlf=auto
# special files
*.sh crlf=input
*.py crlf=input
*.bat text eol=crlf
*.der binary
*.gz binary
*.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

@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.8.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

@@ -150,6 +150,12 @@ 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 util.exe_exists(constants.os_constant("restart_cmd")[0]):
raise errors.NoInstallationError

View File

@@ -55,6 +55,16 @@ class MultipleVhostsTest(util.ApacheTest):
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.util.exe_exists")
def test_prepare_version(self, mock_exe_exists, _):

View File

@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.8.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.7.0"
LE_AUTO_VERSION="0.8.1"
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
@@ -458,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
@@ -476,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."
@@ -500,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
}
@@ -713,24 +741,21 @@ zope.interface==4.1.3 \
mock==1.0.1 \
--hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f \
--hash=sha256:8f83080daa249d036cbccfb8ae5cc6ff007b88d6d937521371afabe7b19badbc
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.7.0 \
--hash=sha256:6e61dba343806ad4cb27af84628152abc9e83a0fa24be6065587d2b46f340d7a \
--hash=sha256:9f75a1947978402026b741bdee8a18fc5a1cfd539b78e523b7e5f279bf18eeb9
certbot==0.7.0 \
--hash=sha256:55604e43d231ac226edefed8dc110d792052095c3d75ad0e4a228ae0989fe5fd \
--hash=sha256:ad5083d75e16d1ab806802d3a32f34973b6d7adaf083aee87e07a6c1359efe88
certbot-apache==0.7.0 \
--hash=sha256:5ab5ed9b2af6c7db9495ce1491122798e9d0764e3df8f0843d11d89690bf7f88 \
--hash=sha256:1ddbfaf01bcb0b05c0dcc8b2ebd37637f080cf798151e8140c20c9f5fe7bae75
letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
letsencrypt-apache==0.7.0 \
--hash=sha256:10445980a6afc810325ea22a56e269229999120848f6c0b323b00275696b5c80 \
--hash=sha256:3f4656088a18e4efea7cd7eb4965e14e8d901f3b64f4691e79cafd0bb91890f0
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.8.1 \
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
certbot==0.8.1 \
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
certbot-apache==0.8.1 \
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
UNLIKELY_EOF
# -------------------------------------------------------------------------

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.util.subprocess",
mock_subprocess).start()
mock.patch(
"certbot_apache.configurator.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.8.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

@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.8.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.8.0.dev0'
__version__ = '0.9.0.dev0'

View File

@@ -1,6 +1,7 @@
"""Certbot command line argument & config processing."""
from __future__ import print_function
import argparse
import copy
import glob
import logging
import logging.handlers
@@ -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
@@ -320,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.
@@ -335,6 +366,9 @@ 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:
@@ -359,7 +393,8 @@ class HelpfulArgumentParser(object):
" {0} conflicts with dialog_mode").format(arg)
)
hooks.validate_hooks(parsed_args)
if parsed_args.validate_hooks:
hooks.validate_hooks(parsed_args)
return parsed_args
@@ -800,6 +835,14 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
"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)

View File

@@ -296,6 +296,32 @@ 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.
@@ -306,13 +332,8 @@ def get_names_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
:rtype: list
"""
loaded_csr = _load_cert_or_req(
return _get_names_from_cert_or_req(
csr, OpenSSL.crypto.load_certificate_request, typ)
# Use a set to avoid duplication with CN and Subject Alt Names
domains = set(d for d in (loaded_csr.get_subject().CN,) if d is not None)
# pylint: disable=protected-access
domains.update(acme_crypto_util._pyopenssl_cert_or_req_san(loaded_csr))
return list(domains)
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):

View File

@@ -1,6 +1,8 @@
"""Certbot main entry point."""
from __future__ import print_function
import atexit
import dialog
import errno
import functools
import logging.handlers
import os
@@ -588,8 +590,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).
@@ -665,7 +675,10 @@ def _handle_exception(exc_type, exc_value, trace, config):
# Here we're passing a client or ACME error out to the client at the shell
# Tell the user a bit about what happened, without overwhelming
# them with a full traceback
err = traceback.format_exception_only(exc_type, exc_value)[0]
if issubclass(exc_type, dialog.error):
err = exc_value.complete_message()
else:
err = traceback.format_exception_only(exc_type, exc_value)[0]
# Typical error from the ACME module:
# acme.messages.Error: urn:acme:error:malformed :: The request message was
# malformed :: Error creating new registration :: Validation of contact

View File

@@ -60,7 +60,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

View File

@@ -7,8 +7,10 @@ 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
@@ -158,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
@@ -616,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?

View File

@@ -2,6 +2,7 @@
from __future__ import print_function
import argparse
import dialog
import functools
import itertools
import os
@@ -341,11 +342,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
# FQDN
self.assertRaises(errors.ConfigurationError,
self._call,
['-d', 'comma,gotwrong.tld'])
['-d', 'a' * 64])
# FQDN 2
self.assertRaises(errors.ConfigurationError,
self._call,
['-d', 'illegal.character=.tld'])
['-d', (('a' * 50) + '.') * 10])
# Wildcard
self.assertRaises(errors.ConfigurationError,
self._call,
@@ -447,6 +448,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)
@@ -651,6 +665,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):
@@ -897,6 +923,13 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_sys.exit.assert_called_with(''.join(
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
# Test dialog errors
exception = dialog.error(message="test message")
main._handle_exception(
dialog.DialogError, exc_value=exception, trace=None, config=None)
error_msg = mock_sys.exit.call_args_list[-1][0][0]
self.assertTrue("test message" in error_msg)
def test_read_file(self):
rel_test_path = os.path.relpath(os.path.join(self.tmp_dir, 'foo'))
self.assertRaises(

View File

@@ -273,6 +273,32 @@ 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

View File

@@ -248,9 +248,9 @@ class ChooseNamesTest(unittest.TestCase):
def test_get_valid_domains(self):
from certbot.display.ops import get_valid_domains
all_valid = ["example.com", "second.example.com",
"also.example.com"]
all_invalid = ["xn--ls8h.tld", "*.wildcard.com", "notFQDN",
"uniçodé.com"]
"also.example.com", "under_score.example.com",
"justtld"]
all_invalid = ["xn--ls8h.tld", "*.wildcard.com", "uniçodé.com"]
two_valid = ["example.com", "xn--ls8h.tld", "also.example.com"]
self.assertEqual(get_valid_domains(all_valid), all_valid)
self.assertEqual(get_valid_domains(all_invalid), [])
@@ -276,19 +276,18 @@ class ChooseNamesTest(unittest.TestCase):
mock_util().input.return_value = (display_util.OK,
"xn--ls8h.tld")
self.assertEqual(_choose_names_manually(), [])
# non-FQDN and no retry
mock_util().input.return_value = (display_util.OK,
"notFQDN")
self.assertEqual(_choose_names_manually(), [])
# Two valid domains
# Valid domains
mock_util().input.return_value = (display_util.OK,
("example.com,"
"under_score.example.com,"
"justtld,"
"valid.example.com"))
self.assertEqual(_choose_names_manually(),
["example.com", "valid.example.com"])
["example.com", "under_score.example.com",
"justtld", "valid.example.com"])
# Three iterations
mock_util().input.return_value = (display_util.OK,
"notFQDN")
"uniçodé.com")
yn = mock.MagicMock()
yn.side_effect = [True, True, False]
mock_util().yesno = yn

View File

@@ -11,6 +11,7 @@ import mock
import pytz
import certbot
from certbot import cli
from certbot import configuration
from certbot import errors
from certbot.storage import ALL_FOUR
@@ -84,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):
@@ -204,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",
@@ -225,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)
@@ -241,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)
@@ -312,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.
@@ -325,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):
@@ -376,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)
@@ -395,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)
@@ -480,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):
@@ -507,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())
@@ -525,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,
@@ -586,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):

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

@@ -423,14 +423,17 @@ def enforce_domain_sanity(domain):
# It wasn't an IP address, so that's good
pass
# FQDN checks from
# http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
# Characters used, domain parts < 63 chars, tld > 1 < 64 chars
# 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))
# FQDN checks according to RFC 2181: domain name should be less than 255
# octets (inclusive). And each label is 1 - 63 octets (inclusive).
# https://tools.ietf.org/html/rfc2181#section-11
msg = "Requested domain {0} is not a FQDN because ".format(domain)
labels = domain.split('.')
for l in labels:
if not 0 < len(l) < 64:
raise errors.ConfigurationError(msg + "label {0} is too long.".format(l))
if len(domain) > 255:
raise errors.ConfigurationError(msg + "it is too long.")
return domain

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
@@ -53,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)
@@ -194,6 +200,15 @@ renew:
and keys; the shell variable $RENEWED_DOMAINS will
contain a space-delimited list of renewed cert domains
(default: None)
--disable-hook-validation
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. (default: True)
certonly:
Options for modifying how a cert is obtained

View File

@@ -71,6 +71,9 @@ The following tools are there to help you:
experimental, non-production Apache2 install on them. ``tox -e
apacheconftest`` can be used to run those specific Apache conf tests.
- ``tox --skip-missing-interpreters`` runs tox while ignoring missing versions
of Python needed for running the tests.
- ``tox -e py27``, ``tox -e py26`` etc, run unit tests for specific Python
versions.
@@ -313,7 +316,9 @@ Steps:
3. Run ``./pep8.travis.sh`` to do a cursory check of your code style.
Fix any errors.
4. Run ``tox -e lint`` to check for pylint errors. Fix any errors.
5. Run ``tox`` to run the entire test suite including coverage. Fix any errors.
5. Run ``tox --skip-missing-interpreters`` to run the entire test suite
including coverage. The ``--skip-missing-interpreters`` argument ignores
missing versions of Python needed for running the tests. Fix any errors.
6. If your code touches communication with an ACME server/Boulder, you
should run the integration tests, see `integration`_. See `Known Issues`_
for some common failures that have nothing to do with your code.

View File

@@ -552,10 +552,3 @@ Beyond the methods discussed here, other methods may be possible, such as
installing Certbot directly with pip from PyPI or downloading a ZIP
archive from GitHub may be technically possible but are not presently
recommended or supported.
.. rubric:: Footnotes
.. [#venv] By using this virtualized Python environment (`virtualenv
<https://virtualenv.pypa.io>`_) we don't pollute the main
OS space with packages from PyPI!

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"
LE_AUTO_VERSION="0.8.1"
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
@@ -458,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
@@ -476,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."
@@ -500,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
}
@@ -713,24 +741,21 @@ zope.interface==4.1.3 \
mock==1.0.1 \
--hash=sha256:b839dd2d9c117c701430c149956918a423a9863b48b09c90e30a6013e7d2f44f \
--hash=sha256:8f83080daa249d036cbccfb8ae5cc6ff007b88d6d937521371afabe7b19badbc
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.7.0 \
--hash=sha256:6e61dba343806ad4cb27af84628152abc9e83a0fa24be6065587d2b46f340d7a \
--hash=sha256:9f75a1947978402026b741bdee8a18fc5a1cfd539b78e523b7e5f279bf18eeb9
certbot==0.7.0 \
--hash=sha256:55604e43d231ac226edefed8dc110d792052095c3d75ad0e4a228ae0989fe5fd \
--hash=sha256:ad5083d75e16d1ab806802d3a32f34973b6d7adaf083aee87e07a6c1359efe88
certbot-apache==0.7.0 \
--hash=sha256:5ab5ed9b2af6c7db9495ce1491122798e9d0764e3df8f0843d11d89690bf7f88 \
--hash=sha256:1ddbfaf01bcb0b05c0dcc8b2ebd37637f080cf798151e8140c20c9f5fe7bae75
letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
letsencrypt-apache==0.7.0 \
--hash=sha256:10445980a6afc810325ea22a56e269229999120848f6c0b323b00275696b5c80 \
--hash=sha256:3f4656088a18e4efea7cd7eb4965e14e8d901f3b64f4691e79cafd0bb91890f0
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.8.1 \
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
certbot==0.8.1 \
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
certbot-apache==0.8.1 \
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
UNLIKELY_EOF
# -------------------------------------------------------------------------

View File

@@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQEcBAABAgAGBQJXSK5DAAoJEE0XyZXNl3Xyyb4H/Ahy9/8ADDaN5V/O/6kl6gE5
amQfm8T10EUD8APnNWYrYKBYruDBVvH0KiEcuAEs7q4xE5BaQatlobSnsHfv4AWW
TwInk2lRxYZ++MwwQf3DrqMK5QKfcoVnViZsRpZ8gHMLzsJllRm7R5eaTewO2ViM
KM+yDB3UsquLUvE4d3/hgBl2mXAUwsxLeFreZayvpoTcX2ARnzbtKqMaIBYDYWcx
DewWtDsPrhKFpb2DY06S6JLmEttysUgv+hbKlaVO0yZ8cCUehkzBIGYoeS4chOLq
fonNCzB8u3RtnLEFiPIy0N+A592jbLsqqUkxjammaJq3lH7nitduMLnpvGKt4yc=
=ex1J
iQEcBAABAgAGBQJXYJmBAAoJEE0XyZXNl3XyyIMH/jtYFb7rl5XXN8hjlKuK5frq
z7/jdK7fvI+mtYJ4i2Cy3yMz8T4wscXGkhxNtipbATWlpevPfjYzm4ZGC25coFZx
fDX44w0hBBgel7EISXGR1ABXb2rj24TZxIYXwaeClylsK9n5CxcWBocn8tDlfr8t
7VQUJEL3l1IlrnKnvpoL4Eq11sxlIPtitDPJ5c98ZM1293ZbWzIqyZKoXLIUkKHg
pkaa80j/QMmFumxzXFenU91JusLdeoblvjjg+kzjGonjslAYIuH4wEEjz2VJuUYe
P2+2ZyW4eLA6rRZhZ3CMtV79HzTPTWiELCYbXezb+yXJJEqzCYtIXkmbNQ3jUEY=
=86lB
-----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.8.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
@@ -458,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
@@ -476,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."
@@ -500,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
}
@@ -719,15 +747,15 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.7.0 \
--hash=sha256:6e61dba343806ad4cb27af84628152abc9e83a0fa24be6065587d2b46f340d7a \
--hash=sha256:9f75a1947978402026b741bdee8a18fc5a1cfd539b78e523b7e5f279bf18eeb9
certbot==0.7.0 \
--hash=sha256:55604e43d231ac226edefed8dc110d792052095c3d75ad0e4a228ae0989fe5fd \
--hash=sha256:ad5083d75e16d1ab806802d3a32f34973b6d7adaf083aee87e07a6c1359efe88
certbot-apache==0.7.0 \
--hash=sha256:5ab5ed9b2af6c7db9495ce1491122798e9d0764e3df8f0843d11d89690bf7f88 \
--hash=sha256:1ddbfaf01bcb0b05c0dcc8b2ebd37637f080cf798151e8140c20c9f5fe7bae75
acme==0.8.1 \
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
certbot==0.8.1 \
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
certbot-apache==0.8.1 \
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed
UNLIKELY_EOF
# -------------------------------------------------------------------------

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
}

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

@@ -181,12 +181,12 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.7.0 \
--hash=sha256:6e61dba343806ad4cb27af84628152abc9e83a0fa24be6065587d2b46f340d7a \
--hash=sha256:9f75a1947978402026b741bdee8a18fc5a1cfd539b78e523b7e5f279bf18eeb9
certbot==0.7.0 \
--hash=sha256:55604e43d231ac226edefed8dc110d792052095c3d75ad0e4a228ae0989fe5fd \
--hash=sha256:ad5083d75e16d1ab806802d3a32f34973b6d7adaf083aee87e07a6c1359efe88
certbot-apache==0.7.0 \
--hash=sha256:5ab5ed9b2af6c7db9495ce1491122798e9d0764e3df8f0843d11d89690bf7f88 \
--hash=sha256:1ddbfaf01bcb0b05c0dcc8b2ebd37637f080cf798151e8140c20c9f5fe7bae75
acme==0.8.1 \
--hash=sha256:ccd7883772efbf933f91713b8241455993834f3620c8fbd459d9ed5e50bbaaca \
--hash=sha256:d3ea4acf280bf6253ad7d641cb0970f230a19805acfed809e7a8ddcf62157d9f
certbot==0.8.1 \
--hash=sha256:89805d9f70249ae859ec4d7a99c00b4bb7083ca90cd12d4d202b76dfc284f7c5 \
--hash=sha256:6ca8df3d310ced6687d38aac17c0fb8c1b2ec7a3bea156a254e4cc2a1c132771
certbot-apache==0.8.1 \
--hash=sha256:c9e3fdc15e65589c2e39eb0e6b1f61f0c0a1db3c17b00bb337f0ff636cc61cb3 \
--hash=sha256:0faf2879884d3b7a58b071902fba37d4b8b58a50e2c3b8ac262c0a74134045ed

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

@@ -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

@@ -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

16
tox.ini
View File

@@ -4,7 +4,7 @@
[tox]
skipsdist = true
envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint
envlist = py{26,33,34,35},cover,lint
# nosetest -v => more verbose output, allows to detect busy waiting
# loops, especially on Travis
@@ -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