mirror of
https://github.com/certbot/certbot.git
synced 2026-08-03 20:02:16 +02:00
Merge branch 'master' into donate-spamfilter
This commit is contained in:
+1
-1
@@ -72,7 +72,7 @@ addons:
|
||||
apt:
|
||||
sources:
|
||||
- augeas
|
||||
packages: # keep in sync with bootstrap/ubuntu.sh and Boulder
|
||||
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
|
||||
- python-dev
|
||||
- python-virtualenv
|
||||
- gcc
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@ WORKDIR /opt/letsencrypt
|
||||
# directories in its path.
|
||||
|
||||
|
||||
COPY bootstrap/ubuntu.sh /opt/letsencrypt/src/ubuntu.sh
|
||||
RUN /opt/letsencrypt/src/ubuntu.sh && \
|
||||
COPY letsencrypt-auto-source/letsencrypt-auto /opt/letsencrypt/src/letsencrypt-auto-source/letsencrypt-auto
|
||||
RUN /opt/letsencrypt/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
/tmp/* \
|
||||
|
||||
+2
-2
@@ -22,8 +22,8 @@ WORKDIR /opt/letsencrypt
|
||||
|
||||
# TODO: Install non-default Python versions for tox.
|
||||
# TODO: Install Apache/Nginx for plugin development.
|
||||
COPY letsencrypt-auto-source/letsencrypt-auto /opt/letsencrypt/src/letsencrypt-auto
|
||||
RUN /opt/letsencrypt/src/letsencrypt-auto --os-packages-only && \
|
||||
COPY letsencrypt-auto-source/letsencrypt-auto /opt/letsencrypt/src/letsencrypt-auto-source/letsencrypt-auto
|
||||
RUN /opt/letsencrypt/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/* \
|
||||
/tmp/* \
|
||||
|
||||
Vendored
+1
-1
@@ -8,7 +8,7 @@ VAGRANTFILE_API_VERSION = "2"
|
||||
$ubuntu_setup_script = <<SETUP_SCRIPT
|
||||
cd /vagrant
|
||||
./letsencrypt-auto-source/letsencrypt-auto --os-packages-only
|
||||
./bootstrap/dev/venv.sh
|
||||
./tools/venv.sh
|
||||
SETUP_SCRIPT
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
This directory contains scripts that install necessary OS-specific
|
||||
prerequisite dependencies (see docs/using.rst).
|
||||
|
||||
General dependencies:
|
||||
- ca-certificates: communication with demo ACMO server at
|
||||
https://www.letsencrypt-demo.org
|
||||
@@ -1,26 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tested with:
|
||||
# - ArchLinux (x86_64)
|
||||
#
|
||||
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
|
||||
# only "virtualenv2" binary, not "virtualenv" necessary in
|
||||
# ./bootstrap/dev/_common_venv.sh
|
||||
|
||||
deps="
|
||||
python2
|
||||
python-virtualenv
|
||||
gcc
|
||||
dialog
|
||||
augeas
|
||||
openssl
|
||||
libffi
|
||||
ca-certificates
|
||||
pkg-config
|
||||
"
|
||||
|
||||
missing=$(pacman -T $deps)
|
||||
|
||||
if [ "$missing" ]; then
|
||||
pacman -S --needed $missing
|
||||
fi
|
||||
@@ -1,94 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Current version tested with:
|
||||
#
|
||||
# - Ubuntu
|
||||
# - 14.04 (x64)
|
||||
# - 15.04 (x64)
|
||||
# - Debian
|
||||
# - 7.9 "wheezy" (x64)
|
||||
# - sid (2015-10-21) (x64)
|
||||
|
||||
# Past versions tested with:
|
||||
#
|
||||
# - Debian 8.0 "jessie" (x64)
|
||||
# - Raspbian 7.8 (armhf)
|
||||
|
||||
# Believed not to work:
|
||||
#
|
||||
# - Debian 6.0.10 "squeeze" (x64)
|
||||
|
||||
apt-get update
|
||||
|
||||
# virtualenv binary can be found in different packages depending on
|
||||
# distro version (#346)
|
||||
|
||||
virtualenv=
|
||||
if apt-cache show virtualenv > /dev/null 2>&1; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
|
||||
if apt-cache show python-virtualenv > /dev/null 2>&1; then
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
fi
|
||||
|
||||
augeas_pkg="libaugeas0 augeas-lenses"
|
||||
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
|
||||
|
||||
AddBackportRepo() {
|
||||
# ARGS:
|
||||
BACKPORT_NAME="$1"
|
||||
BACKPORT_SOURCELINE="$2"
|
||||
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")'
|
||||
fi
|
||||
|
||||
echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/"$BACKPORT_NAME".list
|
||||
apt-get update
|
||||
fi
|
||||
fi
|
||||
apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
|
||||
augeas_pkg=
|
||||
|
||||
}
|
||||
|
||||
|
||||
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
|
||||
if lsb_release -a | grep -q wheezy ; then
|
||||
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
|
||||
elif lsb_release -a | grep -q precise ; then
|
||||
# XXX add ARM case
|
||||
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..."
|
||||
fi
|
||||
# XXX add a case for ubuntu PPAs
|
||||
fi
|
||||
|
||||
apt-get install -y --no-install-recommends \
|
||||
python \
|
||||
python-dev \
|
||||
$virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
$augeas_pkg \
|
||||
libssl-dev \
|
||||
libffi-dev \
|
||||
ca-certificates \
|
||||
|
||||
|
||||
|
||||
if ! command -v virtualenv > /dev/null ; then
|
||||
echo Failed to install a working \"virtualenv\" command, exiting
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,23 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
PACKAGES="
|
||||
dev-lang/python:2.7
|
||||
dev-python/virtualenv
|
||||
dev-util/dialog
|
||||
app-admin/augeas
|
||||
dev-libs/openssl
|
||||
dev-libs/libffi
|
||||
app-misc/ca-certificates
|
||||
virtual/pkgconfig"
|
||||
|
||||
case "$PACKAGE_MANAGER" in
|
||||
(paludis)
|
||||
cave resolve --keep-targets if-possible $PACKAGES -x
|
||||
;;
|
||||
(pkgcore)
|
||||
pmerge --noreplace $PACKAGES
|
||||
;;
|
||||
(portage|*)
|
||||
emerge --noreplace $PACKAGES
|
||||
;;
|
||||
esac
|
||||
@@ -1,60 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Tested with:
|
||||
# - Fedora 22, 23 (x64)
|
||||
# - Centos 7 (x64: on DigitalOcean droplet)
|
||||
# - CentOS 7 Minimal install in a Hyper-V VM
|
||||
|
||||
if type dnf 2>/dev/null
|
||||
then
|
||||
tool=dnf
|
||||
elif type yum 2>/dev/null
|
||||
then
|
||||
tool=yum
|
||||
|
||||
else
|
||||
echo "Neither yum nor dnf found. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Some distros and older versions of current distros use a "python27"
|
||||
# instead of "python" naming convention. Try both conventions.
|
||||
if ! $tool install -y \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv \
|
||||
python-tools \
|
||||
python-pip
|
||||
then
|
||||
if ! $tool install -y \
|
||||
python27 \
|
||||
python27-devel \
|
||||
python27-virtualenv \
|
||||
python27-tools \
|
||||
python27-pip
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! $tool install -y \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-libs \
|
||||
openssl-devel \
|
||||
libffi-devel \
|
||||
redhat-rpm-config \
|
||||
ca-certificates
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if $tool list installed "httpd" >/dev/null 2>&1; then
|
||||
if ! $tool install -y mod_ssl
|
||||
then
|
||||
echo "Apache found, but mod_ssl could not be installed."
|
||||
fi
|
||||
fi
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# SLE12 don't have python-virtualenv
|
||||
|
||||
zypper -nq in -l \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv \
|
||||
gcc \
|
||||
dialog \
|
||||
augeas-lenses \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
ca-certificates \
|
||||
@@ -1 +0,0 @@
|
||||
_arch_common.sh
|
||||
@@ -1 +0,0 @@
|
||||
_rpm_common.sh
|
||||
@@ -1 +0,0 @@
|
||||
_deb_common.sh
|
||||
@@ -1 +0,0 @@
|
||||
This directory contains developer setup.
|
||||
@@ -1 +0,0 @@
|
||||
_rpm_common.sh
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/bin/sh -xe
|
||||
|
||||
pkg install -Ay \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
augeas \
|
||||
libffi \
|
||||
@@ -1 +0,0 @@
|
||||
_gentoo_common.sh
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# Install OS dependencies. In the glorious future, letsencrypt-auto will
|
||||
# source this...
|
||||
|
||||
if test "`id -u`" -ne "0" ; then
|
||||
SUDO=sudo
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
BOOTSTRAP=`dirname $0`
|
||||
if [ ! -f $BOOTSTRAP/debian.sh ] ; then
|
||||
echo "Cannot find the letsencrypt bootstrap scripts in $BOOTSTRAP"
|
||||
exit 1
|
||||
fi
|
||||
if [ -f /etc/debian_version ] ; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_deb_common.sh
|
||||
elif [ -f /etc/arch-release ] ; then
|
||||
echo "Bootstrapping dependencies for Archlinux..."
|
||||
$SUDO $BOOTSTRAP/archlinux.sh
|
||||
elif [ -f /etc/redhat-release ] ; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_rpm_common.sh
|
||||
elif [ -f /etc/gentoo-release ] ; then
|
||||
echo "Bootstrapping dependencies for Gentoo-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_gentoo_common.sh
|
||||
elif uname | grep -iq FreeBSD ; then
|
||||
echo "Bootstrapping dependencies for FreeBSD..."
|
||||
$SUDO $BOOTSTRAP/freebsd.sh
|
||||
elif `grep -qs openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE.."
|
||||
$SUDO $BOOTSTRAP/suse.sh
|
||||
elif uname | grep -iq Darwin ; then
|
||||
echo "Bootstrapping dependencies for Mac OS X..."
|
||||
echo "WARNING: Mac support is very experimental at present..."
|
||||
$BOOTSTRAP/mac.sh
|
||||
else
|
||||
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
|
||||
echo
|
||||
echo "You will need to bootstrap, configure virtualenv, and run a pip install manually"
|
||||
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
|
||||
echo "for more info"
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,18 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
if ! hash brew 2>/dev/null; then
|
||||
echo "Homebrew Not Installed\nDownloading..."
|
||||
ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
|
||||
fi
|
||||
|
||||
brew install augeas
|
||||
brew install dialog
|
||||
|
||||
if ! hash pip 2>/dev/null; then
|
||||
echo "pip Not Installed\nInstalling python from Homebrew..."
|
||||
brew install python
|
||||
fi
|
||||
|
||||
if ! hash virtualenv 2>/dev/null; then
|
||||
echo "virtualenv Not Installed\nInstalling with pip"
|
||||
pip install virtualenv
|
||||
fi
|
||||
@@ -1 +0,0 @@
|
||||
_arch_common.sh
|
||||
@@ -1 +0,0 @@
|
||||
_suse_common.sh
|
||||
@@ -1 +0,0 @@
|
||||
_deb_common.sh
|
||||
@@ -1,33 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# Installs and updates letencrypt virtualenv
|
||||
#
|
||||
# USAGE: source ./dev/venv.sh
|
||||
|
||||
|
||||
XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
||||
VENV_NAME="letsencrypt"
|
||||
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
|
||||
|
||||
# virtualenv call is not idempotent: it overwrites pip upgraded in
|
||||
# later steps, causing "ImportError: cannot import name unpack_url"
|
||||
if [ ! -d $VENV_PATH ]
|
||||
then
|
||||
virtualenv --no-site-packages --python ${LE_PYTHON:-python2} $VENV_PATH
|
||||
fi
|
||||
|
||||
. $VENV_PATH/bin/activate
|
||||
pip install -U setuptools
|
||||
pip install -U pip
|
||||
|
||||
pip install -U letsencrypt letsencrypt-apache # letsencrypt-nginx
|
||||
|
||||
echo
|
||||
echo "Congratulations, Let's Encrypt has been successfully installed/updated!"
|
||||
echo
|
||||
printf "%s" "Your prompt should now be prepended with ($VENV_NAME). Next "
|
||||
printf "time, if the prompt is different, 'source' this script again "
|
||||
printf "before running 'letsencrypt'."
|
||||
echo
|
||||
echo
|
||||
echo "You can now run 'letsencrypt --help'."
|
||||
@@ -23,7 +23,7 @@ once:
|
||||
git clone https://github.com/letsencrypt/letsencrypt
|
||||
cd letsencrypt
|
||||
./letsencrypt-auto-source/letsencrypt-auto --os-packages-only
|
||||
./bootstrap/dev/venv.sh
|
||||
./tools/venv.sh
|
||||
|
||||
Then in each shell where you're working on the client, do:
|
||||
|
||||
@@ -300,7 +300,7 @@ Steps:
|
||||
|
||||
1. Write your code!
|
||||
2. Make sure your environment is set up properly and that you're in your
|
||||
virtualenv. You can do this by running ``./bootstrap/dev/venv.sh``.
|
||||
virtualenv. You can do this by running ``./tools/venv.sh``.
|
||||
(this is a **very important** step)
|
||||
3. Run ``./pep8.travis.sh`` to do a cursory check of your code style.
|
||||
Fix any errors.
|
||||
|
||||
+2
-17
@@ -16,27 +16,12 @@ letsencrypt-auto
|
||||
----------------
|
||||
|
||||
``letsencrypt-auto`` is a wrapper which installs some dependencies
|
||||
from your OS standard package repositories (e.g using `apt-get` or
|
||||
from your OS standard package repositories (e.g. using `apt-get` or
|
||||
`yum`), and for other dependencies it sets up a virtualized Python
|
||||
environment with packages downloaded from PyPI [#venv]_. It also
|
||||
provides automated updates.
|
||||
|
||||
Firstly, please `install Git`_ and run the following commands:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
git clone https://github.com/letsencrypt/letsencrypt
|
||||
cd letsencrypt
|
||||
|
||||
|
||||
.. _`install Git`: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
|
||||
|
||||
.. note:: On RedHat/CentOS 6 you will need to enable the EPEL_
|
||||
repository before install.
|
||||
|
||||
.. _EPEL: http://fedoraproject.org/wiki/EPEL
|
||||
|
||||
To install and run the client you just need to type:
|
||||
To install and run the client, just type...
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
#!/bin/sh -e
|
||||
#
|
||||
# A script to run the latest release version of the Let's Encrypt in a
|
||||
# virtual environment
|
||||
#
|
||||
# Installs and updates the letencrypt virtualenv, and runs letsencrypt
|
||||
# using that virtual environment. This allows the client to function decently
|
||||
# without requiring specific versions of its dependencies from the operating
|
||||
# system.
|
||||
|
||||
# Note: you can set XDG_DATA_HOME or VENV_PATH before running this script,
|
||||
# if you want to change where the virtual environment will be installed
|
||||
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
|
||||
# The path to the letsencrypt-auto script. Everything that uses these might
|
||||
# at some point be inlined...
|
||||
LEA_PATH=`dirname "$0"`
|
||||
BOOTSTRAP=${LEA_PATH}/bootstrap
|
||||
|
||||
# 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
|
||||
# This first clause is redundant with the third, but hedging on portability
|
||||
if [ "$arg" = "-v" ] || [ "$arg" = "--verbose" ] || echo "$arg" | grep -E -- "-v+$" ; then
|
||||
VERBOSE=1
|
||||
elif [ "$arg" = "--debug" ] ; then
|
||||
DEBUG=1
|
||||
fi
|
||||
done
|
||||
|
||||
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
|
||||
# letsencrypt 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`
|
||||
if test "`id -u`" -ne "0" ; then
|
||||
if command -v sudo 1>/dev/null 2>&1; then
|
||||
SUDO=sudo
|
||||
else
|
||||
echo \"sudo\" is not available, will use \"su\" for installation steps...
|
||||
# Because the parameters in `su -c` has to be a string,
|
||||
# we need properly escape it
|
||||
su_sudo() {
|
||||
args=""
|
||||
# This `while` loop iterates over all parameters given to this function.
|
||||
# For each parameter, all `'` will be replace by `'"'"'`, and the escaped string
|
||||
# will be wrapped in a pair of `'`, then appended to `$args` string
|
||||
# For example, `echo "It's only 1\$\!"` will be escaped to:
|
||||
# 'echo' 'It'"'"'s only 1$!'
|
||||
# │ │└┼┘│
|
||||
# │ │ │ └── `'s only 1$!'` the literal string
|
||||
# │ │ └── `\"'\"` is a single quote (as a string)
|
||||
# │ └── `'It'`, to be concatenated with the strings following it
|
||||
# └── `echo` wrapped in a pair of `'`, it's totally fine for the shell command itself
|
||||
while [ $# -ne 0 ]; do
|
||||
args="$args'$(printf "%s" "$1" | sed -e "s/'/'\"'\"'/g")' "
|
||||
shift
|
||||
done
|
||||
su root -c "$args"
|
||||
}
|
||||
SUDO=su_sudo
|
||||
fi
|
||||
else
|
||||
SUDO=
|
||||
fi
|
||||
|
||||
ExperimentalBootstrap() {
|
||||
# Arguments: Platform name, boostrap script name, SUDO command (iff needed)
|
||||
if [ "$DEBUG" = 1 ] ; then
|
||||
if [ "$2" != "" ] ; then
|
||||
echo "Bootstrapping dependencies for $1..."
|
||||
if [ "$3" != "" ] ; then
|
||||
"$3" "$BOOTSTRAP/$2"
|
||||
else
|
||||
"$BOOTSTRAP/$2"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo "WARNING: $1 support is very experimental at present..."
|
||||
echo "if you would like to work on improving it, please ensure you have backups"
|
||||
echo "and then run this script again with the --debug flag!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
DeterminePythonVersion() {
|
||||
if command -v python2.7 > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python2.7}
|
||||
elif command -v python27 > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python27}
|
||||
elif command -v python2 > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python2}
|
||||
elif command -v python > /dev/null ; then
|
||||
export LE_PYTHON=${LE_PYTHON:-python}
|
||||
else
|
||||
echo "Cannot find any Pythons... please install one!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PYVER=`$LE_PYTHON --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
|
||||
if [ $PYVER -lt 26 ] ; then
|
||||
echo "You have an ancient version of Python entombed in your operating system..."
|
||||
echo "This isn't going to work; you'll need at least version 2.6."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
# virtualenv call is not idempotent: it overwrites pip upgraded in
|
||||
# later steps, causing "ImportError: cannot import name unpack_url"
|
||||
if [ ! -d $VENV_PATH ]
|
||||
then
|
||||
if [ ! -f $BOOTSTRAP/debian.sh ] ; then
|
||||
echo "Cannot find the letsencrypt bootstrap scripts in $BOOTSTRAP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f /etc/debian_version ] ; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_deb_common.sh
|
||||
elif [ -f /etc/redhat-release ] ; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_rpm_common.sh
|
||||
elif `grep -q openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_suse_common.sh
|
||||
elif [ -f /etc/arch-release ] ; then
|
||||
if [ "$DEBUG" = 1 ] ; then
|
||||
echo "Bootstrapping dependencies for Archlinux..."
|
||||
$SUDO $BOOTSTRAP/archlinux.sh
|
||||
else
|
||||
echo "Please use pacman to install letsencrypt packages:"
|
||||
echo "# pacman -S letsencrypt letsencrypt-apache"
|
||||
echo
|
||||
echo "If you would like to use the virtualenv way, please run the script again with the"
|
||||
echo "--debug flag."
|
||||
exit 1
|
||||
fi
|
||||
elif [ -f /etc/manjaro-release ] ; then
|
||||
ExperimentalBootstrap "Manjaro Linux" manjaro.sh "$SUDO"
|
||||
elif [ -f /etc/gentoo-release ] ; then
|
||||
ExperimentalBootstrap "Gentoo" _gentoo_common.sh "$SUDO"
|
||||
elif uname | grep -iq FreeBSD ; then
|
||||
ExperimentalBootstrap "FreeBSD" freebsd.sh "$SUDO"
|
||||
elif uname | grep -iq Darwin ; then
|
||||
ExperimentalBootstrap "Mac OS X" mac.sh # homebrew doesn't normally run as root
|
||||
elif grep -iq "Amazon Linux" /etc/issue ; then
|
||||
ExperimentalBootstrap "Amazon Linux" _rpm_common.sh "$SUDO"
|
||||
else
|
||||
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
|
||||
echo
|
||||
echo "You will need to bootstrap, configure virtualenv, and run a pip install manually"
|
||||
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
|
||||
echo "for more info"
|
||||
fi
|
||||
|
||||
DeterminePythonVersion
|
||||
echo "Creating virtual environment..."
|
||||
if [ "$VERBOSE" = 1 ] ; then
|
||||
virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH
|
||||
else
|
||||
virtualenv --no-site-packages --python $LE_PYTHON $VENV_PATH > /dev/null
|
||||
fi
|
||||
else
|
||||
DeterminePythonVersion
|
||||
fi
|
||||
|
||||
|
||||
printf "Updating letsencrypt and virtual environment dependencies..."
|
||||
if [ "$VERBOSE" = 1 ] ; then
|
||||
echo
|
||||
$VENV_BIN/pip install -U setuptools
|
||||
$VENV_BIN/pip install -U pip
|
||||
$VENV_BIN/pip install -U letsencrypt letsencrypt-apache
|
||||
# nginx is buggy / disabled for now, but upgrade it if the user has
|
||||
# installed it manually
|
||||
if $VENV_BIN/pip freeze | grep -q letsencrypt-nginx ; then
|
||||
$VENV_BIN/pip install -U letsencrypt letsencrypt-nginx
|
||||
fi
|
||||
else
|
||||
$VENV_BIN/pip install -U setuptools > /dev/null
|
||||
printf .
|
||||
$VENV_BIN/pip install -U pip > /dev/null
|
||||
printf .
|
||||
# nginx is buggy / disabled for now...
|
||||
$VENV_BIN/pip install -U letsencrypt > /dev/null
|
||||
printf .
|
||||
$VENV_BIN/pip install -U letsencrypt-apache > /dev/null
|
||||
if $VENV_BIN/pip freeze | grep -q letsencrypt-nginx ; then
|
||||
printf .
|
||||
$VENV_BIN/pip install -U letsencrypt-nginx > /dev/null
|
||||
fi
|
||||
echo
|
||||
fi
|
||||
|
||||
# Explain what's about to happen, for the benefit of those getting sudo
|
||||
# password prompts...
|
||||
echo "Requesting root privileges to run with virtualenv:" $SUDO $VENV_BIN/letsencrypt "$@"
|
||||
$SUDO $VENV_BIN/letsencrypt "$@"
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
letsencrypt-auto-source/letsencrypt-auto
|
||||
@@ -290,7 +290,7 @@ BootstrapArchCommon() {
|
||||
#
|
||||
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
|
||||
# only "virtualenv2" binary, not "virtualenv" necessary in
|
||||
# ./bootstrap/dev/_common_venv.sh
|
||||
# ./tools/_venv_common.sh
|
||||
|
||||
deps="
|
||||
python2
|
||||
@@ -372,7 +372,7 @@ Bootstrap() {
|
||||
elif [ -f /etc/redhat-release ]; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
BootstrapRpmCommon
|
||||
elif [ -f /etc/os-release] && `grep -q openSUSE /etc/os-release` ; then
|
||||
elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE-based OSes..."
|
||||
BootstrapSuseCommon
|
||||
elif [ -f /etc/arch-release ]; then
|
||||
|
||||
@@ -128,7 +128,7 @@ Bootstrap() {
|
||||
elif [ -f /etc/redhat-release ]; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
BootstrapRpmCommon
|
||||
elif [ -f /etc/os-release] && `grep -q openSUSE /etc/os-release` ; then
|
||||
elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE-based OSes..."
|
||||
BootstrapSuseCommon
|
||||
elif [ -f /etc/arch-release ]; then
|
||||
|
||||
@@ -4,7 +4,7 @@ BootstrapArchCommon() {
|
||||
#
|
||||
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
|
||||
# only "virtualenv2" binary, not "virtualenv" necessary in
|
||||
# ./bootstrap/dev/_common_venv.sh
|
||||
# ./tools/_venv_common.sh
|
||||
|
||||
deps="
|
||||
python2
|
||||
|
||||
@@ -57,7 +57,7 @@ class AuthHandler(object):
|
||||
def get_authorizations(self, domains, best_effort=False):
|
||||
"""Retrieve all authorizations for challenges.
|
||||
|
||||
:param set domains: Domains for authorization
|
||||
:param list domains: Domains for authorization
|
||||
:param bool best_effort: Whether or not all authorizations are
|
||||
required (this is useful in renewal)
|
||||
|
||||
@@ -480,6 +480,9 @@ def is_preferred(offered_challb, satisfied,
|
||||
return True
|
||||
|
||||
|
||||
_ACME_PREFIX = "urn:acme:error:"
|
||||
|
||||
|
||||
_ERROR_HELP_COMMON = (
|
||||
"To fix these errors, please make sure that your domain name was entered "
|
||||
"correctly and the DNS A record(s) for that domain contain(s) the "
|
||||
@@ -490,7 +493,9 @@ _ERROR_HELP = {
|
||||
"connection":
|
||||
_ERROR_HELP_COMMON + " Additionally, please check that your computer "
|
||||
"has a publicly routable IP address and that no firewalls are preventing "
|
||||
"the server from communicating with the client.",
|
||||
"the server from communicating with the client. If you're using the "
|
||||
"webroot plugin, you should also verify that you are serving files "
|
||||
"from the webroot path you provided.",
|
||||
"dnssec":
|
||||
_ERROR_HELP_COMMON + " Additionally, if you have DNSSEC enabled for "
|
||||
"your domain, please ensure that the signature is valid.",
|
||||
@@ -540,11 +545,13 @@ def _generate_failed_chall_msg(failed_achalls):
|
||||
|
||||
"""
|
||||
typ = failed_achalls[0].error.typ
|
||||
if typ.startswith(_ACME_PREFIX):
|
||||
typ = typ[len(_ACME_PREFIX):]
|
||||
msg = ["The following errors were reported by the server:"]
|
||||
|
||||
for achall in failed_achalls:
|
||||
msg.append("\n\nDomain: %s\nType: %s\nDetail: %s" % (
|
||||
achall.domain, achall.error.typ, achall.error.detail))
|
||||
achall.domain, typ, achall.error.detail))
|
||||
|
||||
if typ in _ERROR_HELP:
|
||||
msg.append("\n\n")
|
||||
|
||||
+151
-55
@@ -338,6 +338,7 @@ def _handle_identical_cert_request(config, cert):
|
||||
else:
|
||||
assert False, "This is impossible"
|
||||
|
||||
|
||||
def _handle_subset_cert_request(config, domains, cert):
|
||||
"""Figure out what to do if a previous cert had a subset of the names now requested
|
||||
|
||||
@@ -420,10 +421,12 @@ def _suggest_donation_if_appropriate(config, action):
|
||||
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
|
||||
|
||||
|
||||
def _report_successful_dry_run():
|
||||
|
||||
def _report_successful_dry_run(config):
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter_util.add_message("The dry run was successful.",
|
||||
reporter_util.HIGH_PRIORITY, on_crash=False)
|
||||
if config.verb != "renew":
|
||||
reporter_util.add_message("The dry run was successful.",
|
||||
reporter_util.HIGH_PRIORITY, on_crash=False)
|
||||
|
||||
|
||||
def _auth_from_domains(le_client, config, domains, lineage=None):
|
||||
@@ -443,7 +446,7 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
|
||||
else:
|
||||
# Renewal, where we already know the specific lineage we're
|
||||
# interested in
|
||||
action = "renew" if _should_renew(config, lineage) else "reinstall"
|
||||
action = "renew"
|
||||
|
||||
if action == "reinstall":
|
||||
# The lineage already exists; allow the caller to try installing
|
||||
@@ -475,7 +478,7 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
|
||||
if lineage is False:
|
||||
raise errors.Error("Certificate could not be obtained")
|
||||
|
||||
if not config.dry_run:
|
||||
if not config.dry_run and not config.verb == "renew":
|
||||
_report_new_cert(lineage.cert, lineage.fullchain)
|
||||
|
||||
return lineage, action
|
||||
@@ -492,7 +495,7 @@ def _avoid_invalidating_lineage(config, lineage, original_server):
|
||||
open(lineage.cert).read())
|
||||
# all our test certs are from happy hacker fake CA, though maybe one day
|
||||
# we should test more methodically
|
||||
now_valid = not "fake" in repr(latest_cert.get_issuer()).lower()
|
||||
now_valid = "fake" not in repr(latest_cert.get_issuer()).lower()
|
||||
|
||||
if _is_staging(config.server):
|
||||
if not _is_staging(original_server) or now_valid:
|
||||
@@ -551,6 +554,7 @@ def set_configurator(previously, now):
|
||||
raise errors.PluginSelectionError(msg.format(repr(previously), repr(now)))
|
||||
return now
|
||||
|
||||
|
||||
def cli_plugin_requests(config):
|
||||
"""
|
||||
Figure out which plugins the user requested with CLI and config options
|
||||
@@ -579,6 +583,7 @@ def cli_plugin_requests(config):
|
||||
|
||||
noninstaller_plugins = ["webroot", "manual", "standalone"]
|
||||
|
||||
|
||||
def choose_configurator_plugins(config, plugins, verb):
|
||||
"""
|
||||
Figure out which configurator we're going to use, modifies
|
||||
@@ -601,7 +606,7 @@ def choose_configurator_plugins(config, plugins, verb):
|
||||
'{1} {2} certonly --{0}{1}{1}'
|
||||
'(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins'
|
||||
'{1} and "--help plugins" for more information.)'.format(
|
||||
req_auth, os.linesep, cli_command))
|
||||
req_auth, os.linesep, cli_command))
|
||||
|
||||
raise errors.MissingCommandlineFlag(msg)
|
||||
else:
|
||||
@@ -676,16 +681,12 @@ def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals
|
||||
def obtain_cert(config, plugins, lineage=None):
|
||||
"""Implements "certonly": authenticate & obtain cert, but do not install it."""
|
||||
|
||||
if config.domains and config.csr is not None:
|
||||
# TODO: --csr could have a priority, when --domains is
|
||||
# supplied, check if CSR matches given domains?
|
||||
return "--domains and --csr are mutually exclusive"
|
||||
|
||||
try:
|
||||
# installers are used in auth mode to determine domain names
|
||||
installer, authenticator = choose_configurator_plugins(config, plugins, "certonly")
|
||||
except errors.PluginSelectionError as e:
|
||||
return e.message
|
||||
logger.info("Could not choose appropriate plugin: %s", e)
|
||||
raise
|
||||
|
||||
# TODO: Handle errors from _init_le_client?
|
||||
le_client = _init_le_client(config, authenticator, installer)
|
||||
@@ -694,8 +695,8 @@ def obtain_cert(config, plugins, lineage=None):
|
||||
# This is a special case; cert and chain are simply saved
|
||||
if config.csr is not None:
|
||||
assert lineage is None, "Did not expect a CSR with a RenewableCert"
|
||||
certr, chain = le_client.obtain_certificate_from_csr(le_util.CSR(
|
||||
file=config.csr[0], data=config.csr[1], form="der"))
|
||||
csr, typ = config.actual_csr
|
||||
certr, chain = le_client.obtain_certificate_from_csr(config.domains, csr, typ)
|
||||
if config.dry_run:
|
||||
logger.info(
|
||||
"Dry run: skipping saving certificate to %s", config.cert_path)
|
||||
@@ -708,13 +709,19 @@ def obtain_cert(config, plugins, lineage=None):
|
||||
_, action = _auth_from_domains(le_client, config, domains, lineage)
|
||||
|
||||
if config.dry_run:
|
||||
_report_successful_dry_run()
|
||||
elif config.verb == "renew" and installer is not None:
|
||||
# In case of a renewal, reload server to pick up new certificate.
|
||||
# In principle we could have a configuration option to inhibit this
|
||||
# from happening.
|
||||
installer.restart()
|
||||
print("reloaded")
|
||||
_report_successful_dry_run(config)
|
||||
elif config.verb == "renew":
|
||||
if installer is None:
|
||||
# Tell the user that the server was not restarted.
|
||||
print("new certificate deployed without reload, fullchain is",
|
||||
lineage.fullchain)
|
||||
else:
|
||||
# In case of a renewal, reload server to pick up new certificate.
|
||||
# In principle we could have a configuration option to inhibit this
|
||||
# from happening.
|
||||
installer.restart()
|
||||
print("new certificate deployed with reload of",
|
||||
config.installer, "server; fullchain is", lineage.fullchain)
|
||||
_suggest_donation_if_appropriate(config, action)
|
||||
|
||||
|
||||
@@ -766,6 +773,7 @@ def _restore_required_config_elements(config, renewalparams):
|
||||
raise errors.Error(
|
||||
"Expected a numeric value for {0}".format(config_item))
|
||||
|
||||
|
||||
def _restore_plugin_configs(config, renewalparams):
|
||||
"""Sets plugin specific values in config from renewalparams
|
||||
|
||||
@@ -798,7 +806,7 @@ def _restore_plugin_configs(config, renewalparams):
|
||||
if config_value == "None":
|
||||
setattr(config.namespace, config_item, None)
|
||||
continue
|
||||
for action in _parser.parser._actions: # pylint: disable=protected-access
|
||||
for action in _parser.parser._actions: # pylint: disable=protected-access
|
||||
if action.type is not None and action.dest == config_item:
|
||||
setattr(config.namespace, config_item,
|
||||
action.type(config_value))
|
||||
@@ -861,10 +869,10 @@ def _reconstitute(config, full_path):
|
||||
try:
|
||||
domains = [le_util.enforce_domain_sanity(x) for x in
|
||||
renewal_candidate.names()]
|
||||
except (UnicodeError, ValueError):
|
||||
except errors.ConfigurationError as error:
|
||||
logger.warning("Renewal configuration file %s references a cert "
|
||||
"that mentions a domain name that we regarded as "
|
||||
"invalid. Skipping.", full_path)
|
||||
"that contains an invalid domain name. The problem "
|
||||
"was: %s. Skipping.", full_path, error)
|
||||
return None
|
||||
|
||||
setattr(config.namespace, "domains", domains)
|
||||
@@ -876,6 +884,42 @@ def _renewal_conf_files(config):
|
||||
return glob.glob(os.path.join(config.renewal_configs_dir, "*.conf"))
|
||||
|
||||
|
||||
def _renew_describe_results(config, renew_successes, renew_failures,
|
||||
renew_skipped, parse_failures):
|
||||
status = lambda x, msg: " " + "\n ".join(i + " (" + msg +")" for i in x)
|
||||
if config.dry_run:
|
||||
print("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry")
|
||||
print("** (The test certificates below have not been saved.)")
|
||||
print()
|
||||
if renew_skipped:
|
||||
print("The following certs are not due for renewal yet:")
|
||||
print(status(renew_skipped, "skipped"))
|
||||
if not renew_successes and not renew_failures:
|
||||
print("No renewals were attempted.")
|
||||
elif renew_successes and not renew_failures:
|
||||
print("Congratulations, all renewals succeeded. The following certs "
|
||||
"have been renewed:")
|
||||
print(status(renew_successes, "success"))
|
||||
elif renew_failures and not renew_successes:
|
||||
print("All renewal attempts failed. The following certs could not be "
|
||||
"renewed:")
|
||||
print(status(renew_failures, "failure"))
|
||||
elif renew_failures and renew_successes:
|
||||
print("The following certs were successfully renewed:")
|
||||
print(status(renew_successes, "success"))
|
||||
print("\nThe following certs could not be renewed:")
|
||||
print(status(renew_failures, "failure"))
|
||||
|
||||
if parse_failures:
|
||||
print("\nAdditionally, the following renewal configuration files "
|
||||
"were invalid: ")
|
||||
print(status(parse_failures, "parsefail"))
|
||||
|
||||
if config.dry_run:
|
||||
print("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry")
|
||||
print("** (The test certificates above have not been saved.)")
|
||||
|
||||
|
||||
def renew(config, unused_plugins):
|
||||
"""Renew previously-obtained certificates."""
|
||||
if config.domains != []:
|
||||
@@ -891,43 +935,47 @@ def renew(config, unused_plugins):
|
||||
"specifying a CSR file. Please try the certonly "
|
||||
"command instead.")
|
||||
renewer_config = configuration.RenewerConfiguration(config)
|
||||
renew_successes = []
|
||||
renew_failures = []
|
||||
renew_skipped = []
|
||||
parse_failures = []
|
||||
for renewal_file in _renewal_conf_files(renewer_config):
|
||||
print("Processing " + renewal_file)
|
||||
# XXX: does this succeed in making a fully independent config object
|
||||
# each time?
|
||||
lineage_config = copy.deepcopy(config)
|
||||
|
||||
# Note that this modifies config (to add back the configuration
|
||||
# elements from within the renewal configuration file).
|
||||
try:
|
||||
renewal_candidate = _reconstitute(lineage_config, renewal_file)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
# reconstitute encountered an unanticipated problem.
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.warning("Renewal configuration file %s produced an "
|
||||
"unexpected error: %s. Skipping.", renewal_file, e)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
parse_failures.append(renewal_file)
|
||||
continue
|
||||
|
||||
try:
|
||||
if renewal_candidate is not None:
|
||||
# _reconstitute succeeded in producing a RenewableCert, so we
|
||||
# have something to work with from this particular config file.
|
||||
|
||||
if renewal_candidate is None:
|
||||
parse_failures.append(renewal_file)
|
||||
else:
|
||||
# XXX: ensure that each call here replaces the previous one
|
||||
zope.component.provideUtility(lineage_config)
|
||||
print("Trying...")
|
||||
# Because obtain_cert itself indirectly decides whether to renew
|
||||
# or not, we couldn't currently make a UI/logging distinction at
|
||||
# this stage to indicate whether renewal was actually attempted
|
||||
# (or successful).
|
||||
obtain_cert(lineage_config,
|
||||
plugins_disco.PluginsRegistry.find_all(),
|
||||
renewal_candidate)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
if _should_renew(lineage_config, renewal_candidate):
|
||||
plugins = plugins_disco.PluginsRegistry.find_all()
|
||||
obtain_cert(lineage_config, plugins, renewal_candidate)
|
||||
renew_successes.append(renewal_candidate.fullchain)
|
||||
else:
|
||||
renew_skipped.append(renewal_candidate.fullchain)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
# obtain_cert (presumably) encountered an unanticipated problem.
|
||||
logger.warning("Attempting to renew cert from %s produced an "
|
||||
"unexpected error: %s. Skipping.", renewal_file, e)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
renew_failures.append(renewal_candidate.fullchain)
|
||||
|
||||
# Describe all the results
|
||||
_renew_describe_results(config, renew_successes, renew_failures,
|
||||
renew_skipped, parse_failures)
|
||||
|
||||
|
||||
def revoke(config, unused_plugins): # TODO: coop with renewal config
|
||||
@@ -1117,8 +1165,53 @@ class HelpfulArgumentParser(object):
|
||||
"'certonly' or 'renew' subcommands")
|
||||
parsed_args.break_my_certs = parsed_args.staging = True
|
||||
|
||||
if parsed_args.csr:
|
||||
self.handle_csr(parsed_args)
|
||||
|
||||
return parsed_args
|
||||
|
||||
def handle_csr(self, parsed_args):
|
||||
"""
|
||||
Process a --csr flag. This needs to happen early enough that the
|
||||
webroot plugin can know about the calls to _process_domain
|
||||
"""
|
||||
try:
|
||||
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
|
||||
typ = OpenSSL.crypto.FILETYPE_ASN1
|
||||
domains = crypto_util.get_sans_from_csr(csr.data, OpenSSL.crypto.FILETYPE_ASN1)
|
||||
except OpenSSL.crypto.Error:
|
||||
try:
|
||||
e1 = traceback.format_exc()
|
||||
typ = OpenSSL.crypto.FILETYPE_PEM
|
||||
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="pem")
|
||||
domains = crypto_util.get_sans_from_csr(csr.data, typ)
|
||||
except OpenSSL.crypto.Error:
|
||||
logger.debug("DER CSR parse error %s", e1)
|
||||
logger.debug("PEM CSR parse error %s", traceback.format_exc())
|
||||
raise errors.Error("Failed to parse CSR file: {0}".format(parsed_args.csr[0]))
|
||||
for d in domains:
|
||||
_process_domain(parsed_args, d)
|
||||
|
||||
for d in domains:
|
||||
sanitised = le_util.enforce_domain_sanity(d)
|
||||
if d.lower() != sanitised:
|
||||
raise errors.ConfigurationError(
|
||||
"CSR domain {0} needs to be sanitised to {1}.".format(d, sanitised))
|
||||
|
||||
if not domains:
|
||||
# TODO: add CN to domains instead:
|
||||
raise errors.Error(
|
||||
"Unfortunately, your CSR %s needs to have a SubjectAltName for every domain"
|
||||
% parsed_args.csr[0])
|
||||
|
||||
parsed_args.actual_csr = (csr, typ)
|
||||
csr_domains, config_domains = set(domains), set(parsed_args.domains)
|
||||
if csr_domains != config_domains:
|
||||
raise errors.ConfigurationError(
|
||||
"Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}"
|
||||
.format(", ".join(csr_domains), ", ".join(config_domains)))
|
||||
|
||||
|
||||
def determine_verb(self):
|
||||
"""Determines the verb/subcommand provided by the user.
|
||||
|
||||
@@ -1403,7 +1496,7 @@ def prepare_and_parse_args(plugins, args):
|
||||
# parser (--help should display plugin-specific options last)
|
||||
_plugins_parsing(helpful, plugins)
|
||||
|
||||
global _parser # pylint: disable=global-statement
|
||||
global _parser # pylint: disable=global-statement
|
||||
_parser = helpful
|
||||
return helpful.parse_args()
|
||||
|
||||
@@ -1540,14 +1633,17 @@ def _plugins_parsing(helpful, plugins):
|
||||
"www.example.com -w /var/www/thing -d thing.net -d m.thing.net`")
|
||||
# --webroot-map still has some awkward properties, so it is undocumented
|
||||
helpful.add("webroot", "--webroot-map", default={}, action=WebrootMapProcessor,
|
||||
help="JSON dictionary mapping domains to webroot paths; this implies -d "
|
||||
"for each entry. You may need to escape this from your shell. "
|
||||
"""Eg: --webroot-map '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' """
|
||||
"This option is merged with, but takes precedence over, -w / -d entries."
|
||||
" At present, if you put webroot-map in a config file, it needs to be "
|
||||
' on a single line, like: webroot-map = {"example.com":"/var/www"}.')
|
||||
help="JSON dictionary mapping domains to webroot paths; this "
|
||||
"implies -d for each entry. You may need to escape this "
|
||||
"from your shell. E.g.: --webroot-map "
|
||||
"""'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' """
|
||||
"This option is merged with, but takes precedence over, "
|
||||
"-w / -d entries. At present, if you put webroot-map in "
|
||||
"a config file, it needs to be on a single line, like: "
|
||||
'webroot-map = {"example.com":"/var/www"}.')
|
||||
|
||||
class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring
|
||||
|
||||
class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.domain_before_webroot = False
|
||||
argparse.Action.__init__(self, *args, **kwargs)
|
||||
@@ -1596,14 +1692,14 @@ def _process_domain(args_or_config, domain_arg, webroot_path=None):
|
||||
args_or_config.webroot_map.setdefault(domain, webroot_path[-1])
|
||||
|
||||
|
||||
class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring
|
||||
class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring
|
||||
def __call__(self, parser, args, webroot_map_arg, option_string=None):
|
||||
webroot_map = json.loads(webroot_map_arg)
|
||||
for domains, webroot_path in webroot_map.iteritems():
|
||||
_process_domain(args, domains, [webroot_path])
|
||||
|
||||
|
||||
class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring
|
||||
class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring
|
||||
def __call__(self, parser, args, domain_arg, option_string=None):
|
||||
"""Just wrap _process_domain in argparseese."""
|
||||
_process_domain(args, domain_arg)
|
||||
@@ -1694,8 +1790,8 @@ def _handle_exception(exc_type, exc_value, trace, config):
|
||||
# acme.messages.Error: urn:acme:error:malformed :: The request message was
|
||||
# malformed :: Error creating new registration :: Validation of contact
|
||||
# mailto:none@longrandomstring.biz failed: Server failure at resolver
|
||||
if ("urn:acme" in err and ":: " in err
|
||||
and config.verbose_count <= flag_default("verbose_count")):
|
||||
if (("urn:acme" in err and ":: " in err and
|
||||
config.verbose_count <= flag_default("verbose_count"))):
|
||||
# prune ACME error code, we have a human description
|
||||
_code, _sep, err = err.partition(":: ")
|
||||
msg = "An unexpected error occurred:\n" + err + "Please see the "
|
||||
|
||||
+5
-19
@@ -195,7 +195,8 @@ class Client(object):
|
||||
else:
|
||||
self.auth_handler = None
|
||||
|
||||
def _obtain_certificate(self, domains, csr):
|
||||
def obtain_certificate_from_csr(self, domains, csr,
|
||||
typ=OpenSSL.crypto.FILETYPE_ASN1):
|
||||
"""Obtain certificate.
|
||||
|
||||
Internal function with precondition that `domains` are
|
||||
@@ -223,26 +224,11 @@ class Client(object):
|
||||
|
||||
authzr = self.auth_handler.get_authorizations(domains)
|
||||
certr = self.acme.request_issuance(
|
||||
jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, csr.data)),
|
||||
jose.ComparableX509(
|
||||
OpenSSL.crypto.load_certificate_request(typ, csr.data)),
|
||||
authzr)
|
||||
return certr, self.acme.fetch_chain(certr)
|
||||
|
||||
def obtain_certificate_from_csr(self, csr):
|
||||
"""Obtain certficiate from CSR.
|
||||
|
||||
:param .le_util.CSR csr: DER-encoded Certificate Signing
|
||||
Request.
|
||||
|
||||
:returns: `.CertificateResource` and certificate chain (as
|
||||
returned by `.fetch_chain`).
|
||||
:rtype: tuple
|
||||
|
||||
"""
|
||||
return self._obtain_certificate(
|
||||
# TODO: add CN to domains?
|
||||
crypto_util.get_sans_from_csr(
|
||||
csr.data, OpenSSL.crypto.FILETYPE_ASN1), csr)
|
||||
|
||||
def obtain_certificate(self, domains):
|
||||
"""Obtains a certificate from the ACME server.
|
||||
@@ -263,7 +249,7 @@ class Client(object):
|
||||
self.config.rsa_key_size, self.config.key_dir)
|
||||
csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)
|
||||
|
||||
return self._obtain_certificate(domains, csr) + (key, csr)
|
||||
return self.obtain_certificate_from_csr(domains, csr) + (key, csr)
|
||||
|
||||
def obtain_and_enroll_certificate(self, domains):
|
||||
"""Obtain and enroll certificate.
|
||||
|
||||
@@ -446,7 +446,7 @@ class NoninteractiveDisplay(object):
|
||||
line=os.linesep, frame=side_frame, msg=message))
|
||||
|
||||
def menu(self, message, choices, ok_label=None, cancel_label=None,
|
||||
default=None, cli_flag=None):
|
||||
help_label=None, default=None, cli_flag=None):
|
||||
# pylint: disable=unused-argument,too-many-arguments
|
||||
"""Avoid displaying a menu.
|
||||
|
||||
|
||||
@@ -308,7 +308,7 @@ def enforce_domain_sanity(domain):
|
||||
|
||||
# Unicode
|
||||
try:
|
||||
domain = domain.encode('ascii')
|
||||
domain = domain.encode('ascii').lower()
|
||||
except UnicodeDecodeError:
|
||||
raise errors.ConfigurationError(
|
||||
"Internationalized domain names are not presently supported: {0}"
|
||||
|
||||
@@ -91,7 +91,7 @@ s.serve_forever()" """
|
||||
help="Automatically allows public IP logging.")
|
||||
|
||||
def prepare(self): # pylint: disable=missing-docstring,no-self-use
|
||||
if self.config.noninteractive_mode:
|
||||
if self.config.noninteractive_mode and not self.conf("test-mode"):
|
||||
raise errors.PluginError("Running manual mode non-interactively is not supported")
|
||||
|
||||
def more_info(self): # pylint: disable=missing-docstring,no-self-use
|
||||
|
||||
@@ -23,16 +23,21 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.plugins.manual import Authenticator
|
||||
self.config = mock.MagicMock(
|
||||
http01_port=8080, manual_test_mode=False, manual_public_ip_logging_ok=False)
|
||||
http01_port=8080, manual_test_mode=False,
|
||||
manual_public_ip_logging_ok=False, noninteractive_mode=True)
|
||||
self.auth = Authenticator(config=self.config, name="manual")
|
||||
self.achalls = [achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
challb=acme_util.HTTP01_P, domain="foo.com", account_key=KEY)]
|
||||
|
||||
config_test_mode = mock.MagicMock(
|
||||
http01_port=8080, manual_test_mode=True)
|
||||
http01_port=8080, manual_test_mode=True, noninteractive_mode=True)
|
||||
self.auth_test_mode = Authenticator(
|
||||
config=config_test_mode, name="manual")
|
||||
|
||||
def test_prepare(self):
|
||||
self.assertRaises(errors.PluginError, self.auth.prepare)
|
||||
self.auth_test_mode.prepare() # error not raised
|
||||
|
||||
def test_more_info(self):
|
||||
self.assertTrue(isinstance(self.auth.more_info(), str))
|
||||
|
||||
|
||||
@@ -90,6 +90,9 @@ class ServerManager(object):
|
||||
logger.debug("Stopping server at %s:%d...",
|
||||
*instance.server.socket.getsockname()[:2])
|
||||
instance.server.shutdown()
|
||||
# Not calling server_close causes problems when renewing multiple
|
||||
# certs with `letsencrypt renew` using TLSSNI01 and PyOpenSSL 0.13
|
||||
instance.server.server_close()
|
||||
instance.thread.join()
|
||||
del self._instances[port]
|
||||
|
||||
@@ -200,7 +203,8 @@ class Authenticator(common.Plugin):
|
||||
return self.supported_challenges
|
||||
|
||||
def perform(self, achalls): # pylint: disable=missing-docstring
|
||||
if any(util.already_listening(port) for port in self._necessary_ports):
|
||||
renewer = self.config.verb == "renew"
|
||||
if any(util.already_listening(port, renewer) for port in self._necessary_ports):
|
||||
raise errors.MisconfigurationError(
|
||||
"At least one of the (possibly) required ports is "
|
||||
"already taken.")
|
||||
|
||||
@@ -125,7 +125,7 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
self.config.standalone_supported_challenges = chall
|
||||
self.assertRaises(
|
||||
errors.MisconfigurationError, self.auth.perform, [])
|
||||
mock_util.already_listening.assert_called_once_with(port)
|
||||
mock_util.already_listening.assert_called_once_with(port, False)
|
||||
mock_util.already_listening.reset_mock()
|
||||
|
||||
@mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility")
|
||||
|
||||
@@ -11,7 +11,7 @@ from letsencrypt import interfaces
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def already_listening(port):
|
||||
def already_listening(port, renewer=False):
|
||||
"""Check if a process is already listening on the port.
|
||||
|
||||
If so, also tell the user via a display notification.
|
||||
@@ -49,11 +49,20 @@ def already_listening(port):
|
||||
pid = listeners[0]
|
||||
name = psutil.Process(pid).name()
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
extra = ""
|
||||
if renewer:
|
||||
extra = (
|
||||
" For automated renewal, you may want to use a script that stops"
|
||||
" and starts your webserver. You can find an example at"
|
||||
" https://letsencrypt.org/howitworks/#writing-your-own-renewal-script"
|
||||
". Alternatively you can use the webroot plugin to renew without"
|
||||
" needing to stop and start your webserver.")
|
||||
display.notification(
|
||||
"The program {0} (process ID {1}) is already listening "
|
||||
"on TCP port {2}. This will prevent us from binding to "
|
||||
"that port. Please stop the {0} program temporarily "
|
||||
"and then try again.".format(name, pid, port))
|
||||
"and then try again.{3}".format(name, pid, port, extra),
|
||||
height=13)
|
||||
return True
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
# Perhaps the result of a race where the process could have
|
||||
|
||||
@@ -49,8 +49,10 @@ to serve all files under specified web root ({0})."""
|
||||
path_map = self.conf("map")
|
||||
|
||||
if not path_map:
|
||||
raise errors.PluginError("--{0} must be set".format(
|
||||
self.option_name("path")))
|
||||
raise errors.PluginError(
|
||||
"Missing parts of webroot configuration; please set either "
|
||||
"--webroot-path and --domains, or --webroot-map. Run with "
|
||||
" --help webroot for examples.")
|
||||
for name, path in path_map.items():
|
||||
if not os.path.isdir(path):
|
||||
raise errors.PluginError(path + " does not exist or is not a directory")
|
||||
|
||||
@@ -437,9 +437,12 @@ class ReportFailedChallsTest(unittest.TestCase):
|
||||
"chall": acme_util.HTTP01,
|
||||
"uri": "uri",
|
||||
"status": messages.STATUS_INVALID,
|
||||
"error": messages.Error(typ="tls", detail="detail"),
|
||||
"error": messages.Error(typ="urn:acme:error:tls", detail="detail"),
|
||||
}
|
||||
|
||||
# Prevent future regressions if the error type changes
|
||||
self.assertTrue(kwargs["error"].description is not None)
|
||||
|
||||
self.http01 = achallenges.KeyAuthorizationAnnotatedChallenge(
|
||||
# pylint: disable=star-args
|
||||
challb=messages.ChallengeBody(**kwargs),
|
||||
|
||||
@@ -227,8 +227,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
self.assertTrue("MisconfigurationError" in ret)
|
||||
|
||||
args = ["certonly", "--webroot"]
|
||||
ret, _, _, _ = self._call(args)
|
||||
self.assertTrue("--webroot-path must be set" in ret)
|
||||
try:
|
||||
self._call(args)
|
||||
assert False, "Exception should have been raised"
|
||||
except errors.PluginSelectionError as e:
|
||||
self.assertTrue("please set either --webroot-path" in e.message)
|
||||
|
||||
self._cli_missing_flag(["--standalone"], "With the standalone plugin, you probably")
|
||||
|
||||
@@ -324,11 +327,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
self.assertEqual(config.fullchain_path, os.path.abspath(fullchain))
|
||||
|
||||
def test_certonly_bad_args(self):
|
||||
ret, _, _, _ = self._call(['-d', 'foo.bar', 'certonly', '--csr', CSR])
|
||||
self.assertEqual(ret, '--domains and --csr are mutually exclusive')
|
||||
|
||||
ret, _, _, _ = self._call(['-a', 'bad_auth', 'certonly'])
|
||||
self.assertEqual(ret, 'The requested bad_auth plugin does not appear to be installed')
|
||||
try:
|
||||
self._call(['-a', 'bad_auth', 'certonly'])
|
||||
assert False, "Exception should have been raised"
|
||||
except errors.PluginSelectionError as e:
|
||||
self.assertTrue('The requested bad_auth plugin does not appear' in e.message)
|
||||
|
||||
def test_check_config_sanity_domain(self):
|
||||
# Punycode
|
||||
@@ -603,19 +606,103 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
print "Logs:"
|
||||
print lf.read()
|
||||
|
||||
|
||||
def test_renewal_verb(self):
|
||||
def test_renew_verb(self):
|
||||
with open(test_util.vector_path('sample-renewal.conf')) as src:
|
||||
# put the correct path for cert.pem, chain.pem etc in the renewal conf
|
||||
renewal_conf = src.read().replace("MAGICDIR", test_util.vector_path())
|
||||
rd = os.path.join(self.config_dir, "renewal")
|
||||
os.makedirs(rd)
|
||||
if not os.path.exists(rd):
|
||||
os.makedirs(rd)
|
||||
rc = os.path.join(rd, "sample-renewal.conf")
|
||||
with open(rc, "w") as dest:
|
||||
dest.write(renewal_conf)
|
||||
args = ["renew", "--dry-run", "-tvv"]
|
||||
self._test_renewal_common(True, [], args=args, renew=True)
|
||||
|
||||
def test_renew_verb_empty_config(self):
|
||||
renewer_configs_dir = os.path.join(self.config_dir, 'renewal')
|
||||
os.makedirs(renewer_configs_dir)
|
||||
with open(os.path.join(renewer_configs_dir, 'empty.conf'), 'w'):
|
||||
pass # leave the file empty
|
||||
self.test_renew_verb()
|
||||
|
||||
def _make_dummy_renewal_config(self):
|
||||
renewer_configs_dir = os.path.join(self.config_dir, 'renewal')
|
||||
os.makedirs(renewer_configs_dir)
|
||||
with open(os.path.join(renewer_configs_dir, 'test.conf'), 'w') as f:
|
||||
f.write("My contents don't matter")
|
||||
|
||||
def _test_renew_common(self, renewalparams=None,
|
||||
names=None, assert_oc_called=None):
|
||||
self._make_dummy_renewal_config()
|
||||
with mock.patch('letsencrypt.storage.RenewableCert') as mock_rc:
|
||||
mock_lineage = mock.MagicMock()
|
||||
mock_lineage.fullchain = "somepath/fullchain.pem"
|
||||
if renewalparams is not None:
|
||||
mock_lineage.configuration = {'renewalparams': renewalparams}
|
||||
if names is not None:
|
||||
mock_lineage.names.return_value = names
|
||||
mock_rc.return_value = mock_lineage
|
||||
with mock.patch('letsencrypt.cli.obtain_cert') as mock_obtain_cert:
|
||||
self._test_renewal_common(True, None,
|
||||
args=['renew'], renew=False)
|
||||
if assert_oc_called is not None:
|
||||
if assert_oc_called:
|
||||
self.assertTrue(mock_obtain_cert.called)
|
||||
else:
|
||||
self.assertFalse(mock_obtain_cert.called)
|
||||
|
||||
def test_renew_no_renewalparams(self):
|
||||
self._test_renew_common(assert_oc_called=False)
|
||||
|
||||
def test_renew_no_authenticator(self):
|
||||
self._test_renew_common(renewalparams={}, assert_oc_called=False)
|
||||
|
||||
def test_renew_with_bad_int(self):
|
||||
renewalparams = {'authenticator': 'webroot',
|
||||
'rsa_key_size': 'over 9000'}
|
||||
self._test_renew_common(renewalparams=renewalparams,
|
||||
assert_oc_called=False)
|
||||
|
||||
def test_renew_with_bad_domain(self):
|
||||
renewalparams = {'authenticator': 'webroot'}
|
||||
names = ['*.example.com']
|
||||
self._test_renew_common(renewalparams=renewalparams,
|
||||
names=names, assert_oc_called=False)
|
||||
|
||||
def test_renew_plugin_config_restoration(self):
|
||||
renewalparams = {'authenticator': 'webroot',
|
||||
'webroot_path': 'None',
|
||||
'webroot_imaginary_flag': '42'}
|
||||
self._test_renew_common(renewalparams=renewalparams,
|
||||
assert_oc_called=True)
|
||||
|
||||
def test_renew_reconstitute_error(self):
|
||||
# pylint: disable=protected-access
|
||||
with mock.patch('letsencrypt.cli._reconstitute') as mock_reconstitute:
|
||||
mock_reconstitute.side_effect = Exception
|
||||
self._test_renew_common(assert_oc_called=False)
|
||||
|
||||
def test_renew_obtain_cert_error(self):
|
||||
self._make_dummy_renewal_config()
|
||||
with mock.patch('letsencrypt.storage.RenewableCert') as mock_rc:
|
||||
mock_lineage = mock.MagicMock()
|
||||
mock_lineage.fullchain = "somewhere/fullchain.pem"
|
||||
mock_rc.return_value = mock_lineage
|
||||
mock_lineage.configuration = {
|
||||
'renewalparams': {'authenticator': 'webroot'}}
|
||||
with mock.patch('letsencrypt.cli.obtain_cert') as mock_obtain_cert:
|
||||
mock_obtain_cert.side_effect = Exception
|
||||
self._test_renewal_common(True, None,
|
||||
args=['renew'], renew=False)
|
||||
|
||||
def test_renew_with_bad_cli_args(self):
|
||||
self.assertRaises(errors.Error, self._test_renewal_common, True, None,
|
||||
args='renew -d example.com'.split(), renew=False)
|
||||
self.assertRaises(errors.Error, self._test_renewal_common, True, None,
|
||||
args='renew --csr {0}'.format(CSR).split(),
|
||||
renew=False)
|
||||
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
@mock.patch('letsencrypt.cli._treat_as_renewal')
|
||||
@mock.patch('letsencrypt.cli._init_le_client')
|
||||
|
||||
@@ -82,6 +82,7 @@ class ClientTest(unittest.TestCase):
|
||||
no_verify_ssl=False, config_dir="/etc/letsencrypt")
|
||||
# pylint: disable=star-args
|
||||
self.account = mock.MagicMock(**{"key.pem": KEY})
|
||||
self.eg_domains = ["example.com", "www.example.com"]
|
||||
|
||||
from letsencrypt.client import Client
|
||||
with mock.patch("letsencrypt.client.acme_client.Client") as acme:
|
||||
@@ -101,21 +102,40 @@ class ClientTest(unittest.TestCase):
|
||||
self.acme.fetch_chain.return_value = mock.sentinel.chain
|
||||
|
||||
def _check_obtain_certificate(self):
|
||||
self.client.auth_handler.get_authorizations.assert_called_once_with(
|
||||
["example.com", "www.example.com"])
|
||||
self.client.auth_handler.get_authorizations.assert_called_once_with(self.eg_domains)
|
||||
self.acme.request_issuance.assert_called_once_with(
|
||||
jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, CSR_SAN)),
|
||||
self.client.auth_handler.get_authorizations())
|
||||
self.acme.fetch_chain.assert_called_once_with(mock.sentinel.certr)
|
||||
|
||||
def test_obtain_certificate_from_csr(self):
|
||||
# FIXME move parts of this to test_cli.py...
|
||||
@mock.patch("letsencrypt.cli._process_domain")
|
||||
def test_obtain_certificate_from_csr(self, mock_process_domain):
|
||||
self._mock_obtain_certificate()
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(le_util.CSR(
|
||||
form="der", file=None, data=CSR_SAN)))
|
||||
self._check_obtain_certificate()
|
||||
from letsencrypt import cli
|
||||
test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
|
||||
mock_parsed_args = mock.MagicMock()
|
||||
with mock.patch("letsencrypt.client.le_util.CSR") as mock_CSR:
|
||||
mock_CSR.return_value = test_csr
|
||||
mock_parsed_args.domains = self.eg_domains[:]
|
||||
mock_parser = mock.MagicMock(cli.HelpfulArgumentParser)
|
||||
cli.HelpfulArgumentParser.handle_csr(mock_parser, mock_parsed_args)
|
||||
|
||||
# make sure cli processing occurred
|
||||
cli_processed = (call[0][1] for call in mock_process_domain.call_args_list)
|
||||
self.assertEqual(set(cli_processed), set(("example.com", "www.example.com")))
|
||||
# Now provoke an inconsistent domains error...
|
||||
mock_parsed_args.domains.append("hippopotamus.io")
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
cli.HelpfulArgumentParser.handle_csr, mock_parser, mock_parsed_args)
|
||||
|
||||
self.assertEqual(
|
||||
(mock.sentinel.certr, mock.sentinel.chain),
|
||||
self.client.obtain_certificate_from_csr(self.eg_domains, test_csr))
|
||||
# and that the cert was obtained correctly
|
||||
self._check_obtain_certificate()
|
||||
|
||||
|
||||
@mock.patch("letsencrypt.client.crypto_util")
|
||||
def test_obtain_certificate(self, mock_crypto_util):
|
||||
|
||||
@@ -20,17 +20,16 @@ else
|
||||
readlink="readlink"
|
||||
fi
|
||||
|
||||
common() {
|
||||
letsencrypt_test \
|
||||
common_no_force_renew() {
|
||||
letsencrypt_test_no_force_renew \
|
||||
--authenticator standalone \
|
||||
--installer null \
|
||||
"$@"
|
||||
}
|
||||
|
||||
common_no_force_renew() {
|
||||
letsencrypt_test_no_force_renew \
|
||||
--authenticator standalone \
|
||||
--installer null \
|
||||
common() {
|
||||
common_no_force_renew \
|
||||
--renew-by-default \
|
||||
"$@"
|
||||
}
|
||||
|
||||
@@ -51,21 +50,27 @@ common --domains le3.wtf install \
|
||||
--cert-path "${root}/csr/cert.pem" \
|
||||
--key-path "${root}/csr/key.pem"
|
||||
|
||||
CheckCertCount() {
|
||||
CERTCOUNT=`ls "${root}/conf/archive/le.wtf/cert"* | wc -l`
|
||||
if [ "$CERTCOUNT" -ne "$1" ] ; then
|
||||
echo Wrong cert count, not "$1" `ls "${root}/conf/archive/le.wtf/"*`
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
CheckCertCount 1
|
||||
# This won't renew (because it's not time yet)
|
||||
letsencrypt_test_no_force_renew --authenticator standalone --installer null renew
|
||||
common_no_force_renew renew
|
||||
CheckCertCount 1
|
||||
|
||||
# --renew-by-default is used, so renewal should occur
|
||||
common renew
|
||||
CheckCertCount 2
|
||||
|
||||
# This will renew because the expiry is less than 10 years from now
|
||||
sed -i "4arenew_before_expiry = 10 years" "$root/conf/renewal/le1.wtf.conf"
|
||||
letsencrypt_test_no_force_renew --authenticator standalone --installer null renew
|
||||
|
||||
ls "$root/conf/archive/le1.wtf"
|
||||
# dir="$root/conf/archive/le1.wtf"
|
||||
# for x in cert chain fullchain privkey;
|
||||
# do
|
||||
# latest="$(ls -1t $dir/ | grep -e "^${x}" | head -n1)"
|
||||
# live="$($readlink -f "$root/conf/live/le1.wtf/${x}.pem")"
|
||||
# [ "${dir}/${latest}" = "$live" ] # renewer fails this test
|
||||
# done
|
||||
sed -i "4arenew_before_expiry = 10 years" "$root/conf/renewal/le.wtf.conf"
|
||||
common_no_force_renew renew
|
||||
CheckCertCount 3
|
||||
|
||||
# revoke by account key
|
||||
common revoke --cert-path "$root/conf/live/le.wtf/cert.pem"
|
||||
|
||||
@@ -12,20 +12,8 @@ store_flags="$store_flags --logs-dir $root/logs"
|
||||
export root store_flags
|
||||
|
||||
letsencrypt_test () {
|
||||
letsencrypt \
|
||||
--server "${SERVER:-http://localhost:4000/directory}" \
|
||||
--no-verify-ssl \
|
||||
--tls-sni-01-port 5001 \
|
||||
--http-01-port 5002 \
|
||||
--manual-test-mode \
|
||||
$store_flags \
|
||||
--text \
|
||||
--no-redirect \
|
||||
--agree-tos \
|
||||
--register-unsafely-without-email \
|
||||
letsencrypt_test_no_force_renew \
|
||||
--renew-by-default \
|
||||
--debug \
|
||||
-vvvvvvv \
|
||||
"$@"
|
||||
}
|
||||
|
||||
@@ -37,7 +25,7 @@ letsencrypt_test_no_force_renew () {
|
||||
--http-01-port 5002 \
|
||||
--manual-test-mode \
|
||||
$store_flags \
|
||||
--text \
|
||||
--non-interactive \
|
||||
--no-redirect \
|
||||
--agree-tos \
|
||||
--register-unsafely-without-email \
|
||||
|
||||
@@ -35,22 +35,16 @@ then
|
||||
#sudo cp /etc/httpd/sites-available/$PUBLIC_HOSTNAME.conf /etc/httpd/sites-enabled/
|
||||
fi
|
||||
|
||||
# run letsencrypt-apache2 via letsencrypt-auto
|
||||
# Run letsencrypt-apache2.
|
||||
cd letsencrypt
|
||||
|
||||
export SUDO=sudo
|
||||
if [ -f /etc/debian_version ] ; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
$SUDO bootstrap/_deb_common.sh
|
||||
elif [ -f /etc/redhat-release ] ; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
$SUDO bootstrap/_rpm_common.sh
|
||||
else
|
||||
echo "Dont have bootstrapping for this OS!"
|
||||
exit 1
|
||||
echo "Bootstrapping dependencies..."
|
||||
letsencrypt-auto-source/letsencrypt-auto --os-packages-only
|
||||
if [ $? -ne 0 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bootstrap/dev/venv.sh
|
||||
tools/venv.sh
|
||||
sudo venv/bin/letsencrypt -v --debug --text --agree-dev-preview --agree-tos \
|
||||
--renew-by-default --redirect --register-unsafely-without-email \
|
||||
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL
|
||||
|
||||
@@ -6,71 +6,15 @@ VENV_NAME="venv"
|
||||
LEA_PATH=./letsencrypt/
|
||||
VENV_PATH=${LEA_PATH/$VENV_NAME}
|
||||
VENV_BIN=${VENV_PATH}/bin
|
||||
BOOTSTRAP=${LEA_PATH}/bootstrap
|
||||
|
||||
SUDO=sudo
|
||||
|
||||
ExperimentalBootstrap() {
|
||||
# Arguments: Platform name, boostrap script name, SUDO command (iff needed)
|
||||
if [ "$2" != "" ] ; then
|
||||
echo "Bootstrapping dependencies for $1..."
|
||||
if [ "$3" != "" ] ; then
|
||||
"$3" "$BOOTSTRAP/$2"
|
||||
else
|
||||
"$BOOTSTRAP/$2"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# virtualenv call is not idempotent: it overwrites pip upgraded in
|
||||
# later steps, causing "ImportError: cannot import name unpack_url"
|
||||
if [ ! -f $BOOTSTRAP/debian.sh ] ; then
|
||||
echo "Cannot find the letsencrypt bootstrap scripts in $BOOTSTRAP"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -f /etc/debian_version ] ; then
|
||||
echo "Bootstrapping dependencies for Debian-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_deb_common.sh
|
||||
elif [ -f /etc/redhat-release ] ; then
|
||||
echo "Bootstrapping dependencies for RedHat-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_rpm_common.sh
|
||||
elif `grep -q openSUSE /etc/os-release` ; then
|
||||
echo "Bootstrapping dependencies for openSUSE-based OSes..."
|
||||
$SUDO $BOOTSTRAP/_suse_common.sh
|
||||
elif [ -f /etc/arch-release ] ; then
|
||||
if [ "$DEBUG" = 1 ] ; then
|
||||
echo "Bootstrapping dependencies for Archlinux..."
|
||||
$SUDO $BOOTSTRAP/archlinux.sh
|
||||
else
|
||||
echo "Please use pacman to install letsencrypt packages:"
|
||||
echo "# pacman -S letsencrypt letsencrypt-apache"
|
||||
echo
|
||||
echo "If you would like to use the virtualenv way, please run the script again with the"
|
||||
echo "--debug flag."
|
||||
exit 1
|
||||
fi
|
||||
elif [ -f /etc/manjaro-release ] ; then
|
||||
ExperimentalBootstrap "Manjaro Linux" manjaro.sh "$SUDO"
|
||||
elif [ -f /etc/gentoo-release ] ; then
|
||||
ExperimentalBootstrap "Gentoo" _gentoo_common.sh "$SUDO"
|
||||
elif uname | grep -iq FreeBSD ; then
|
||||
ExperimentalBootstrap "FreeBSD" freebsd.sh "$SUDO"
|
||||
elif uname | grep -iq Darwin ; then
|
||||
ExperimentalBootstrap "Mac OS X" mac.sh # homebrew doesn't normally run as root
|
||||
elif grep -iq "Amazon Linux" /etc/issue ; then
|
||||
ExperimentalBootstrap "Amazon Linux" _rpm_common.sh "$SUDO"
|
||||
else
|
||||
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
|
||||
echo
|
||||
echo "You will need to bootstrap, configure virtualenv, and run a pip install manually"
|
||||
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
|
||||
echo "for more info"
|
||||
fi
|
||||
echo "Bootstrapped!"
|
||||
"$LEA_PATH/letsencrypt-auto" --os-packages-only
|
||||
|
||||
cd letsencrypt
|
||||
./bootstrap/dev/venv.sh
|
||||
./tools/venv.sh
|
||||
PYVER=`python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
|
||||
|
||||
if [ $PYVER -eq 26 ] ; then
|
||||
|
||||
Reference in New Issue
Block a user