Merge remote-tracking branch 'origin/master' into plugin-docs

This commit is contained in:
Peter Eckersley 2016-05-12 19:18:51 -07:00
commit ff2d8da20d
17 changed files with 375 additions and 194 deletions

View file

@ -484,9 +484,11 @@ class ClientNetworkTest(unittest.TestCase):
def test_check_response_not_ok_jobj_no_error(self):
self.response.ok = False
self.response.json.return_value = {}
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
with mock.patch('acme.client.messages.Error.from_json') as from_json:
from_json.side_effect = jose.DeserializationError
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
def test_check_response_not_ok_jobj_error(self):
self.response.ok = False

View file

@ -37,9 +37,9 @@ class Error(jose.JSONObjectWithFields, errors.Error):
)
)
typ = jose.Field('type')
typ = jose.Field('type', omitempty=True, default='about:blank')
title = jose.Field('title', omitempty=True)
detail = jose.Field('detail')
detail = jose.Field('detail', omitempty=True)
@property
def description(self):
@ -143,12 +143,6 @@ class Directory(jose.JSONDeSerializable):
def __init__(self, jobj):
canon_jobj = util.map_keys(jobj, self._canon_key)
if not set(canon_jobj).issubset(
set(self._REGISTERED_TYPES).union(['meta'])):
# TODO: acme-spec is not clear about this: 'It is a JSON
# dictionary, whose keys are the "resource" values listed
# in {{https-requests}}'
raise ValueError('Wrong directory fields')
# TODO: check that everything is an absolute URL; acme-spec is
# not clear on that
self._jobj = canon_jobj
@ -171,10 +165,7 @@ class Directory(jose.JSONDeSerializable):
@classmethod
def from_json(cls, jobj):
jobj['meta'] = cls.Meta.from_json(jobj.pop('meta', {}))
try:
return cls(jobj)
except ValueError as error:
raise jose.DeserializationError(str(error))
return cls(jobj)
class Resource(jose.JSONObjectWithFields):

View file

@ -28,6 +28,14 @@ class ErrorTest(unittest.TestCase):
self.error_custom = Error(typ='custom', detail='bar')
self.jobj_cusom = {'type': 'custom', 'detail': 'bar'}
def test_default_typ(self):
from acme.messages import Error
self.assertEqual(Error().typ, 'about:blank')
def test_from_json_empty(self):
from acme.messages import Error
self.assertEqual(Error(), Error.from_json('{}'))
def test_from_json_hashable(self):
from acme.messages import Error
hash(Error.from_json(self.error.to_json()))
@ -97,9 +105,9 @@ class DirectoryTest(unittest.TestCase):
),
})
def test_init_wrong_key_value_error(self):
def test_init_wrong_key_value_success(self): # pylint: disable=no-self-use
from acme.messages import Directory
self.assertRaises(ValueError, Directory, {'foo': 'bar'})
Directory({'foo': 'bar'})
def test_getitem(self):
self.assertEqual('reg', self.dir['new-reg'])
@ -127,10 +135,9 @@ class DirectoryTest(unittest.TestCase):
},
})
def test_from_json_deserialization_error_on_wrong_key(self):
def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use
from acme.messages import Directory
self.assertRaises(
jose.DeserializationError, Directory.from_json, {'foo': 'bar'})
Directory.from_json({'foo': 'bar'})
class RegistrationTest(unittest.TestCase):

View file

@ -17,7 +17,7 @@ class RawNginxParser(object):
right_bracket = Literal("}").suppress()
semicolon = Literal(";").suppress()
space = White().suppress()
key = Word(alphanums + "_/")
key = Word(alphanums + "_/+-.")
# Matches anything that is not a special character AND any chars in single
# or double quotes
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")

View file

@ -37,8 +37,14 @@ helpful_parser = None
# should only be used for purposes where inability to detect letsencrypt-auto
# fails safely
LEAUTO = "letsencrypt-auto"
if "CERTBOT_AUTO" in os.environ:
# if we're here, this is probably going to be certbot-auto, unless the
# user saved the script under a different name
LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"])
fragment = os.path.join(".local", "share", "letsencrypt")
cli_command = "letsencrypt-auto" if fragment in sys.argv[0] else "certbot"
cli_command = LEAUTO if fragment in sys.argv[0] else "certbot"
# Argparse's help formatting has a lot of unhelpful peculiarities, so we want
# to replace as much of it as we can...
@ -141,6 +147,22 @@ def usage_strings(plugins):
return USAGE % (apache_doc, nginx_doc), SHORT_USAGE
def possible_deprecation_warning(config):
"A deprecation warning for users with the old, not-self-upgrading letsencrypt-auto."
if cli_command != LEAUTO:
return
if config.no_self_upgrade:
# users setting --no-self-upgrade might be hanging on a clent version like 0.3.0
# or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't
# need warnings
return
if "CERTBOT_AUTO" not in os.environ:
logger.warn("You are running with an old copy of letsencrypt-auto that does "
"not receive updates, and is less reliable than more recent versions. "
"We recommend upgrading to the latest certbot-auto script, or using native "
"OS packages.")
class _Default(object):
"""A class to use as a default to detect if a value is set by a user"""

View file

@ -661,6 +661,7 @@ def main(cli_args=sys.argv[1:]):
le_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)
logger.debug("certbot version: %s", certbot.__version__)
# do not log `config`, as it contains sensitive data (e.g. revoke --key)!

View file

@ -241,6 +241,9 @@ to serve all files under specified web root ({0})."""
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

View file

@ -218,13 +218,26 @@ class AuthenticatorTest(unittest.TestCase):
self.auth.perform([self.achall])
os_error = OSError()
os_error.errno = errno.ENOENT
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

@ -1,6 +1,6 @@
#!/bin/sh
#
# Download and run the latest release version of the Let's Encrypt client.
# Download and run the latest release version of the Certbot client.
#
# NOTE: THIS SCRIPT IS AUTO-GENERATED AND SELF-UPDATING
#
@ -20,10 +20,35 @@ VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.6.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
to both this script and certbot will be downloaded and installed. After
ensuring you have the latest versions installed, certbot will be invoked with
all arguments you have provided.
Help for certbot itself cannot be provided until it is installed.
--debug attempt experimental installation
-h, --help print this help
-n, --non-interactive, --noninteractive run without asking for user input
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
-v, --verbose provide more output
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
# This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
for arg in "$@" ; do
case "$arg" in
--debug)
@ -34,25 +59,26 @@ for arg in "$@" ; do
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1;;
--help)
HELP=1;;
--noninteractive|--non-interactive)
ASSUME_YES=1;;
--verbose)
VERBOSE=1;;
[!-]*|-*[!v]*|-)
# Anything that isn't -v, -vv, etc.: that is, anything that does not
# start with a -, contains anything that's not a v, or is just "-"
;;
*) # -v+ remains.
VERBOSE=1;;
esac
done
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
# letsencrypt itself needs root access for almost all modes of operation
# 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
# this script *can* be run as root (not recommended), or fall back to using
# `su`
SUDO_ENV=""
export CERTBOT_AUTO="$0"
if test "`id -u`" -ne "0" ; then
if command -v sudo 1>/dev/null 2>&1; then
SUDO=sudo
SUDO_ENV="CERTBOT_AUTO=$0"
else
echo \"sudo\" is not available, will use \"su\" for installation steps...
# Because the parameters in `su -c` has to be a string,
@ -81,6 +107,12 @@ 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
@ -151,30 +183,45 @@ BootstrapDebCommon() {
augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
if [ "$ASSUME_YES" = 1 ]; then
YES_FLAG="-y"
fi
AddBackportRepo() {
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
if echo $BACKPORT_NAME | grep -q wheezy ; then
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
$SUDO apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
fi
}
@ -186,12 +233,12 @@ BootstrapDebCommon() {
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Let's Encrypt apache plugin..."
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
fi
$SUDO apt-get install -y --no-install-recommends \
$SUDO apt-get install $YES_FLAG --no-install-recommends \
python \
python-dev \
$virtualenv \
@ -212,9 +259,10 @@ BootstrapDebCommon() {
BootstrapRpmCommon() {
# Tested with:
# - Fedora 22, 23 (x64)
# - Fedora 20, 21, 22, 23 (x64)
# - Centos 7 (x64: on DigitalOcean droplet)
# - CentOS 7 Minimal install in a Hyper-V VM
# - CentOS 6 (EPEL must be installed manually)
if type dnf 2>/dev/null
then
@ -228,54 +276,62 @@ BootstrapRpmCommon() {
exit 1
fi
pkgs="
gcc
dialog
augeas-libs
openssl
openssl-devel
libffi-devel
redhat-rpm-config
ca-certificates
"
# Some distros and older versions of current distros use a "python27"
# instead of "python" naming convention. Try both conventions.
if ! $SUDO $tool install -y \
python \
python-devel \
python-virtualenv \
python-tools \
python-pip
then
if ! $SUDO $tool install -y \
python27 \
python27-devel \
python27-virtualenv \
python27-tools \
python27-pip
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
if $SUDO $tool list python >/dev/null 2>&1; then
pkgs="$pkgs
python
python-devel
python-virtualenv
python-tools
python-pip
"
else
pkgs="$pkgs
python27
python27-devel
python27-virtualenv
python27-tools
python27-pip
"
fi
if ! $SUDO $tool install -y \
gcc \
dialog \
augeas-libs \
openssl \
openssl-devel \
libffi-devel \
redhat-rpm-config \
ca-certificates
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
if $SUDO $tool list installed "httpd" >/dev/null 2>&1; then
if ! $SUDO $tool install -y mod_ssl
then
echo "Apache found, but mod_ssl could not be installed."
fi
pkgs="$pkgs
mod_ssl
"
fi
if [ "$ASSUME_YES" = 1 ]; then
yes_flag="-y"
fi
if ! $SUDO $tool install $yes_flag $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
fi
}
BootstrapSuseCommon() {
# SLE12 don't have python-virtualenv
$SUDO zypper -nq in -l \
if [ "$ASSUME_YES" = 1 ]; then
zypper_flags="-nq"
install_flags="-l"
fi
$SUDO zypper $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \
@ -310,8 +366,12 @@ BootstrapArchCommon() {
# pacman -T exits with 127 if there are missing dependencies
missing=$($SUDO pacman -T $deps) || true
if [ "$ASSUME_YES" = 1 ]; then
noconfirm="--noconfirm"
fi
if [ "$missing" ]; then
$SUDO pacman -S --needed $missing
$SUDO pacman -S --needed $missing $noconfirm
fi
}
@ -426,7 +486,7 @@ Bootstrap() {
elif grep -iq "Amazon Linux" /etc/issue ; then
ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
else
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
echo "Sorry, I don't know how to bootstrap Certbot on your operating system!"
echo
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"
@ -446,7 +506,8 @@ if [ "$1" = "--le-auto-phase2" ]; then
shift 1 # the --le-auto-phase2 arg
if [ -f "$VENV_BIN/letsencrypt" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep ^letsencrypt | cut -d " " -f 2)
# grep for both certbot and letsencrypt until certbot and shim packages have been released
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
else
INSTALLED_VERSION="none"
fi
@ -465,8 +526,8 @@ if [ "$1" = "--le-auto-phase2" ]; then
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
# This is the flattened list of packages letsencrypt-auto installs. To generate
# this, do `pip install --no-cache-dir -e acme -e . -e letsencrypt-apache`, and
# This is the flattened list of packages certbot-auto installs. To generate
# this, do `pip install --no-cache-dir -e acme -e . -e certbot-apache`, and
# then use `hashin` or a more secure method to gather the hashes.
argparse==1.4.0 \
@ -823,18 +884,30 @@ UNLIKELY_EOF
fi
echo "Installation succeeded."
fi
echo "Requesting root privileges to run letsencrypt..."
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
$SUDO "$VENV_BIN/letsencrypt" "$@"
echo "Requesting root privileges to run certbot..."
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
else
# Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of
# letsencrypt-auto (which is always the same as that of the letsencrypt
# package). Phase 2 checks the version of the locally installed letsencrypt.
# certbot-auto (which is always the same as that of the certbot
# package). Phase 2 checks the version of the locally installed certbot.
if [ ! -f "$VENV_BIN/letsencrypt" ]; then
if [ "$HELP" = 1 ]; then
echo "$USAGE"
exit 0
fi
# If it looks like we've never bootstrapped before, bootstrap:
Bootstrap
fi
@ -953,7 +1026,7 @@ def verified_new_le_auto(get, tag, temp_dir):
stderr=dev_null)
except CalledProcessError as exc:
raise ExpectedError("Couldn't verify signature of downloaded "
"letsencrypt-auto.", exc)
"certbot-auto.", exc)
def main():
@ -978,29 +1051,27 @@ if __name__ == '__main__':
UNLIKELY_EOF
# ---------------------------------------------------------------------------
DeterminePythonVersion
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
if ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
echo "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more
# dependencies (curl, etc.), for better flow control, and for the option of
# future Windows compatibility.
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# Install new copy of letsencrypt-auto.
# Install new copy of certbot-auto.
# TODO: Deal with quotes in pathnames.
echo "Replacing letsencrypt-auto..."
echo "Replacing certbot-auto..."
# Clone permissions with cp. chmod and chown don't have a --reference
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
# Using mv rather than cp leaves the old file descriptor pointing to the
# original copy so the shell can continue to read it unmolested. mv across
# 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.
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
$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"

View file

@ -20,10 +20,35 @@ VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
to both this script and certbot will be downloaded and installed. After
ensuring you have the latest versions installed, certbot will be invoked with
all arguments you have provided.
Help for certbot itself cannot be provided until it is installed.
--debug attempt experimental installation
-h, --help print this help
-n, --non-interactive, --noninteractive run without asking for user input
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
-v, --verbose provide more output
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
# This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
for arg in "$@" ; do
case "$arg" in
--debug)
@ -34,25 +59,26 @@ for arg in "$@" ; do
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1;;
--help)
HELP=1;;
--noninteractive|--non-interactive)
ASSUME_YES=1;;
--verbose)
VERBOSE=1;;
[!-]*|-*[!v]*|-)
# Anything that isn't -v, -vv, etc.: that is, anything that does not
# start with a -, contains anything that's not a v, or is just "-"
;;
*) # -v+ remains.
VERBOSE=1;;
esac
done
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
# 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
# this script *can* be run as root (not recommended), or fall back to using
# `su`
SUDO_ENV=""
export CERTBOT_AUTO="$0"
if test "`id -u`" -ne "0" ; then
if command -v sudo 1>/dev/null 2>&1; then
SUDO=sudo
SUDO_ENV="CERTBOT_AUTO=$0"
else
echo \"sudo\" is not available, will use \"su\" for installation steps...
# Because the parameters in `su -c` has to be a string,
@ -81,6 +107,12 @@ 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
@ -177,7 +209,8 @@ if [ "$1" = "--le-auto-phase2" ]; then
shift 1 # the --le-auto-phase2 arg
if [ -f "$VENV_BIN/letsencrypt" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep ^letsencrypt | cut -d " " -f 2)
# grep for both certbot and letsencrypt until certbot and shim packages have been released
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
else
INSTALLED_VERSION="none"
fi
@ -220,17 +253,29 @@ UNLIKELY_EOF
echo "Installation succeeded."
fi
echo "Requesting root privileges to run certbot..."
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
$SUDO "$VENV_BIN/letsencrypt" "$@"
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
else
# Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of
# letsencrypt-auto (which is always the same as that of the certbot
# certbot-auto (which is always the same as that of the certbot
# package). Phase 2 checks the version of the locally installed certbot.
if [ ! -f "$VENV_BIN/letsencrypt" ]; then
if [ "$HELP" = 1 ]; then
echo "$USAGE"
exit 0
fi
# If it looks like we've never bootstrapped before, bootstrap:
Bootstrap
fi
@ -248,29 +293,27 @@ else
UNLIKELY_EOF
# ---------------------------------------------------------------------------
DeterminePythonVersion
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
if ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
echo "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more
# dependencies (curl, etc.), for better flow control, and for the option of
# future Windows compatibility.
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# Install new copy of letsencrypt-auto.
# Install new copy of certbot-auto.
# TODO: Deal with quotes in pathnames.
echo "Replacing letsencrypt-auto..."
echo "Replacing certbot-auto..."
# Clone permissions with cp. chmod and chown don't have a --reference
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
# Using mv rather than cp leaves the old file descriptor pointing to the
# original copy so the shell can continue to read it unmolested. mv across
# 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.
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
$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"

View file

@ -21,7 +21,11 @@ BootstrapArchCommon() {
# pacman -T exits with 127 if there are missing dependencies
missing=$($SUDO pacman -T $deps) || true
if [ "$ASSUME_YES" = 1 ]; then
noconfirm="--noconfirm"
fi
if [ "$missing" ]; then
$SUDO pacman -S --needed $missing
$SUDO pacman -S --needed $missing $noconfirm
fi
}

View file

@ -34,30 +34,45 @@ BootstrapDebCommon() {
augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
if [ "$ASSUME_YES" = 1 ]; then
YES_FLAG="-y"
fi
AddBackportRepo() {
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
if echo $BACKPORT_NAME | grep -q wheezy ; then
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
$SUDO apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
fi
}
@ -74,7 +89,7 @@ BootstrapDebCommon() {
# XXX add a case for ubuntu PPAs
fi
$SUDO apt-get install -y --no-install-recommends \
$SUDO apt-get install $YES_FLAG --no-install-recommends \
python \
python-dev \
$virtualenv \

View file

@ -1,8 +1,9 @@
BootstrapRpmCommon() {
# Tested with:
# - Fedora 22, 23 (x64)
# - Fedora 20, 21, 22, 23 (x64)
# - Centos 7 (x64: on DigitalOcean droplet)
# - CentOS 7 Minimal install in a Hyper-V VM
# - CentOS 6 (EPEL must be installed manually)
if type dnf 2>/dev/null
then
@ -16,46 +17,49 @@ BootstrapRpmCommon() {
exit 1
fi
pkgs="
gcc
dialog
augeas-libs
openssl
openssl-devel
libffi-devel
redhat-rpm-config
ca-certificates
"
# Some distros and older versions of current distros use a "python27"
# instead of "python" naming convention. Try both conventions.
if ! $SUDO $tool install -y \
python \
python-devel \
python-virtualenv \
python-tools \
python-pip
then
if ! $SUDO $tool install -y \
python27 \
python27-devel \
python27-virtualenv \
python27-tools \
python27-pip
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
if $SUDO $tool list python >/dev/null 2>&1; then
pkgs="$pkgs
python
python-devel
python-virtualenv
python-tools
python-pip
"
else
pkgs="$pkgs
python27
python27-devel
python27-virtualenv
python27-tools
python27-pip
"
fi
if ! $SUDO $tool install -y \
gcc \
dialog \
augeas-libs \
openssl \
openssl-devel \
libffi-devel \
redhat-rpm-config \
ca-certificates
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
if $SUDO $tool list installed "httpd" >/dev/null 2>&1; then
if ! $SUDO $tool install -y mod_ssl
then
echo "Apache found, but mod_ssl could not be installed."
fi
pkgs="$pkgs
mod_ssl
"
fi
if [ "$ASSUME_YES" = 1 ]; then
yes_flag="-y"
fi
if ! $SUDO $tool install $yes_flag $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
fi
}

View file

@ -1,7 +1,12 @@
BootstrapSuseCommon() {
# SLE12 don't have python-virtualenv
$SUDO zypper -nq in -l \
if [ "$ASSUME_YES" = 1 ]; then
zypper_flags="-nq"
install_flags="-l"
fi
$SUDO zypper $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \

View file

@ -103,7 +103,7 @@ def verified_new_le_auto(get, tag, temp_dir):
stderr=dev_null)
except CalledProcessError as exc:
raise ExpectedError("Couldn't verify signature of downloaded "
"letsencrypt-auto.", exc)
"certbot-auto.", exc)
def main():

View file

@ -1,4 +1,4 @@
# This is the flattened list of packages letsencrypt-auto installs. To generate
# This is the flattened list of packages certbot-auto installs. To generate
# this, do `pip install --no-cache-dir -e acme -e . -e certbot-apache`, and
# then use `hashin` or a more secure method to gather the hashes.

View file

@ -231,7 +231,7 @@ class AutoTests(TestCase):
* The OpenSSL sig mismatches.
For tests which get to the end, we run merely ``letsencrypt --version``.
The functioning of the rest of the letsencrypt script is covered by other
The functioning of the rest of the certbot script is covered by other
test suites.
"""
@ -277,7 +277,7 @@ class AutoTests(TestCase):
ok_(re.match(r'letsencrypt \d+\.\d+\.\d+',
err.strip().splitlines()[-1]))
# Make a few assertions to test the validity of the next tests:
self.assertIn('Upgrading letsencrypt-auto ', out)
self.assertIn('Upgrading certbot-auto ', out)
self.assertIn('Creating virtual environment...', out)
# Now we have le-auto 99.9.9 and LE 99.9.9 installed. This
@ -286,14 +286,14 @@ class AutoTests(TestCase):
# Test when neither phase-1 upgrade nor phase-2 upgrade is
# needed (probably a common case):
out, err = run_letsencrypt_auto()
self.assertNotIn('Upgrading letsencrypt-auto ', out)
self.assertNotIn('Upgrading certbot-auto ', out)
self.assertNotIn('Creating virtual environment...', out)
# Test when a phase-1 upgrade is not needed but a phase-2
# upgrade is:
set_le_script_version(venv_dir, '0.0.1')
out, err = run_letsencrypt_auto()
self.assertNotIn('Upgrading letsencrypt-auto ', out)
self.assertNotIn('Upgrading certbot-auto ', out)
self.assertIn('Creating virtual environment...', out)
def test_openssl_failure(self):
@ -312,10 +312,10 @@ class AutoTests(TestCase):
except CalledProcessError as exc:
eq_(exc.returncode, 1)
self.assertIn("Couldn't verify signature of downloaded "
"letsencrypt-auto.",
"certbot-auto.",
exc.output)
else:
self.fail('Signature check on letsencrypt-auto erroneously passed.')
self.fail('Signature check on certbot-auto erroneously passed.')
def test_pip_failure(self):
"""Make sure pip stops us if there is a hash mismatch."""