mirror of
https://github.com/certbot/certbot.git
synced 2026-06-04 22:33:00 -04:00
Merge remote-tracking branch 'letsencrypt/master'
This commit is contained in:
commit
5db2c75506
80 changed files with 1351 additions and 940 deletions
|
|
@ -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
|
||||
|
|
|
|||
22
CHANGES.rst
22
CHANGES.rst
|
|
@ -5,23 +5,7 @@ Please note:
|
|||
the change log will only get updated after first release - for now please use the
|
||||
`commit log <https://github.com/letsencrypt/letsencrypt/commits/master>`_.
|
||||
|
||||
To see the changes in a given release, inspect the github milestone for the
|
||||
release. For instance:
|
||||
|
||||
Release 0.1.0 (not released yet)
|
||||
--------------------------------
|
||||
|
||||
New Features:
|
||||
|
||||
* ...
|
||||
|
||||
Fixes:
|
||||
|
||||
* ...
|
||||
|
||||
Other changes:
|
||||
|
||||
* ...
|
||||
|
||||
Release 0.0.0 (not released yet)
|
||||
--------------------------------
|
||||
|
||||
Initial release.
|
||||
https://github.com/letsencrypt/letsencrypt/issues?utf8=%E2%9C%93&q=milestone%3A0.3.0
|
||||
|
|
|
|||
|
|
@ -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/* \
|
||||
|
|
|
|||
|
|
@ -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/* \
|
||||
|
|
|
|||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
|
@ -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|
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.4.0.dev0'
|
||||
version = '0.5.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
@ -157,7 +157,7 @@ Plugin-architecture
|
|||
Let's Encrypt has a plugin architecture to facilitate support for
|
||||
different webservers, other TLS servers, and operating systems.
|
||||
The interfaces available for plugins to implement are defined in
|
||||
`interfaces.py`_.
|
||||
`interfaces.py`_ and `plugins/common.py`_.
|
||||
|
||||
The most common kind of plugin is a "Configurator", which is likely to
|
||||
implement the `~letsencrypt.interfaces.IAuthenticator` and
|
||||
|
|
@ -168,6 +168,7 @@ There are also `~letsencrypt.interfaces.IDisplay` plugins,
|
|||
which implement bindings to alternative UI libraries.
|
||||
|
||||
.. _interfaces.py: https://github.com/letsencrypt/letsencrypt/blob/master/letsencrypt/interfaces.py
|
||||
.. _plugins/common.py: https://github.com/letsencrypt/letsencrypt/blob/master/letsencrypt/plugins/common.py#L34
|
||||
|
||||
|
||||
Authenticators
|
||||
|
|
@ -300,7 +301,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.
|
||||
|
|
|
|||
198
docs/using.rst
198
docs/using.rst
|
|
@ -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
|
||||
|
||||
|
|
@ -64,7 +49,7 @@ or for full help, type:
|
|||
|
||||
``letsencrypt-auto`` is the recommended method of running the Let's Encrypt
|
||||
client beta releases on systems that don't have a packaged version. Debian,
|
||||
Arch linux, FreeBSD, and OpenBSD now have native packages, so on those
|
||||
Arch Linux, Gentoo, FreeBSD, and OpenBSD now have native packages, so on those
|
||||
systems you can just install ``letsencrypt`` (and perhaps
|
||||
``letsencrypt-apache``). If you'd like to run the latest copy from Git, or
|
||||
run your own locally modified copy of the client, follow the instructions in
|
||||
|
|
@ -86,7 +71,9 @@ Plugin Auth Inst Notes
|
|||
=========== ==== ==== ===============================================================
|
||||
apache_ Y Y Automates obtaining and installing a cert with Apache 2.4 on
|
||||
Debian-based distributions with ``libaugeas0`` 1.0+.
|
||||
standalone_ Y N Uses a "standalone" webserver to obtain a cert.
|
||||
standalone_ Y N Uses a "standalone" webserver to obtain a cert. This is useful
|
||||
on systems with no webserver, or when direct integration with
|
||||
the local webserver is not supported or not desired.
|
||||
webroot_ Y N Obtains a cert by writing to the webroot directory of an
|
||||
already running webserver.
|
||||
manual_ Y N Helps you obtain a cert by giving you instructions to perform
|
||||
|
|
@ -106,36 +93,27 @@ This automates both obtaining *and* installing certs on an Apache
|
|||
webserver. To specify this plugin on the command line, simply include
|
||||
``--apache``.
|
||||
|
||||
Standalone
|
||||
----------
|
||||
|
||||
To obtain a cert using a "standalone" webserver, you can use the
|
||||
standalone plugin by including ``certonly`` and ``--standalone``
|
||||
on the command line. This plugin needs to bind to port 80 or 443 in
|
||||
order to perform domain validation, so you may need to stop your
|
||||
existing webserver. To control which port the plugin uses, include
|
||||
one of the options shown below on the command line.
|
||||
|
||||
* ``--standalone-supported-challenges http-01`` to use port 80
|
||||
* ``--standalone-supported-challenges tls-sni-01`` to use port 443
|
||||
|
||||
Webroot
|
||||
-------
|
||||
|
||||
If you're running a webserver that you don't want to stop to use
|
||||
standalone, you can use the webroot plugin to obtain a cert by
|
||||
including ``certonly`` and ``--webroot`` on the command line. In
|
||||
addition, you'll need to specify ``--webroot-path`` or ``-w`` with the root
|
||||
directory of the files served by your webserver. For example,
|
||||
``--webroot-path /var/www/html`` or
|
||||
``--webroot-path /usr/share/nginx/html`` are two common webroot paths.
|
||||
If you're running a local webserver for which you have the ability
|
||||
to modify the content being served, and you'd prefer not to stop the
|
||||
webserver during the certificate issuance process, you can use the webroot
|
||||
plugin to obtain a cert by including ``certonly`` and ``--webroot`` on
|
||||
the command line. In addition, you'll need to specify ``--webroot-path``
|
||||
or ``-w`` with the top-level directory ("web root") containing the files
|
||||
served by your webserver. For example, ``--webroot-path /var/www/html``
|
||||
or ``--webroot-path /usr/share/nginx/html`` are two common webroot paths.
|
||||
|
||||
If you're getting a certificate for many domains at once, each domain will use
|
||||
the most recent ``--webroot-path``. So for instance:
|
||||
If you're getting a certificate for many domains at once, the plugin
|
||||
needs to know where each domain's files are served from, which could
|
||||
potentially be a separate directory for each domain. When requested a
|
||||
certificate for multiple domains, each domain will use the most recently
|
||||
specified ``--webroot-path``. So, for instance,
|
||||
|
||||
``letsencrypt certonly --webroot -w /var/www/example/ -d www.example.com -d example.com -w /var/www/eg -d eg.is -d www.eg.is``
|
||||
|
||||
Would obtain a single certificate for all of those names, using the
|
||||
would obtain a single certificate for all of those names, using the
|
||||
``/var/www/example`` webroot directory for the first two, and
|
||||
``/var/www/eg`` for the second two.
|
||||
|
||||
|
|
@ -150,8 +128,28 @@ made to your web server would look like:
|
|||
66.133.109.36 - - [05/Jan/2016:20:11:24 -0500] "GET /.well-known/acme-challenge/HGr8U1IeTW4kY_Z6UIyaakzOkyQgPr_7ArlLgtZE8SX HTTP/1.1" 200 87 "-" "Mozilla/5.0 (compatible; Let's Encrypt validation server; +https://www.letsencrypt.org)"
|
||||
|
||||
Note that to use the webroot plugin, your server must be configured to serve
|
||||
files from hidden directories.
|
||||
files from hidden directories. If ``/.well-known`` is treated specially by
|
||||
your webserver configuration, you might need to modify the configuration
|
||||
to ensure that files inside ``/.well-known/ache-challenge`` are served by
|
||||
the webserver.
|
||||
|
||||
Standalone
|
||||
----------
|
||||
|
||||
To obtain a cert using a "standalone" webserver, you can use the
|
||||
standalone plugin by including ``certonly`` and ``--standalone``
|
||||
on the command line. This plugin needs to bind to port 80 or 443 in
|
||||
order to perform domain validation, so you may need to stop your
|
||||
existing webserver. To control which port the plugin uses, include
|
||||
one of the options shown below on the command line.
|
||||
|
||||
* ``--standalone-supported-challenges http-01`` to use port 80
|
||||
* ``--standalone-supported-challenges tls-sni-01`` to use port 443
|
||||
|
||||
The standalone plugin does not rely on any other server software running
|
||||
on the machine where you obtain the certificate. It must still be possible
|
||||
for that machine to accept inbound connections from the Internet on the
|
||||
specified port using each requested domain name.
|
||||
|
||||
Manual
|
||||
------
|
||||
|
|
@ -161,7 +159,8 @@ other than your target webserver or perform the steps for domain
|
|||
validation yourself, you can use the manual plugin. While hidden from
|
||||
the UI, you can use the plugin to obtain a cert by specifying
|
||||
``certonly`` and ``--manual`` on the command line. This requires you
|
||||
to copy and paste commands into another terminal session.
|
||||
to copy and paste commands into another terminal session, which may
|
||||
be on a different computer.
|
||||
|
||||
Nginx
|
||||
-----
|
||||
|
|
@ -172,7 +171,7 @@ is still experimental, however, and is not installed with
|
|||
letsencrypt-auto_. If installed, you can select this plugin on the
|
||||
command line by including ``--nginx``.
|
||||
|
||||
Third party plugins
|
||||
Third-party plugins
|
||||
-------------------
|
||||
|
||||
These plugins are listed at
|
||||
|
|
@ -182,24 +181,65 @@ interested, you can also :ref:`write your own plugin <dev-plugin>`.
|
|||
Renewal
|
||||
=======
|
||||
|
||||
.. note:: Let's Encrypt CA issues short lived certificates (90
|
||||
.. note:: Let's Encrypt CA issues short-lived certificates (90
|
||||
days). Make sure you renew the certificates at least once in 3
|
||||
months.
|
||||
|
||||
In order to renew certificates simply call the ``letsencrypt`` (or
|
||||
letsencrypt-auto_) again, and use the same values when prompted. You
|
||||
can automate it slightly by passing necessary flags on the CLI (see
|
||||
`--help all`), or even further using the :ref:`config-file`. The
|
||||
``--renew-by-default`` flag may be helpful for automating renewal. If
|
||||
you're sure that UI doesn't prompt for any details you can add the
|
||||
command to ``crontab`` (make it less than every 90 days to avoid
|
||||
problems, say every month).
|
||||
The ``letsencrypt`` client now supports a ``renew`` action to check
|
||||
all installed certificates for impending expiry and attempt to renew
|
||||
them. The simplest form is simply
|
||||
|
||||
``letsencrypt renew``
|
||||
|
||||
This will attempt to renew any previously-obtained certificates that
|
||||
expire in less than 30 days. The same plugin and options that were used
|
||||
at the time the certificate was originally issued will be used for the
|
||||
renewal attempt, unless you specify other plugins or options.
|
||||
|
||||
If you're sure that this command executes successfully without human
|
||||
intervention, you can add the command to ``crontab`` (since certificates
|
||||
are only renewed when they're determined to be near expiry, the command
|
||||
can run on a regular basis, like every week or every day); note that
|
||||
the current version provides detailed output describing either renewal
|
||||
success or failure.
|
||||
|
||||
The ``--force-renew`` flag may be helpful for automating renewal;
|
||||
it causes the expiration time of the certificate(s) to be ignored when
|
||||
considering renewal, and attempts to renew each and every installed
|
||||
certificate regardless of its age. (This form is not appropriate to run
|
||||
daily because each certificate will be renewed every day, which will
|
||||
quickly run into the certificate authority rate limit.)
|
||||
|
||||
Note that options provided to ``letsencrypt renew`` will apply to
|
||||
*every* certificate for which renewal is attempted; for example,
|
||||
``letsencrypt renew --rsa-key-size 4096`` would try to replace every
|
||||
near-expiry certificate with an equivalent certificate using a 4096-bit
|
||||
RSA public key. If a certificate is successfully renewed using
|
||||
specified options, those options will be saved and used for future
|
||||
renewals of that certificate.
|
||||
|
||||
|
||||
An alternative form that provides for more fine-grained control over the
|
||||
renewal process (while renewing specified certificates one at a time),
|
||||
is ``letsencrypt certonly`` with the complete set of subject domains of
|
||||
a specific certificate specified via `-d` flags, like
|
||||
|
||||
``letsencrypt certonly -d example.com -d www.example.com``
|
||||
|
||||
(All of the domains covered by the certificate must be specified in
|
||||
this case in order to renew and replace the old certificate rather
|
||||
than obtaining a new one; don't forget any `www.` domains! Specifying
|
||||
a subset of the domains creates a new, separate certificate containing
|
||||
only those domains, rather than replacing the original certificate.)
|
||||
The ``certonly`` form attempts to renew one individual certificate.
|
||||
|
||||
|
||||
Please note that the CA will send notification emails to the address
|
||||
you provide if you do not renew certificates that are about to expire.
|
||||
|
||||
Let's Encrypt is working hard on automating the renewal process. Until
|
||||
the tool is ready, we are sorry for the inconvenience!
|
||||
Let's Encrypt is working hard on improving the renewal process, and we
|
||||
apologize for any inconveniences you encounter in integrating these
|
||||
commands into your individual environment.
|
||||
|
||||
|
||||
.. _where-certs:
|
||||
|
|
@ -389,6 +429,52 @@ If you don't want to use the Apache plugin, you can omit the
|
|||
|
||||
Packages for Debian Jessie are coming in the next few weeks.
|
||||
|
||||
**Gentoo**
|
||||
|
||||
The official Let's Encrypt client is available in Gentoo Portage. If you
|
||||
want to use the Apache plugin, it has to be installed separately:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
emerge -av app-crypt/letsencrypt
|
||||
emerge -av app-crypt/letsencrypt-apache
|
||||
|
||||
Currently, only the Apache plugin is included in Portage. However, if you
|
||||
want the nginx plugin, you can use Layman to add the mrueg overlay which
|
||||
does include the nginx plugin package:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
emerge -av app-portage/layman
|
||||
layman -S
|
||||
layman -a mrueg
|
||||
emerge -av app-crypt/letsencrypt-nginx
|
||||
|
||||
When using the Apache plugin, you will run into a "cannot find a cert or key
|
||||
directive" error if you're sporting the default Gentoo ``httpd.conf``.
|
||||
You can fix this by commenting out two lines in ``/etc/apache2/httpd.conf``
|
||||
as follows:
|
||||
|
||||
Change
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
<IfDefine SSL>
|
||||
LoadModule ssl_module modules/mod_ssl.so
|
||||
</IfDefine>
|
||||
|
||||
to
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
#<IfDefine SSL>
|
||||
LoadModule ssl_module modules/mod_ssl.so
|
||||
#</IfDefine>
|
||||
|
||||
For the time being, this is the only way for the Apache plugin to recognise
|
||||
the appropriate directives when installing the certificate.
|
||||
Note: this change is not required for the other plugins.
|
||||
|
||||
**Other Operating Systems**
|
||||
|
||||
OS packaging is an ongoing effort. If you'd like to package
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@ include LICENSE.txt
|
|||
include README.rst
|
||||
recursive-include docs *
|
||||
recursive-include letsencrypt_apache/tests/testdata *
|
||||
include letsencrypt_apache/centos-options-ssl-apache.conf
|
||||
include letsencrypt_apache/options-ssl-apache.conf
|
||||
recursive-include letsencrypt_apache/augeas_lens *.aug
|
||||
|
|
|
|||
|
|
@ -106,7 +106,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
add("handle-sites", default=constants.os_constant("handle_sites"),
|
||||
help="Let installer handle enabling sites for you." +
|
||||
"(Only Ubuntu/Debian currently)")
|
||||
le_util.add_deprecated_argument(add, "init-script", 1)
|
||||
le_util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
|
||||
le_util.add_deprecated_argument(
|
||||
add, argument_name="init-script", nargs=1)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Initialize an Apache Configurator.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.4.0.dev0'
|
||||
version = '0.5.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
|
|||
202
letsencrypt-auto
202
letsencrypt-auto
|
|
@ -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 "$@"
|
||||
1
letsencrypt-auto
Symbolic link
1
letsencrypt-auto
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
letsencrypt-auto-source/letsencrypt-auto
|
||||
|
|
@ -19,7 +19,7 @@ XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
|
|||
VENV_NAME="letsencrypt"
|
||||
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
|
||||
VENV_BIN=${VENV_PATH}/bin
|
||||
LE_AUTO_VERSION="0.4.0.dev0"
|
||||
LE_AUTO_VERSION="0.5.0.dev0"
|
||||
|
||||
# This script takes the same arguments as the main letsencrypt program, but it
|
||||
# additionally responds to --verbose (more output) and --debug (allow support
|
||||
|
|
@ -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
|
||||
|
|
@ -638,17 +638,17 @@ zope.event==4.1.0
|
|||
# sha256: sJyMHUezUxxADgGVaX8UFKYyId5u9HhZik8UYPfZo5I
|
||||
zope.interface==4.1.3
|
||||
|
||||
# sha256: QMIkIvGF3mcJhGLAKRX7n5EVIPjOrfLtklN6ePjbJes
|
||||
# sha256: fNFWiij6VxfG5o7u3oNbtrYKQ4q9vhzOLATfxNlozvQ
|
||||
acme==0.3.0
|
||||
# sha256: ilvjjTWOS86xchl0WBZ0YOAw_0rmqdnjNsxb1hq2RD8
|
||||
# sha256: T37KMj0TnsuvHIzCCmoww2fpfpOBTj7cd4NAqucXcpw
|
||||
acme==0.4.0
|
||||
|
||||
# sha256: qdnzpoRf_44QXKoktNoAKs2RBAxUta2Sr6GS0t_tAKo
|
||||
# sha256: ELWJaHNvBZIqVPJYkla8yXLtXIuamqAf6f_VAFv16Uk
|
||||
letsencrypt==0.3.0
|
||||
# sha256: 33BQiANlNLGqGpirTfdCEElTF9YbpaKiYpTbK4zeGD8
|
||||
# sha256: lwsV1OdEzzlMeb08C_PRxaCXZ2vOk_1AI2755rZHmPM
|
||||
letsencrypt==0.4.0
|
||||
|
||||
# sha256: EypLpEw3-Tr8unw4aSFsHXgRiU8ZYLrJKOJohP2tC9M
|
||||
# sha256: HYvP13GzA-DDJYwlfOoaraJO0zuYO48TCSAyTUAGCqA
|
||||
letsencrypt-apache==0.3.0
|
||||
# sha256: D3YDaVFjLsMSEfjI5B5D5tn5FeWUtNHYXCObw3ih2tg
|
||||
# sha256: VTgvsePYGRmI4IOSAnxoYFHd8KciD73bxIuIHtbVFd8
|
||||
letsencrypt-apache==0.4.0
|
||||
|
||||
# sha256: uDndLZwRfHAUMMFJlWkYpCOphjtIsJyQ4wpgE-fS9E8
|
||||
# sha256: j4MIDaoknQNsvM-4rlzG_wB7iNbZN1ITca-r57Gbrbw
|
||||
|
|
@ -1632,6 +1632,7 @@ UNLIKELY_EOF
|
|||
echo "$PEEP_OUT"
|
||||
exit 1
|
||||
fi
|
||||
echo "Installation succeeded."
|
||||
fi
|
||||
echo "Requesting root privileges to run letsencrypt..."
|
||||
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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
|
||||
|
|
@ -209,6 +209,7 @@ UNLIKELY_EOF
|
|||
echo "$PEEP_OUT"
|
||||
exit 1
|
||||
fi
|
||||
echo "Installation succeeded."
|
||||
fi
|
||||
echo "Requesting root privileges to run letsencrypt..."
|
||||
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ BootstrapGentooCommon() {
|
|||
|
||||
case "$PACKAGE_MANAGER" in
|
||||
(paludis)
|
||||
"$SUDO" cave resolve --keep-targets if-possible $PACKAGES -x
|
||||
"$SUDO" cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
|
||||
;;
|
||||
(pkgcore)
|
||||
"$SUDO" pmerge --noreplace $PACKAGES
|
||||
"$SUDO" pmerge --noreplace --oneshot $PACKAGES
|
||||
;;
|
||||
(portage|*)
|
||||
"$SUDO" emerge --noreplace $PACKAGES
|
||||
"$SUDO" emerge --noreplace --oneshot $PACKAGES
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
|
|
|||
|
|
@ -201,17 +201,17 @@ zope.event==4.1.0
|
|||
# sha256: sJyMHUezUxxADgGVaX8UFKYyId5u9HhZik8UYPfZo5I
|
||||
zope.interface==4.1.3
|
||||
|
||||
# sha256: QMIkIvGF3mcJhGLAKRX7n5EVIPjOrfLtklN6ePjbJes
|
||||
# sha256: fNFWiij6VxfG5o7u3oNbtrYKQ4q9vhzOLATfxNlozvQ
|
||||
acme==0.3.0
|
||||
# sha256: ilvjjTWOS86xchl0WBZ0YOAw_0rmqdnjNsxb1hq2RD8
|
||||
# sha256: T37KMj0TnsuvHIzCCmoww2fpfpOBTj7cd4NAqucXcpw
|
||||
acme==0.4.0
|
||||
|
||||
# sha256: qdnzpoRf_44QXKoktNoAKs2RBAxUta2Sr6GS0t_tAKo
|
||||
# sha256: ELWJaHNvBZIqVPJYkla8yXLtXIuamqAf6f_VAFv16Uk
|
||||
letsencrypt==0.3.0
|
||||
# sha256: 33BQiANlNLGqGpirTfdCEElTF9YbpaKiYpTbK4zeGD8
|
||||
# sha256: lwsV1OdEzzlMeb08C_PRxaCXZ2vOk_1AI2755rZHmPM
|
||||
letsencrypt==0.4.0
|
||||
|
||||
# sha256: EypLpEw3-Tr8unw4aSFsHXgRiU8ZYLrJKOJohP2tC9M
|
||||
# sha256: HYvP13GzA-DDJYwlfOoaraJO0zuYO48TCSAyTUAGCqA
|
||||
letsencrypt-apache==0.3.0
|
||||
# sha256: D3YDaVFjLsMSEfjI5B5D5tn5FeWUtNHYXCObw3ih2tg
|
||||
# sha256: VTgvsePYGRmI4IOSAnxoYFHd8KciD73bxIuIHtbVFd8
|
||||
letsencrypt-apache==0.4.0
|
||||
|
||||
# sha256: uDndLZwRfHAUMMFJlWkYpCOphjtIsJyQ4wpgE-fS9E8
|
||||
# sha256: j4MIDaoknQNsvM-4rlzG_wB7iNbZN1ITca-r57Gbrbw
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.4.0.dev0'
|
||||
version = '0.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'letsencrypt=={0}'.format(version),
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.4.0.dev0'
|
||||
version = '0.5.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Let's Encrypt client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '0.4.0.dev0'
|
||||
__version__ = '0.5.0.dev0'
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ from __future__ import print_function
|
|||
# (TODO: split this file into main.py and cli.py)
|
||||
import argparse
|
||||
import atexit
|
||||
import copy
|
||||
import functools
|
||||
import glob
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
|
|
@ -44,6 +46,18 @@ from letsencrypt.plugins import disco as plugins_disco
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Global, to save us from a lot of argument passing within the scope of this module
|
||||
_parser = None
|
||||
|
||||
# These are the items which get pulled out of a renewal configuration
|
||||
# file's renewalparams and actually used in the client configuration
|
||||
# during the renewal process. We have to record their types here because
|
||||
# the renewal configuration process loses this information.
|
||||
STR_CONFIG_ITEMS = ["config_dir", "logs_dir", "work_dir", "user_agent",
|
||||
"server", "account", "authenticator", "installer",
|
||||
"standalone_supported_challenges"]
|
||||
INT_CONFIG_ITEMS = ["rsa_key_size", "tls_sni_01_port", "http01_port"]
|
||||
|
||||
# For help strings, figure out how the user ran us.
|
||||
# When invoked from letsencrypt-auto, sys.argv[0] is something like:
|
||||
# "/home/user/.local/share/letsencrypt/bin/letsencrypt"
|
||||
|
|
@ -69,6 +83,7 @@ the cert. Major SUBCOMMANDS are:
|
|||
(default) run Obtain & install a cert in your current webserver
|
||||
certonly Obtain cert, but do not install it (aka "auth")
|
||||
install Install a previously obtained cert in a server
|
||||
renew Renew previously obtained certs that are near expiry
|
||||
revoke Revoke a previously obtained certificate
|
||||
rollback Rollback server configuration changes made during install
|
||||
config_changes Show changes made to server config during installation
|
||||
|
|
@ -208,15 +223,12 @@ def _find_duplicative_certs(config, domains):
|
|||
# Verify the directory is there
|
||||
le_util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid())
|
||||
|
||||
for renewal_file in os.listdir(configs_dir):
|
||||
if not renewal_file.endswith(".conf"):
|
||||
continue
|
||||
for renewal_file in _renewal_conf_files(cli_config):
|
||||
try:
|
||||
full_path = os.path.join(configs_dir, renewal_file)
|
||||
candidate_lineage = storage.RenewableCert(full_path, cli_config)
|
||||
candidate_lineage = storage.RenewableCert(renewal_file, cli_config)
|
||||
except (errors.CertStorageError, IOError):
|
||||
logger.warning("Renewal configuration file %s is broken. "
|
||||
"Skipping.", full_path)
|
||||
logger.warning("Renewal conf file %s is broken. Skipping.", renewal_file)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
continue
|
||||
# TODO: Handle these differently depending on whether they are
|
||||
# expired or still valid?
|
||||
|
|
@ -235,7 +247,10 @@ def _find_duplicative_certs(config, domains):
|
|||
|
||||
|
||||
def _treat_as_renewal(config, domains):
|
||||
"""Determine whether there are duplicated names and how to handle them.
|
||||
"""Determine whether there are duplicated names and how to handle
|
||||
them (renew, reinstall, newcert, or raising an error to stop
|
||||
the client run if the user chooses to cancel the operation when
|
||||
prompted).
|
||||
|
||||
:returns: Two-element tuple containing desired new-certificate behavior as
|
||||
a string token ("reinstall", "renew", or "newcert"), plus either
|
||||
|
|
@ -262,8 +277,24 @@ def _treat_as_renewal(config, domains):
|
|||
elif subset_names_cert is not None:
|
||||
return _handle_subset_cert_request(config, domains, subset_names_cert)
|
||||
|
||||
|
||||
def _should_renew(config, lineage):
|
||||
"Return true if any of the circumstances for automatic renewal apply."
|
||||
if config.renew_by_default:
|
||||
logger.info("Auto-renewal forced with --force-renewal...")
|
||||
return True
|
||||
if lineage.should_autorenew(interactive=True):
|
||||
logger.info("Cert is due for renewal, auto-renewing...")
|
||||
return True
|
||||
if config.dry_run:
|
||||
logger.info("Cert not due for renewal, but simulating renewal for dry run")
|
||||
return True
|
||||
logger.info("Cert not yet due for renewal")
|
||||
return False
|
||||
|
||||
|
||||
def _handle_identical_cert_request(config, cert):
|
||||
"""Figure out what to do if a cert has the same names as a perviously obtained one
|
||||
"""Figure out what to do if a cert has the same names as a previously obtained one
|
||||
|
||||
:param storage.RenewableCert cert:
|
||||
|
||||
|
|
@ -271,17 +302,12 @@ def _handle_identical_cert_request(config, cert):
|
|||
:rtype: tuple
|
||||
|
||||
"""
|
||||
if config.renew_by_default:
|
||||
logger.info("Auto-renewal forced with --renew-by-default...")
|
||||
return "renew", cert
|
||||
if cert.should_autorenew(interactive=True):
|
||||
logger.info("Cert is due for renewal, auto-renewing...")
|
||||
if _should_renew(config, cert):
|
||||
return "renew", cert
|
||||
if config.reinstall:
|
||||
# Set with --reinstall, force an identical certificate to be
|
||||
# reinstalled without further prompting.
|
||||
return "reinstall", cert
|
||||
|
||||
question = (
|
||||
"You have an existing certificate that contains exactly the same "
|
||||
"domains you requested and isn't close to expiry."
|
||||
|
|
@ -311,6 +337,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
|
||||
|
||||
|
|
@ -379,23 +406,28 @@ def _report_new_cert(cert_path, fullchain_path):
|
|||
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
|
||||
|
||||
|
||||
def _suggest_donation_if_appropriate(config):
|
||||
def _suggest_donation_if_appropriate(config, action):
|
||||
"""Potentially suggest a donation to support Let's Encrypt."""
|
||||
if not config.staging: # --dry-run implies --staging
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
msg = ("If you like Let's Encrypt, please consider supporting our work by:\n\n"
|
||||
"Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n"
|
||||
"Donating to EFF: https://eff.org/donate-le\n\n")
|
||||
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
|
||||
|
||||
|
||||
def _report_successful_dry_run():
|
||||
if config.staging or config.verb == "renew":
|
||||
# --dry-run implies --staging
|
||||
return
|
||||
if action not in ["renew", "newcert"]:
|
||||
return
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter_util.add_message("The dry run was successful.",
|
||||
reporter_util.HIGH_PRIORITY, on_crash=False)
|
||||
msg = ("If you like Let's Encrypt, please consider supporting our work by:\n\n"
|
||||
"Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n"
|
||||
"Donating to EFF: https://eff.org/donate-le\n\n")
|
||||
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
|
||||
|
||||
|
||||
def _auth_from_domains(le_client, config, domains):
|
||||
def _report_successful_dry_run(config):
|
||||
reporter_util = zope.component.getUtility(interfaces.IReporter)
|
||||
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):
|
||||
"""Authenticate and enroll certificate."""
|
||||
# Note: This can raise errors... caught above us though. This is now
|
||||
# a three-way case: reinstall (which results in a no-op here because
|
||||
|
|
@ -404,10 +436,14 @@ def _auth_from_domains(le_client, config, domains):
|
|||
# (which results in treating the request as a renewal), or newcert
|
||||
# (which results in treating the request as a new certificate request).
|
||||
|
||||
action, lineage = _treat_as_renewal(config, domains)
|
||||
if config.dry_run and action == "reinstall":
|
||||
logger.info(
|
||||
"Cert not due for renewal, but simulating renewal for dry run")
|
||||
# If lineage is specified, use that one instead of looking around for
|
||||
# a matching one.
|
||||
if lineage is None:
|
||||
# This will find a relevant matching lineage that exists
|
||||
action, lineage = _treat_as_renewal(config, domains)
|
||||
else:
|
||||
# Renewal, where we already know the specific lineage we're
|
||||
# interested in
|
||||
action = "renew"
|
||||
|
||||
if action == "reinstall":
|
||||
|
|
@ -440,7 +476,7 @@ def _auth_from_domains(le_client, config, domains):
|
|||
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
|
||||
|
|
@ -457,7 +493,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:
|
||||
|
|
@ -516,6 +552,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
|
||||
|
|
@ -544,6 +581,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
|
||||
|
|
@ -566,7 +604,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:
|
||||
|
|
@ -635,30 +673,28 @@ def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals
|
|||
else:
|
||||
display_ops.success_renewal(domains, action)
|
||||
|
||||
_suggest_donation_if_appropriate(config)
|
||||
_suggest_donation_if_appropriate(config, action)
|
||||
|
||||
|
||||
def obtain_cert(config, plugins):
|
||||
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"
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
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)
|
||||
|
||||
action = "newcert"
|
||||
# This is a special case; cert and chain are simply saved
|
||||
if config.csr is not None:
|
||||
certr, chain = le_client.obtain_certificate_from_csr(le_util.CSR(
|
||||
file=config.csr[0], data=config.csr[1], form="der"))
|
||||
assert lineage is None, "Did not expect a CSR with a RenewableCert"
|
||||
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)
|
||||
|
|
@ -668,11 +704,23 @@ def obtain_cert(config, plugins):
|
|||
_report_new_cert(cert_path, cert_fullchain)
|
||||
else:
|
||||
domains = _find_domains(config, installer)
|
||||
_auth_from_domains(le_client, config, domains)
|
||||
_, action = _auth_from_domains(le_client, config, domains, lineage)
|
||||
|
||||
if config.dry_run:
|
||||
_report_successful_dry_run()
|
||||
_suggest_donation_if_appropriate(config)
|
||||
_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)
|
||||
|
||||
|
||||
def install(config, plugins):
|
||||
|
|
@ -695,6 +743,295 @@ def install(config, plugins):
|
|||
le_client.enhance_config(domains, config)
|
||||
|
||||
|
||||
def _set_by_cli(var):
|
||||
"""
|
||||
Return True if a particular config variable has been set by the user
|
||||
(CLI or config file) including if the user explicitly set it to the
|
||||
default. Returns False if the variable was assigned a default value.
|
||||
"""
|
||||
detector = _set_by_cli.detector
|
||||
if detector is None:
|
||||
# Setup on first run: `detector` is a weird version of config in which
|
||||
# the default value of every attribute is wrangled to be boolean-false
|
||||
plugins = plugins_disco.PluginsRegistry.find_all()
|
||||
# reconstructed_args == sys.argv[1:], or whatever was passed to main()
|
||||
reconstructed_args = _parser.args + [_parser.verb]
|
||||
detector = _set_by_cli.detector = prepare_and_parse_args(
|
||||
plugins, reconstructed_args, detect_defaults=True)
|
||||
# propagate plugin requests: eg --standalone modifies config.authenticator
|
||||
auth, inst = cli_plugin_requests(detector)
|
||||
detector.authenticator = auth if auth else ""
|
||||
detector.installer = inst if inst else ""
|
||||
logger.debug("Default Detector is %r", detector)
|
||||
|
||||
try:
|
||||
# Is detector.var something that isn't false?
|
||||
change_detected = getattr(detector, var)
|
||||
except AttributeError:
|
||||
logger.warning("Missing default analysis for %r", var)
|
||||
return False
|
||||
|
||||
if change_detected:
|
||||
return True
|
||||
# Special case: we actually want account to be set to "" if the server
|
||||
# the account was on has changed
|
||||
elif var == "account" and (detector.server or detector.dry_run or detector.staging):
|
||||
return True
|
||||
# Special case: vars like --no-redirect that get set True -> False
|
||||
# default to None; False means they were set
|
||||
elif var in detector.store_false_vars and change_detected is not None:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
# static housekeeping var
|
||||
_set_by_cli.detector = None
|
||||
|
||||
def _restore_required_config_elements(config, renewalparams):
|
||||
"""Sets non-plugin specific values in config from renewalparams
|
||||
|
||||
:param configuration.NamespaceConfig config: configuration for the
|
||||
current lineage
|
||||
:param configobj.Section renewalparams: parameters from the renewal
|
||||
configuration file that defines this lineage
|
||||
|
||||
"""
|
||||
# string-valued items to add if they're present
|
||||
for config_item in STR_CONFIG_ITEMS:
|
||||
if config_item in renewalparams and not _set_by_cli(config_item):
|
||||
value = renewalparams[config_item]
|
||||
# Unfortunately, we've lost type information from ConfigObj,
|
||||
# so we don't know if the original was NoneType or str!
|
||||
if value == "None":
|
||||
value = None
|
||||
setattr(config.namespace, config_item, value)
|
||||
# int-valued items to add if they're present
|
||||
for config_item in INT_CONFIG_ITEMS:
|
||||
if config_item in renewalparams and not _set_by_cli(config_item):
|
||||
try:
|
||||
value = int(renewalparams[config_item])
|
||||
setattr(config.namespace, config_item, value)
|
||||
except ValueError:
|
||||
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
|
||||
|
||||
:param configuration.NamespaceConfig config: configuration for the
|
||||
current lineage
|
||||
:param configobj.Section renewalparams: Parameters from the renewal
|
||||
configuration file that defines this lineage
|
||||
|
||||
"""
|
||||
# Now use parser to get plugin-prefixed items with correct types
|
||||
# XXX: the current approach of extracting only prefixed items
|
||||
# related to the actually-used installer and authenticator
|
||||
# works as long as plugins don't need to read plugin-specific
|
||||
# variables set by someone else (e.g., assuming Apache
|
||||
# configurator doesn't need to read webroot_ variables).
|
||||
# Note: if a parameter that used to be defined in the parser is no
|
||||
# longer defined, stored copies of that parameter will be
|
||||
# deserialized as strings by this logic even if they were
|
||||
# originally meant to be some other type.
|
||||
if renewalparams["authenticator"] == "webroot":
|
||||
_restore_webroot_config(config, renewalparams)
|
||||
plugin_prefixes = []
|
||||
else:
|
||||
plugin_prefixes = [renewalparams["authenticator"]]
|
||||
|
||||
if renewalparams.get("installer", None) is not None:
|
||||
plugin_prefixes.append(renewalparams["installer"])
|
||||
for plugin_prefix in set(plugin_prefixes):
|
||||
for config_item, config_value in renewalparams.iteritems():
|
||||
if config_item.startswith(plugin_prefix + "_") and not _set_by_cli(config_item):
|
||||
# Values None, True, and False need to be treated specially,
|
||||
# As they don't get parsed correctly based on type
|
||||
if config_value in ("None", "True", "False"):
|
||||
# bool("False") == True
|
||||
# pylint: disable=eval-used
|
||||
setattr(config.namespace, config_item, eval(config_value))
|
||||
continue
|
||||
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))
|
||||
break
|
||||
else:
|
||||
setattr(config.namespace, config_item, str(config_value))
|
||||
|
||||
def _restore_webroot_config(config, renewalparams):
|
||||
"""
|
||||
webroot_map is, uniquely, a dict, and the general-purpose configuration
|
||||
restoring logic is not able to correctly parse it from the serialized
|
||||
form.
|
||||
"""
|
||||
if "webroot_map" in renewalparams:
|
||||
# if the user does anything that would create a new webroot map on the
|
||||
# CLI, don't use the old one
|
||||
if not (_set_by_cli("webroot_map") or _set_by_cli("webroot_path")):
|
||||
setattr(config.namespace, "webroot_map", renewalparams["webroot_map"])
|
||||
elif "webroot_path" in renewalparams:
|
||||
logger.info("Ancient renewal conf file without webroot-map, restoring webroot-path")
|
||||
setattr(config.namespace, "webroot_path", renewalparams["webroot_path"])
|
||||
|
||||
|
||||
def _reconstitute(config, full_path):
|
||||
"""Try to instantiate a RenewableCert, updating config with relevant items.
|
||||
|
||||
This is specifically for use in renewal and enforces several checks
|
||||
and policies to ensure that we can try to proceed with the renwal
|
||||
request. The config argument is modified by including relevant options
|
||||
read from the renewal configuration file.
|
||||
|
||||
:param configuration.NamespaceConfig config: configuration for the
|
||||
current lineage
|
||||
:param str full_path: Absolute path to the configuration file that
|
||||
defines this lineage
|
||||
|
||||
:returns: the RenewableCert object or None if a fatal error occurred
|
||||
:rtype: `storage.RenewableCert` or NoneType
|
||||
|
||||
"""
|
||||
try:
|
||||
renewal_candidate = storage.RenewableCert(
|
||||
full_path, configuration.RenewerConfiguration(config))
|
||||
except (errors.CertStorageError, IOError):
|
||||
logger.warning("Renewal configuration file %s is broken. Skipping.", full_path)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
return None
|
||||
if "renewalparams" not in renewal_candidate.configuration:
|
||||
logger.warning("Renewal configuration file %s lacks "
|
||||
"renewalparams. Skipping.", full_path)
|
||||
return None
|
||||
renewalparams = renewal_candidate.configuration["renewalparams"]
|
||||
if "authenticator" not in renewalparams:
|
||||
logger.warning("Renewal configuration file %s does not specify "
|
||||
"an authenticator. Skipping.", full_path)
|
||||
return None
|
||||
# Now restore specific values along with their data types, if
|
||||
# those elements are present.
|
||||
try:
|
||||
_restore_required_config_elements(config, renewalparams)
|
||||
_restore_plugin_configs(config, renewalparams)
|
||||
except (ValueError, errors.Error) as error:
|
||||
logger.warning(
|
||||
"An error occured while parsing %s. The error was %s. "
|
||||
"Skipping the file.", full_path, error.message)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
return None
|
||||
|
||||
try:
|
||||
for d in renewal_candidate.names():
|
||||
_process_domain(config, d)
|
||||
except errors.ConfigurationError as error:
|
||||
logger.warning("Renewal configuration file %s references a cert "
|
||||
"that contains an invalid domain name. The problem "
|
||||
"was: %s. Skipping.", full_path, error)
|
||||
return None
|
||||
|
||||
return renewal_candidate
|
||||
|
||||
def _renewal_conf_files(config):
|
||||
"""Return /path/to/*.conf in the renewal conf directory"""
|
||||
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 != []:
|
||||
raise errors.Error("Currently, the renew verb is only capable of "
|
||||
"renewing all installed certificates that are due "
|
||||
"to be renewed; individual domains cannot be "
|
||||
"specified with this action. If you would like to "
|
||||
"renew specific certificates, use the certonly "
|
||||
"command. The renew verb may provide other options "
|
||||
"for selecting certificates to renew in the future.")
|
||||
if config.csr is not None:
|
||||
raise errors.Error("Currently, the renew verb cannot be used when "
|
||||
"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)
|
||||
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
|
||||
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 None:
|
||||
parse_failures.append(renewal_file)
|
||||
else:
|
||||
# XXX: ensure that each call here replaces the previous one
|
||||
zope.component.provideUtility(lineage_config)
|
||||
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
|
||||
"""Revoke a previously obtained certificate."""
|
||||
# For user-agent construction
|
||||
|
|
@ -813,14 +1150,14 @@ class HelpfulArgumentParser(object):
|
|||
# Maps verbs/subcommands to the functions that implement them
|
||||
VERBS = {"auth": obtain_cert, "certonly": obtain_cert,
|
||||
"config_changes": config_changes, "everything": run,
|
||||
"install": install, "plugins": plugins_cmd,
|
||||
"install": install, "plugins": plugins_cmd, "renew": renew,
|
||||
"revoke": revoke, "rollback": rollback, "run": run}
|
||||
|
||||
# List of topics for which additional help can be provided
|
||||
HELP_TOPICS = ["all", "security",
|
||||
"paths", "automation", "testing"] + VERBS.keys()
|
||||
|
||||
def __init__(self, args, plugins):
|
||||
def __init__(self, args, plugins, detect_defaults=False):
|
||||
plugin_names = [name for name, _p in plugins.iteritems()]
|
||||
self.help_topics = self.HELP_TOPICS + plugin_names + [None]
|
||||
usage, short_usage = usage_strings(plugins)
|
||||
|
|
@ -834,6 +1171,14 @@ class HelpfulArgumentParser(object):
|
|||
self.parser._add_config_file_help = False # pylint: disable=protected-access
|
||||
self.silent_parser = SilentParser(self.parser)
|
||||
|
||||
# This setting attempts to force all default values to things that are
|
||||
# pythonically false; it is used to detect when values have been
|
||||
# explicitly set by the user, including when they are set to their
|
||||
# normal default value
|
||||
self.detect_defaults = detect_defaults
|
||||
if detect_defaults:
|
||||
self.store_false_vars = {} # vars that use "store_false"
|
||||
|
||||
self.args = args
|
||||
self.determine_verb()
|
||||
help1 = self.prescan_for_flag("-h", self.help_topics)
|
||||
|
|
@ -845,7 +1190,7 @@ class HelpfulArgumentParser(object):
|
|||
print(usage)
|
||||
sys.exit(0)
|
||||
self.visible_topics = self.determine_help_topics(self.help_arg)
|
||||
self.groups = {} # elements are added by .add_group()
|
||||
self.groups = {} # elements are added by .add_group()
|
||||
|
||||
def parse_args(self):
|
||||
"""Parses command line arguments and returns the result.
|
||||
|
|
@ -867,23 +1212,76 @@ class HelpfulArgumentParser(object):
|
|||
parsed_args.domains.append(domain)
|
||||
|
||||
if parsed_args.staging or parsed_args.dry_run:
|
||||
if (parsed_args.server not in
|
||||
(flag_default("server"), constants.STAGING_URI)):
|
||||
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
|
||||
conflicts = ["--staging"] if parsed_args.staging else []
|
||||
conflicts += ["--dry-run"] if parsed_args.dry_run else []
|
||||
raise errors.Error("--server value conflicts with {0}".format(
|
||||
" and ".join(conflicts)))
|
||||
if not self.detect_defaults:
|
||||
raise errors.Error("--server value conflicts with {0}".format(
|
||||
" and ".join(conflicts)))
|
||||
|
||||
parsed_args.server = constants.STAGING_URI
|
||||
|
||||
if parsed_args.dry_run:
|
||||
if self.verb != "certonly":
|
||||
if self.verb not in ["certonly", "renew"]:
|
||||
raise errors.Error("--dry-run currently only works with the "
|
||||
"'certonly' subcommand")
|
||||
"'certonly' or 'renew' subcommands (%r)" % self.verb)
|
||||
parsed_args.break_my_certs = parsed_args.staging = True
|
||||
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
|
||||
# The user has a prod account, but might not have a staging
|
||||
# one; we don't want to start trying to perform interactive registration
|
||||
parsed_args.agree_tos = True
|
||||
parsed_args.register_unsafely_without_email = True
|
||||
|
||||
if parsed_args.csr:
|
||||
self.handle_csr(parsed_args)
|
||||
|
||||
if self.detect_defaults: # plumbing
|
||||
parsed_args.store_false_vars = self.store_false_vars
|
||||
|
||||
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.
|
||||
|
||||
|
|
@ -932,10 +1330,16 @@ class HelpfulArgumentParser(object):
|
|||
def add(self, topic, *args, **kwargs):
|
||||
"""Add a new command line argument.
|
||||
|
||||
@topic is required, to indicate which part of the help will document
|
||||
it, but can be None for `always documented'.
|
||||
:param str: help topic this should be listed under, can be None for
|
||||
"always documented"
|
||||
:param list *args: the names of this argument flag
|
||||
:param dict **kwargs: various argparse settings for this argument
|
||||
|
||||
"""
|
||||
|
||||
if self.detect_defaults:
|
||||
kwargs = self.modify_arg_for_default_detection(self, *args, **kwargs)
|
||||
|
||||
if self.visible_topics[topic]:
|
||||
if topic in self.groups:
|
||||
group = self.groups[topic]
|
||||
|
|
@ -946,6 +1350,39 @@ class HelpfulArgumentParser(object):
|
|||
kwargs["help"] = argparse.SUPPRESS
|
||||
self.parser.add_argument(*args, **kwargs)
|
||||
|
||||
|
||||
def modify_arg_for_default_detection(self, *args, **kwargs):
|
||||
"""
|
||||
Adding an arg, but ensure that it has a default that evaluates to false,
|
||||
so that _set_by_cli can tell if it was set. Only called if detect_defaults==True.
|
||||
|
||||
:param list *args: the names of this argument flag
|
||||
:param dict **kwargs: various argparse settings for this argument
|
||||
|
||||
:returns: a modified versions of kwargs
|
||||
"""
|
||||
# argument either doesn't have a default, or the default doesn't
|
||||
# isn't Pythonically false
|
||||
if kwargs.get("default", True):
|
||||
arg_type = kwargs.get("type", None)
|
||||
if arg_type == int or kwargs.get("action", "") == "count":
|
||||
kwargs["default"] = 0
|
||||
elif arg_type == read_file or "-c" in args:
|
||||
kwargs["default"] = ""
|
||||
kwargs["type"] = str
|
||||
else:
|
||||
kwargs["default"] = ""
|
||||
# This doesn't matter at present (none of the store_false args
|
||||
# are renewal-relevant), but implement it for future sanity:
|
||||
# detect the setting of args whose presence causes True -> False
|
||||
if kwargs.get("action", "") == "store_false":
|
||||
kwargs["default"] = None
|
||||
for var in args:
|
||||
self.store_false_vars[var] = True
|
||||
|
||||
return kwargs
|
||||
|
||||
|
||||
def add_deprecated_argument(self, argument_name, num_args):
|
||||
"""Adds a deprecated argument with the name argument_name.
|
||||
|
||||
|
|
@ -1013,7 +1450,7 @@ class HelpfulArgumentParser(object):
|
|||
return dict([(t, t == chosen_topic) for t in self.help_topics])
|
||||
|
||||
|
||||
def prepare_and_parse_args(plugins, args):
|
||||
def prepare_and_parse_args(plugins, args, detect_defaults=False):
|
||||
"""Returns parsed command line arguments.
|
||||
|
||||
:param .PluginsRegistry plugins: available plugins
|
||||
|
|
@ -1023,7 +1460,7 @@ def prepare_and_parse_args(plugins, args):
|
|||
:rtype: argparse.Namespace
|
||||
|
||||
"""
|
||||
helpful = HelpfulArgumentParser(args, plugins)
|
||||
helpful = HelpfulArgumentParser(args, plugins, detect_defaults)
|
||||
|
||||
# --help is automatically provided by argparse
|
||||
helpful.add(
|
||||
|
|
@ -1040,6 +1477,11 @@ def prepare_and_parse_args(plugins, args):
|
|||
help="Run without ever asking for user input. This may require "
|
||||
"additional command line flags; the client will try to explain "
|
||||
"which ones are required if it finds one missing")
|
||||
helpful.add(
|
||||
None, "--dry-run", action="store_true", dest="dry_run",
|
||||
help="Perform a test run of the client, obtaining test (invalid) certs"
|
||||
" but not saving them to disk. This can currently only be used"
|
||||
" with the 'certonly' subcommand.")
|
||||
helpful.add(
|
||||
None, "--register-unsafely-without-email", action="store_true",
|
||||
help="Specifying this flag enables registering an account with no "
|
||||
|
|
@ -1078,10 +1520,12 @@ def prepare_and_parse_args(plugins, args):
|
|||
version="%(prog)s {0}".format(letsencrypt.__version__),
|
||||
help="show program's version number and exit")
|
||||
helpful.add(
|
||||
"automation", "--renew-by-default", action="store_true",
|
||||
help="Select renewal by default when domains are a superset of a "
|
||||
"previously attained cert (often --keep-until-expiring is "
|
||||
"more appropriate). Implies --expand.")
|
||||
"automation", "--force-renewal", "--renew-by-default",
|
||||
action="store_true", dest="renew_by_default", help="If a certificate "
|
||||
"already exists for the requested domains, renew it now, "
|
||||
"regardless of whether it is near expiry. (Often "
|
||||
"--keep-until-expiring is more appropriate). Also implies "
|
||||
"--expand.")
|
||||
helpful.add(
|
||||
"automation", "--agree-tos", dest="tos", action="store_true",
|
||||
help="Agree to the Let's Encrypt Subscriber Agreement")
|
||||
|
|
@ -1160,6 +1604,16 @@ def prepare_and_parse_args(plugins, args):
|
|||
help="Require that all configuration files are owned by the current "
|
||||
"user; only needed if your config is somewhere unsafe like /tmp/")
|
||||
|
||||
helpful.add_group(
|
||||
"renew", description="The 'renew' subcommand will attempt to renew all"
|
||||
" certificates (or more precisely, certificate lineages) you have"
|
||||
" previously obtained if they are close to expiry, and print a"
|
||||
" summary of the results. By default, 'renew' will reuse the options"
|
||||
" used to create obtain or most recently successfully renew each"
|
||||
" certificate lineage. You can try it with `--dry-run` first. For"
|
||||
" more fine-grained control, you can renew individual lineages with"
|
||||
" the `certonly` subcommand.")
|
||||
|
||||
helpful.add_deprecated_argument("--agree-dev-preview", 0)
|
||||
|
||||
_create_subparsers(helpful)
|
||||
|
|
@ -1168,6 +1622,9 @@ def prepare_and_parse_args(plugins, args):
|
|||
# parser (--help should display plugin-specific options last)
|
||||
_plugins_parsing(helpful, plugins)
|
||||
|
||||
if not detect_defaults:
|
||||
global _parser # pylint: disable=global-statement
|
||||
_parser = helpful
|
||||
return helpful.parse_args()
|
||||
|
||||
|
||||
|
|
@ -1254,10 +1711,6 @@ def _paths_parser(helpful):
|
|||
add("testing", "--test-cert", "--staging", action='store_true', dest='staging',
|
||||
help='Use the staging server to obtain test (invalid) certs; equivalent'
|
||||
' to --server ' + constants.STAGING_URI)
|
||||
add("testing", "--dry-run", action="store_true", dest="dry_run",
|
||||
help="Perform a test run of the client, obtaining test (invalid) certs"
|
||||
" but not saving them to disk. This can currently only be used"
|
||||
" with the 'certonly' subcommand.")
|
||||
|
||||
|
||||
def _plugins_parsing(helpful, plugins):
|
||||
|
|
@ -1296,21 +1749,24 @@ def _plugins_parsing(helpful, plugins):
|
|||
# they are parsed in conjunction with --domains, they live here for
|
||||
# legibility. helpful.add_plugin_ags must be called first to add the
|
||||
# "webroot" topic
|
||||
helpful.add("webroot", "-w", "--webroot-path", action=WebrootPathProcessor,
|
||||
helpful.add("webroot", "-w", "--webroot-path", default=[], action=WebrootPathProcessor,
|
||||
help="public_html / webroot path. This can be specified multiple times to "
|
||||
"handle different domains; each domain will have the webroot path that"
|
||||
" preceded it. For instance: `-w /var/www/example -d example.com -d "
|
||||
"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)
|
||||
|
|
@ -1320,8 +1776,7 @@ class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring
|
|||
Keep a record of --webroot-path / -w flags during processing, so that
|
||||
we know which apply to which -d flags
|
||||
"""
|
||||
if args.webroot_path is None: # first -w flag encountered
|
||||
args.webroot_path = []
|
||||
if not args.webroot_path: # first -w flag encountered
|
||||
# if any --domain flags preceded the first --webroot-path flag,
|
||||
# apply that webroot path to those; subsequent entries in
|
||||
# args.webroot_map are filled in by cli.DomainFlagProcessor
|
||||
|
|
@ -1359,14 +1814,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)
|
||||
|
|
@ -1389,7 +1844,7 @@ def setup_log_file_handler(config, logfile, fmt):
|
|||
|
||||
|
||||
def _cli_log_handler(config, level, fmt):
|
||||
if config.text_mode:
|
||||
if config.text_mode or config.noninteractive_mode or config.verb == "renew":
|
||||
handler = colored_logging.StreamHandler()
|
||||
handler.setFormatter(logging.Formatter(fmt))
|
||||
else:
|
||||
|
|
@ -1457,8 +1912,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 "
|
||||
|
|
@ -1475,9 +1930,9 @@ def _handle_exception(exc_type, exc_value, trace, config):
|
|||
def main(cli_args=sys.argv[1:]):
|
||||
"""Command line argument parsing and main script execution."""
|
||||
sys.excepthook = functools.partial(_handle_exception, config=None)
|
||||
plugins = plugins_disco.PluginsRegistry.find_all()
|
||||
|
||||
# note: arg parser internally handles --help (and exits afterwards)
|
||||
plugins = plugins_disco.PluginsRegistry.find_all()
|
||||
args = prepare_and_parse_args(plugins, cli_args)
|
||||
config = configuration.NamespaceConfig(args)
|
||||
zope.component.provideUtility(config)
|
||||
|
|
@ -1506,6 +1961,9 @@ def main(cli_args=sys.argv[1:]):
|
|||
displayer = display_util.NoninteractiveDisplay(sys.stdout)
|
||||
elif config.text_mode:
|
||||
displayer = display_util.FileDisplay(sys.stdout)
|
||||
elif config.verb == "renew":
|
||||
config.noninteractive_mode = True
|
||||
displayer = display_util.NoninteractiveDisplay(sys.stdout)
|
||||
else:
|
||||
displayer = display_util.NcursesDisplay()
|
||||
zope.component.provideUtility(displayer)
|
||||
|
|
@ -1515,17 +1973,6 @@ def main(cli_args=sys.argv[1:]):
|
|||
zope.component.provideUtility(report)
|
||||
atexit.register(report.atexit_print_messages)
|
||||
|
||||
if not os.geteuid() == 0:
|
||||
logger.warning(
|
||||
"Root (sudo) is required to run most of letsencrypt functionality.")
|
||||
# check must be done after arg parsing as --help should work
|
||||
# w/o root; on the other hand, e.g. "letsencrypt run
|
||||
# --authenticator dns" or "letsencrypt plugins" does not
|
||||
# require root as well
|
||||
#return (
|
||||
# "{0}Root is required to run letsencrypt. Please use sudo.{0}"
|
||||
# .format(os.linesep))
|
||||
|
||||
return config.func(config, plugins)
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Let's Encrypt user-supplied configuration."""
|
||||
import copy
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
|
|
@ -78,6 +79,12 @@ class NamespaceConfig(object):
|
|||
return os.path.join(
|
||||
self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR)
|
||||
|
||||
def __deepcopy__(self, _memo):
|
||||
# Work around https://bugs.python.org/issue1515 for py26 tests :( :(
|
||||
# https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276
|
||||
new_ns = copy.deepcopy(self.namespace)
|
||||
return type(self)(new_ns)
|
||||
|
||||
|
||||
class RenewerConfiguration(object):
|
||||
"""Configuration wrapper for renewer."""
|
||||
|
|
|
|||
|
|
@ -37,7 +37,9 @@ STAGING_URI = "https://acme-staging.api.letsencrypt.org/directory"
|
|||
RENEWER_DEFAULTS = dict(
|
||||
renewer_enabled="yes",
|
||||
renew_before_expiry="30 days",
|
||||
deploy_before_expiry="20 days",
|
||||
# This value should ensure that there is never a deployment delay by
|
||||
# default.
|
||||
deploy_before_expiry="99 years",
|
||||
)
|
||||
"""Defaults for renewer script."""
|
||||
|
||||
|
|
|
|||
|
|
@ -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}"
|
||||
|
|
|
|||
|
|
@ -41,6 +41,36 @@ class Plugin(object):
|
|||
self.config = config
|
||||
self.name = name
|
||||
|
||||
@jose_util.abstractclassmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
"""Add plugin arguments to the CLI argument parser.
|
||||
|
||||
:param callable add: Function that proxies calls to
|
||||
`argparse.ArgumentParser.add_argument` prepending options
|
||||
with unique plugin name prefix.
|
||||
|
||||
NOTE: if you add argpase arguments such that users setting them can
|
||||
create a config entry that python's bool() would consider false (ie,
|
||||
the use might set the variable to "", [], 0, etc), please ensure that
|
||||
cli._set_by_cli() works for your variable.
|
||||
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def inject_parser_options(cls, parser, name):
|
||||
"""Inject parser options.
|
||||
|
||||
See `~.IPlugin.inject_parser_options` for docs.
|
||||
|
||||
"""
|
||||
# dummy function, doesn't check if dest.startswith(self.dest_namespace)
|
||||
def add(arg_name_no_prefix, *args, **kwargs):
|
||||
# pylint: disable=missing-docstring
|
||||
return parser.add_argument(
|
||||
"--{0}{1}".format(option_namespace(name), arg_name_no_prefix),
|
||||
*args, **kwargs)
|
||||
return cls.add_parser_arguments(add)
|
||||
|
||||
@property
|
||||
def option_namespace(self):
|
||||
"""ArgumentParser options namespace (prefix of all options)."""
|
||||
|
|
@ -64,32 +94,6 @@ class Plugin(object):
|
|||
def conf(self, var):
|
||||
"""Find a configuration value for variable ``var``."""
|
||||
return getattr(self.config, self.dest(var))
|
||||
|
||||
@classmethod
|
||||
def inject_parser_options(cls, parser, name):
|
||||
"""Inject parser options.
|
||||
|
||||
See `~.IPlugin.inject_parser_options` for docs.
|
||||
|
||||
"""
|
||||
# dummy function, doesn't check if dest.startswith(self.dest_namespace)
|
||||
def add(arg_name_no_prefix, *args, **kwargs):
|
||||
# pylint: disable=missing-docstring
|
||||
return parser.add_argument(
|
||||
"--{0}{1}".format(option_namespace(name), arg_name_no_prefix),
|
||||
*args, **kwargs)
|
||||
return cls.add_parser_arguments(add)
|
||||
|
||||
@jose_util.abstractclassmethod
|
||||
def add_parser_arguments(cls, add):
|
||||
"""Add plugin arguments to the CLI argument parser.
|
||||
|
||||
:param callable add: Function that proxies calls to
|
||||
`argparse.ArgumentParser.add_argument` prepending options
|
||||
with unique plugin name prefix.
|
||||
|
||||
"""
|
||||
|
||||
# other
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,8 @@ s.serve_forever()" """
|
|||
help="Automatically allows public IP logging.")
|
||||
|
||||
def prepare(self): # pylint: disable=missing-docstring,no-self-use
|
||||
pass # pragma: no cover
|
||||
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
|
||||
return ("This plugin requires user's manual intervention in setting "
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -769,6 +769,9 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
|||
self.cli_config = cli_config
|
||||
target_version = self.next_free_version()
|
||||
archive = self.cli_config.archive_dir
|
||||
# XXX if anyone ever moves a renewal configuration file, this will
|
||||
# break... perhaps prefix should be the dirname of the previous
|
||||
# cert.pem?
|
||||
prefix = os.path.join(archive, self.lineagename)
|
||||
target = dict(
|
||||
[(kind,
|
||||
|
|
|
|||
|
|
@ -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,13 +227,17 @@ 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")
|
||||
|
||||
with mock.patch("letsencrypt.cli._init_le_client") as mock_init:
|
||||
with mock.patch("letsencrypt.cli._auth_from_domains"):
|
||||
with mock.patch("letsencrypt.cli._auth_from_domains") as mock_afd:
|
||||
mock_afd.return_value = (mock.MagicMock(), mock.MagicMock())
|
||||
self._call(["certonly", "--manual", "-d", "foo.bar"])
|
||||
unused_config, auth, unused_installer = mock_init.call_args[0]
|
||||
self.assertTrue(isinstance(auth, manual.Authenticator))
|
||||
|
|
@ -323,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
|
||||
|
|
@ -530,35 +534,52 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self.assertRaises(errors.Error,
|
||||
self._certonly_new_request_common, mock_client)
|
||||
|
||||
def _test_certonly_renewal_common(self, renewal_verb, extra_args=None):
|
||||
def _test_renewal_common(self, due_for_renewal, extra_args, log_out=None,
|
||||
args=None, renew=True):
|
||||
# pylint: disable=too-many-locals
|
||||
cert_path = 'letsencrypt/tests/testdata/cert.pem'
|
||||
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
|
||||
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
|
||||
mock_lineage.should_autorenew.return_value = due_for_renewal
|
||||
mock_certr = mock.MagicMock()
|
||||
mock_key = mock.MagicMock(pem='pem_key')
|
||||
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
|
||||
mock_renewal.return_value = (renewal_verb, mock_lineage)
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
|
||||
mock_key, 'csr')
|
||||
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
|
||||
mock_init.return_value = mock_client
|
||||
get_utility_path = 'letsencrypt.cli.zope.component.getUtility'
|
||||
with mock.patch(get_utility_path) as mock_get_utility:
|
||||
with mock.patch('letsencrypt.cli.OpenSSL'):
|
||||
with mock.patch('letsencrypt.cli.crypto_util'):
|
||||
args = ['-d', 'foo.bar', '-a',
|
||||
'standalone', 'certonly']
|
||||
if extra_args:
|
||||
args += extra_args
|
||||
self._call(args)
|
||||
mock_client = mock.MagicMock()
|
||||
mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
|
||||
mock_key, 'csr')
|
||||
try:
|
||||
with mock.patch('letsencrypt.cli._find_duplicative_certs') as mock_fdc:
|
||||
mock_fdc.return_value = (mock_lineage, None)
|
||||
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
|
||||
mock_init.return_value = mock_client
|
||||
get_utility_path = 'letsencrypt.cli.zope.component.getUtility'
|
||||
with mock.patch(get_utility_path) as mock_get_utility:
|
||||
with mock.patch('letsencrypt.cli.OpenSSL') as mock_ssl:
|
||||
mock_latest = mock.MagicMock()
|
||||
mock_latest.get_issuer.return_value = "Fake fake"
|
||||
mock_ssl.crypto.load_certificate.return_value = mock_latest
|
||||
with mock.patch('letsencrypt.cli.crypto_util'):
|
||||
if not args:
|
||||
args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']
|
||||
if extra_args:
|
||||
args += extra_args
|
||||
self._call(args)
|
||||
|
||||
mock_client.obtain_certificate.assert_called_once_with(['foo.bar'])
|
||||
if log_out:
|
||||
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
|
||||
self.assertTrue(log_out in lf.read())
|
||||
|
||||
if renew:
|
||||
mock_client.obtain_certificate.assert_called_once_with(['isnot.org'])
|
||||
else:
|
||||
self.assertEqual(mock_client.obtain_certificate.call_count, 0)
|
||||
except:
|
||||
self._dump_log()
|
||||
raise
|
||||
|
||||
return mock_lineage, mock_get_utility
|
||||
|
||||
def test_certonly_renewal(self):
|
||||
lineage, get_utility = self._test_certonly_renewal_common('renew')
|
||||
lineage, get_utility = self._test_renewal_common(True, [])
|
||||
self.assertEqual(lineage.save_successor.call_count, 1)
|
||||
lineage.update_all_links_to.assert_called_once_with(
|
||||
lineage.latest_common_version())
|
||||
|
|
@ -566,12 +587,122 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self.assertTrue('fullchain.pem' in cert_msg)
|
||||
self.assertTrue('donate' in get_utility().add_message.call_args[0][0])
|
||||
|
||||
def test_certonly_dry_run_reinstall_is_renewal(self):
|
||||
_, get_utility = self._test_certonly_renewal_common('reinstall',
|
||||
['--dry-run'])
|
||||
def test_certonly_renewal_triggers(self):
|
||||
# --dry-run should force renewal
|
||||
_, get_utility = self._test_renewal_common(False, ['--dry-run', '--keep'],
|
||||
log_out="simulating renewal")
|
||||
self.assertEqual(get_utility().add_message.call_count, 1)
|
||||
self.assertTrue('dry run' in get_utility().add_message.call_args[0][0])
|
||||
|
||||
_, _ = self._test_renewal_common(False, ['--renew-by-default', '-tvv', '--debug'],
|
||||
log_out="Auto-renewal forced")
|
||||
self.assertEqual(get_utility().add_message.call_count, 1)
|
||||
|
||||
_, _ = self._test_renewal_common(False, ['-tvv', '--debug', '--keep'],
|
||||
log_out="not yet due", renew=False)
|
||||
|
||||
def _dump_log(self):
|
||||
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
|
||||
print "Logs:"
|
||||
print lf.read()
|
||||
|
||||
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")
|
||||
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')
|
||||
|
|
@ -581,8 +712,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly'])
|
||||
self.assertFalse(mock_client.obtain_certificate.called)
|
||||
self.assertFalse(mock_client.obtain_and_enroll_certificate.called)
|
||||
self.assertTrue(
|
||||
'donate' in mock_get_utility().add_message.call_args[0][0])
|
||||
self.assertEqual(mock_get_utility().add_message.call_count, 0)
|
||||
#self.assertTrue('donate' not in mock_get_utility().add_message.call_args[0][0])
|
||||
|
||||
def _test_certonly_csr_common(self, extra_args=None):
|
||||
certr = 'certr'
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
28
letsencrypt/tests/testdata/archive/sample-renewal/cert1.pem
vendored
Normal file
28
letsencrypt/tests/testdata/archive/sample-renewal/cert1.pem
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF
|
||||
ADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5
|
||||
MDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+
|
||||
slm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN
|
||||
NYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h
|
||||
A5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx
|
||||
UpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP
|
||||
r4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC
|
||||
AhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8
|
||||
L54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE
|
||||
bDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy
|
||||
eXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0
|
||||
c2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB
|
||||
8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw
|
||||
Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl
|
||||
cnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy
|
||||
dGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl
|
||||
IFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0
|
||||
b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy
|
||||
KzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve
|
||||
jzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2
|
||||
Ese3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU
|
||||
+ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf
|
||||
rAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ==
|
||||
-----END CERTIFICATE-----
|
||||
19
letsencrypt/tests/testdata/archive/sample-renewal/chain1.pem
vendored
Normal file
19
letsencrypt/tests/testdata/archive/sample-renewal/chain1.pem
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV
|
||||
BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw
|
||||
NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i
|
||||
8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8
|
||||
tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj
|
||||
7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8
|
||||
BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD
|
||||
HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj
|
||||
UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7
|
||||
eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
|
||||
A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB
|
||||
vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl
|
||||
zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo
|
||||
vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L
|
||||
oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW
|
||||
rFo4Uv1EnkKJm3vJFe50eJGhEKlx
|
||||
-----END CERTIFICATE-----
|
||||
47
letsencrypt/tests/testdata/archive/sample-renewal/fullchain1.pem
vendored
Normal file
47
letsencrypt/tests/testdata/archive/sample-renewal/fullchain1.pem
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIE1DCCA7ygAwIBAgITAPoz/CBluNQV/Eh9F+CS6dSxEDANBgkqhkiG9w0BAQsF
|
||||
ADAfMR0wGwYDVQQDDBRoYXBweSBoYWNrZXIgZmFrZSBDQTAeFw0xNjAyMDIyMzQ5
|
||||
MDBaFw0xNjA1MDIyMzQ5MDBaMBQxEjAQBgNVBAMTCWlzbm90Lm9yZzCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBALyudqLKcIdWZ5VaK1fuhlEDbZtvs2E+
|
||||
slm4dmSS1nFve7MdlZ69K0gdtnhkiPQ0wGQTligeDZ8fY8iL87GZO0tp5f7S+QJN
|
||||
NYCiYw6j4qp5JBy/zG22kJz1Quu7/vXMYLzLvK6x6YixiWAWyqqvlUVBLS1r4W3h
|
||||
A5Z+F1EIsXeyz7TJe3lAzIWAAxpfH9OviIz2rEDotuCdU771USLLNSw4qJojNlTx
|
||||
UpZG6lGFs8KGb8tqROXknaMKE4PvN3SITixSUTFbktt1Wz60moWbNdLMKvgkzuUP
|
||||
r4viO2P4SO5slNAY0ZeEssPpVAelN3EvrAcEZtoKmG5fnQDVo8uVag0CAwEAAaOC
|
||||
AhIwggIOMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB
|
||||
BQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUqhI4u6aaPrcYQnmypxV8Tap8
|
||||
L54wHwYDVR0jBBgwFoAU+3hPEvlgFYMsnxd/NBmzLjbqQYkweAYIKwYBBQUHAQEE
|
||||
bDBqMDMGCCsGAQUFBzABhidodHRwOi8vb2NzcC5zdGFnaW5nLXgxLmxldHNlbmNy
|
||||
eXB0Lm9yZy8wMwYIKwYBBQUHMAKGJ2h0dHA6Ly9jZXJ0LnN0YWdpbmcteDEubGV0
|
||||
c2VuY3J5cHQub3JnLzAUBgNVHREEDTALgglpc25vdC5vcmcwgf4GA1UdIASB9jCB
|
||||
8zAIBgZngQwBAgEwgeYGCysGAQQBgt8TAQEBMIHWMCYGCCsGAQUFBwIBFhpodHRw
|
||||
Oi8vY3BzLmxldHNlbmNyeXB0Lm9yZzCBqwYIKwYBBQUHAgIwgZ4MgZtUaGlzIENl
|
||||
cnRpZmljYXRlIG1heSBvbmx5IGJlIHJlbGllZCB1cG9uIGJ5IFJlbHlpbmcgUGFy
|
||||
dGllcyBhbmQgb25seSBpbiBhY2NvcmRhbmNlIHdpdGggdGhlIENlcnRpZmljYXRl
|
||||
IFBvbGljeSBmb3VuZCBhdCBodHRwczovL2xldHNlbmNyeXB0Lm9yZy9yZXBvc2l0
|
||||
b3J5LzANBgkqhkiG9w0BAQsFAAOCAQEAbAhX6FfQwELayneY4l5RvYSdw/Jj5CRy
|
||||
KzrM7ISld7x9YPpxX6Pmht/YyMhLWrtxvFUR2+RNhSIYB8IjQEjmKjvR7UNeiUve
|
||||
jzPEAuTg/9m3i0FJpPHc2aKGzlLFQCMm5/RrvnXI6ljIcyhocLvMiN46iexcExI2
|
||||
Ese3w8GoH6wARYKxU/QBexfoXQLgtAbYzNRE6EgKWtB+txV+7+d2MgbhCEit5VwU
|
||||
+ydT8inp9URsA7iKM03hDdGOBysddkrm1/yEhVy/Oo6bT9WMAUHVvz61hHekWcSf
|
||||
rAQ6BayubvWOUx06eTowXr1gln/rl+WXOxcsJeag127NuhmHOCXZxQ==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDETCCAfmgAwIBAgIJAJzxkS6o1QkIMA0GCSqGSIb3DQEBCwUAMB8xHTAbBgNV
|
||||
BAMMFGhhcHB5IGhhY2tlciBmYWtlIENBMB4XDTE1MDQwNzIzNTAzOFoXDTI1MDQw
|
||||
NDIzNTAzOFowHzEdMBsGA1UEAwwUaGFwcHkgaGFja2VyIGZha2UgQ0EwggEiMA0G
|
||||
CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDCCkd5mgXFErJ3F2M0E9dw+Ta/md5i
|
||||
8TDId01HberAApqmydG7UZYF3zLTSzNjlNSOmtybvrSGUnZ9r9tSQcL8VM6WUOM8
|
||||
tnIpiIjEA2QkBycMwvRmZ/B2ltPdYs/R9BqNwO1g18GDZrHSzUYtNKNeFI6Glamj
|
||||
7GK2Vr0SmiEamlNIR5ktAFsEErzf/d4jCF7sosMsJpMCm1p58QkP4LHLShVLXDa8
|
||||
BMfVoI+ipYcA08iNUFkgW8VWDclIDxcysa0psDDtMjX3+4aPkE/cefmP+1xOfUuD
|
||||
HOGV8XFynsP4EpTfVOZr0/g9gYQ7ZArqXX7GTQkFqduwPm/w5qxSPTarAgMBAAGj
|
||||
UDBOMB0GA1UdDgQWBBT7eE8S+WAVgyyfF380GbMuNupBiTAfBgNVHSMEGDAWgBT7
|
||||
eE8S+WAVgyyfF380GbMuNupBiTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUA
|
||||
A4IBAQAd9Da+Zv+TjMv7NTAmliqnWHY6d3UxEZN3hFEJ58IQVHbBZVZdW7zhRktB
|
||||
vR05Kweac0HJeK91TKmzvXl21IXLvh0gcNLU/uweD3no/snfdB4OoFompljThmgl
|
||||
zBqiqWoKBJQrLCA8w5UB+ReomRYd/EYXF/6TAfzm6hr//Xt5mPiUHPdvYt75lMAo
|
||||
vRxLSbF8TSQ6b7BYxISWjPgFASNNqJNHEItWsmQMtAjjwzb9cs01XH9pChVAWn9L
|
||||
oeMKa+SlHSYrWG93+EcrIH/dGU76uNOiaDzBSKvaehG53h25MHuO1anNICJvZovW
|
||||
rFo4Uv1EnkKJm3vJFe50eJGhEKlx
|
||||
-----END CERTIFICATE-----
|
||||
28
letsencrypt/tests/testdata/archive/sample-renewal/privkey1.pem
vendored
Normal file
28
letsencrypt/tests/testdata/archive/sample-renewal/privkey1.pem
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC8rnaiynCHVmeV
|
||||
WitX7oZRA22bb7NhPrJZuHZkktZxb3uzHZWevStIHbZ4ZIj0NMBkE5YoHg2fH2PI
|
||||
i/OxmTtLaeX+0vkCTTWAomMOo+KqeSQcv8xttpCc9ULru/71zGC8y7yusemIsYlg
|
||||
Fsqqr5VFQS0ta+Ft4QOWfhdRCLF3ss+0yXt5QMyFgAMaXx/Tr4iM9qxA6LbgnVO+
|
||||
9VEiyzUsOKiaIzZU8VKWRupRhbPChm/LakTl5J2jChOD7zd0iE4sUlExW5LbdVs+
|
||||
tJqFmzXSzCr4JM7lD6+L4jtj+EjubJTQGNGXhLLD6VQHpTdxL6wHBGbaCphuX50A
|
||||
1aPLlWoNAgMBAAECggEAfKKWFWS6PnwSAnNErFoQeZVVItb/XB5JO8EA2+CvLNFi
|
||||
mefR/MCixYlzDkYCvaXW7ISPrMJlZxYaGNBx0oAQzfkPB2wfNqj/zY/29SXGxast
|
||||
8puzk0mEb1oHsaZGfeFaiXvfkFpPlI8J2uJTT7qaVNv/1sArciSv9QonpsyiRhlB
|
||||
yqT49juNVoR1tJHyXzkkRfHKTG8OlJd4kuFOl3fM9dTFPQ/ft0kTNAQ/B4SFvSwF
|
||||
RJsbLbsbFGsUdV9ekE6UX6oWD/Ah707rvgtCyS0Bc+0O3t2EKwmm3RXPRUMHCVxE
|
||||
bKdTxRB4etbjMVXMuVhB8Y4GbfrtMCy+qxZQ6znCAQKBgQDr7bcYAZVZp/nBMVB+
|
||||
lBO9w73J6lnEWm6bZ9728KlGAKETaRhxZQSi6TN6MWwNwnk6rinyz4uVwVr9ZRCs
|
||||
WkB1TbvW0JNcWdr3YClwsKXAt8X22bjGe0LagDJHG6r1TPS+MdovOS2M6IMaxlbT
|
||||
rzFhSJ8ojLX3tqnOsmc7YAFLjQKBgQDMu8E9hoJt82lQzOGrjHmGzGEu2GLx9WKO
|
||||
e4nkj335kX6fIhMMqSXBFbTJZwXoYvk5J8ZnaARbYG0m5nxDCwRjX5HWa8q0B2Po
|
||||
ta53w01sKKznzlPjUhsdhEthun7MCFfLZpgvcZ9xVzOXo3/Zfn2+RrsPSjrVDqBy
|
||||
hj+k5mW4gQKBgHFWKf3LTO7cBdvsD8ou4mjn7nVgMi1kb/wR4wdnxzmMtdR4STi4
|
||||
GYkVVBhgQ5M8mDY7UoWFdH3FfCt8cI0Lcimn5ROl8RSNSeZKeL3c7lNtNRmHr/8R
|
||||
WaVTrlOAlBjxFiWEF1dWNW6ah9jF7RIV+DfOxj6ZkhTk2CAmjfb1AMpFAoGABf96
|
||||
KdNG/vGipDtcYSo8ZTaXoke0nmISARqdb5TEnAsnKoJVDInoEUARi9T411YO9x2z
|
||||
MlRZzFOG3xzhhxVLi53BKAcAaUXOJ4MrGVcfbYvDhQcGbiJ5qOO3UaWlEVUtPUhE
|
||||
LR+nDCsB1+9yT2zlQi3QTSJflt5W1QQZ2TrmwAECgYEAvQ7+sTcHs1K9yKj7koEu
|
||||
A19FbMA0IwvrVRcV/VqmlsoW6e6wW2YND+GtaDbKdD0aBPivqLJwpNFrsRA+W0iB
|
||||
vzmML6sKhhL+j7tjSgq+iQdBkKz0j9PyReuhe9CRnljMmyun+4qKEk0KUvxBrjPY
|
||||
Skn+ML18qyUoEPnmbpfHxCs=
|
||||
-----END PRIVATE KEY-----
|
||||
1
letsencrypt/tests/testdata/live/sample-renewal/cert.pem
vendored
Symbolic link
1
letsencrypt/tests/testdata/live/sample-renewal/cert.pem
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../archive/sample-renewal/cert1.pem
|
||||
1
letsencrypt/tests/testdata/live/sample-renewal/chain.pem
vendored
Symbolic link
1
letsencrypt/tests/testdata/live/sample-renewal/chain.pem
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../archive/sample-renewal/chain1.pem
|
||||
1
letsencrypt/tests/testdata/live/sample-renewal/fullchain.pem
vendored
Symbolic link
1
letsencrypt/tests/testdata/live/sample-renewal/fullchain.pem
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../archive/sample-renewal/fullchain1.pem
|
||||
1
letsencrypt/tests/testdata/live/sample-renewal/privkey.pem
vendored
Symbolic link
1
letsencrypt/tests/testdata/live/sample-renewal/privkey.pem
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../archive/sample-renewal/privkey1.pem
|
||||
76
letsencrypt/tests/testdata/sample-renewal.conf
vendored
Executable file
76
letsencrypt/tests/testdata/sample-renewal.conf
vendored
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
cert = MAGICDIR/live/sample-renewal/cert.pem
|
||||
privkey = MAGICDIR/live/sample-renewal/privkey.pem
|
||||
chain = MAGICDIR/live/sample-renewal/chain.pem
|
||||
fullchain = MAGICDIR/live/sample-renewal/fullchain.pem
|
||||
renew_before_expiry = 1 year
|
||||
|
||||
# Options and defaults used in the renewal process
|
||||
[renewalparams]
|
||||
no_self_upgrade = False
|
||||
apache_enmod = a2enmod
|
||||
no_verify_ssl = False
|
||||
ifaces = None
|
||||
apache_dismod = a2dismod
|
||||
register_unsafely_without_email = False
|
||||
apache_handle_modules = True
|
||||
uir = None
|
||||
installer = none
|
||||
nginx_ctl = nginx
|
||||
config_dir = MAGICDIR
|
||||
text_mode = False
|
||||
func = <function obtain_cert at 0x7f093a163c08>
|
||||
staging = True
|
||||
prepare = False
|
||||
work_dir = /var/lib/letsencrypt
|
||||
tos = False
|
||||
init = False
|
||||
http01_port = 80
|
||||
duplicate = False
|
||||
noninteractive_mode = True
|
||||
key_path = None
|
||||
nginx = False
|
||||
nginx_server_root = /etc/nginx
|
||||
fullchain_path = /home/ubuntu/letsencrypt/chain.pem
|
||||
email = None
|
||||
csr = None
|
||||
agree_dev_preview = None
|
||||
redirect = None
|
||||
verb = certonly
|
||||
verbose_count = -3
|
||||
config_file = None
|
||||
renew_by_default = False
|
||||
hsts = False
|
||||
apache_handle_sites = True
|
||||
authenticator = standalone
|
||||
domains = isnot.org,
|
||||
rsa_key_size = 2048
|
||||
apache_challenge_location = /etc/apache2
|
||||
checkpoints = 1
|
||||
manual_test_mode = False
|
||||
apache = False
|
||||
cert_path = /home/ubuntu/letsencrypt/cert.pem
|
||||
webroot_path = None
|
||||
reinstall = False
|
||||
expand = False
|
||||
strict_permissions = False
|
||||
apache_server_root = /etc/apache2
|
||||
account = None
|
||||
dry_run = False
|
||||
manual_public_ip_logging_ok = False
|
||||
chain_path = /home/ubuntu/letsencrypt/chain.pem
|
||||
break_my_certs = False
|
||||
standalone = True
|
||||
manual = False
|
||||
server = https://acme-staging.api.letsencrypt.org/directory
|
||||
standalone_supported_challenges = "tls-sni-01,http-01"
|
||||
webroot = False
|
||||
os_packages_only = False
|
||||
apache_init_script = None
|
||||
user_agent = None
|
||||
apache_le_vhost_ext = -le-ssl.conf
|
||||
debug = False
|
||||
tls_sni_01_port = 443
|
||||
logs_dir = /var/log/letsencrypt
|
||||
apache_vhost_root = /etc/apache2/sites-available
|
||||
configurator = None
|
||||
[[webroot_map]]
|
||||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.4.0.dev0'
|
||||
version = '0.5.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'setuptools', # pkg_resources
|
||||
|
|
|
|||
|
|
@ -20,16 +20,22 @@ else
|
|||
readlink="readlink"
|
||||
fi
|
||||
|
||||
common() {
|
||||
letsencrypt_test \
|
||||
common_no_force_renew() {
|
||||
letsencrypt_test_no_force_renew \
|
||||
--authenticator standalone \
|
||||
--installer null \
|
||||
"$@"
|
||||
}
|
||||
|
||||
common() {
|
||||
common_no_force_renew \
|
||||
--renew-by-default \
|
||||
"$@"
|
||||
}
|
||||
|
||||
common --domains le1.wtf --standalone-supported-challenges tls-sni-01 auth
|
||||
common --domains le2.wtf --standalone-supported-challenges http-01 run
|
||||
common -a manual -d le.wtf auth
|
||||
common -a manual -d le.wtf auth --rsa-key-size 4096
|
||||
|
||||
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
|
||||
OPENSSL_CNF=examples/openssl.cnf
|
||||
|
|
@ -44,6 +50,40 @@ 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)
|
||||
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/le.wtf.conf"
|
||||
common_no_force_renew renew --rsa-key-size 2048
|
||||
CheckCertCount 3
|
||||
|
||||
# The 4096 bit setting should persist to the first renewal, but be overriden in the second
|
||||
|
||||
size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1`
|
||||
size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1`
|
||||
size3=`wc -c ${root}/conf/archive/le.wtf/privkey3.pem | cut -d" " -f1`
|
||||
# 4096 bit PEM keys are about ~3270 bytes, 2048 ones are about 1700 bytes
|
||||
if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; then
|
||||
echo key sizes violate assumptions:
|
||||
ls -l "${root}/conf/archive/le.wtf/privkey"*
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# revoke by account key
|
||||
common revoke --cert-path "$root/conf/live/le.wtf/cert.pem"
|
||||
# revoke renewed
|
||||
|
|
|
|||
|
|
@ -12,6 +12,12 @@ store_flags="$store_flags --logs-dir $root/logs"
|
|||
export root store_flags
|
||||
|
||||
letsencrypt_test () {
|
||||
letsencrypt_test_no_force_renew \
|
||||
--renew-by-default \
|
||||
"$@"
|
||||
}
|
||||
|
||||
letsencrypt_test_no_force_renew () {
|
||||
letsencrypt \
|
||||
--server "${SERVER:-http://localhost:4000/directory}" \
|
||||
--no-verify-ssl \
|
||||
|
|
@ -19,11 +25,10 @@ letsencrypt_test () {
|
|||
--http-01-port 5002 \
|
||||
--manual-test-mode \
|
||||
$store_flags \
|
||||
--text \
|
||||
--non-interactive \
|
||||
--no-redirect \
|
||||
--agree-tos \
|
||||
--register-unsafely-without-email \
|
||||
--renew-by-default \
|
||||
--debug \
|
||||
-vvvvvvv \
|
||||
"$@"
|
||||
|
|
|
|||
|
|
@ -241,21 +241,21 @@ def local_git_clone(repo_url):
|
|||
"clones master of repo_url"
|
||||
with lcd(LOGDIR):
|
||||
local('if [ -d letsencrypt ]; then rm -rf letsencrypt; fi')
|
||||
local('git clone %s'% repo_url)
|
||||
local('git clone %s letsencrypt'% repo_url)
|
||||
local('tar czf le.tar.gz letsencrypt')
|
||||
|
||||
def local_git_branch(repo_url, branch_name):
|
||||
"clones branch <branch_name> of repo_url"
|
||||
with lcd(LOGDIR):
|
||||
local('if [ -d letsencrypt ]; then rm -rf letsencrypt; fi')
|
||||
local('git clone %s --branch %s --single-branch'%(repo_url, branch_name))
|
||||
local('git clone %s letsencrypt --branch %s --single-branch'%(repo_url, branch_name))
|
||||
local('tar czf le.tar.gz letsencrypt')
|
||||
|
||||
def local_git_PR(repo_url, PRnumstr, merge_master=True):
|
||||
"clones specified pull request from repo_url and optionally merges into master"
|
||||
with lcd(LOGDIR):
|
||||
local('if [ -d letsencrypt ]; then rm -rf letsencrypt; fi')
|
||||
local('git clone %s'% repo_url)
|
||||
local('git clone %s letsencrypt'% repo_url)
|
||||
local('cd letsencrypt && git fetch origin pull/%s/head:lePRtest'%PRnumstr)
|
||||
local('cd letsencrypt && git co lePRtest')
|
||||
if merge_master:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
#private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4)
|
||||
|
||||
cd letsencrypt
|
||||
./letsencrypt-auto certonly -v --standalone --debug \
|
||||
./letsencrypt-auto --os-packages-only
|
||||
./letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \
|
||||
--text --agree-dev-preview --agree-tos \
|
||||
--renew-by-default --redirect \
|
||||
--register-unsafely-without-email \
|
||||
|
|
|
|||
55
tests/letstest/scripts/test_renew_standalone.sh
Executable file
55
tests/letstest/scripts/test_renew_standalone.sh
Executable file
|
|
@ -0,0 +1,55 @@
|
|||
#!/bin/bash -x
|
||||
|
||||
# $OS_TYPE $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL
|
||||
# are dynamically set at execution
|
||||
|
||||
# run letsencrypt-apache2 via letsencrypt-auto
|
||||
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
|
||||
fi
|
||||
|
||||
bootstrap/dev/venv.sh
|
||||
sudo venv/bin/letsencrypt certonly --debug --standalone -t --agree-dev-preview --agree-tos \
|
||||
--renew-by-default --redirect --register-unsafely-without-email \
|
||||
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL -v
|
||||
if [ $? -ne 0 ] ; then
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
if [ "$OS_TYPE" = "ubuntu" ] ; then
|
||||
venv/bin/tox -e apacheconftest
|
||||
else
|
||||
echo Not running hackish apache tests on $OS_TYPE
|
||||
fi
|
||||
|
||||
if [ $? -ne 0 ] ; then
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
sudo venv/bin/letsencrypt renew --renew-by-default
|
||||
|
||||
if [ $? -ne 0 ] ; then
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
|
||||
ls /etc/letsencrypt/archive/$PUBLIC_HOSTNAME | grep -q 2.pem
|
||||
|
||||
if [ $? -ne 0 ] ; then
|
||||
FAIL=1
|
||||
fi
|
||||
|
||||
# return error if any of the subtests failed
|
||||
if [ "$FAIL" = 1 ] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -9,9 +9,9 @@ fi
|
|||
|
||||
function sayhash { # $1 <-- HASH ; $2 <---SIGFILEBALL
|
||||
while read -p "Press Enter to read the hash aloud or type 'done': " INP && [ "$INP" = "" ] ; do
|
||||
cat $1 | (echo "(Parameter.set 'Duration_Stretch 1.5)"; \
|
||||
cat $1 | (echo "(Parameter.set 'Duration_Stretch 1.8)"; \
|
||||
echo -n '(SayText "'; \
|
||||
sha1sum | cut -c1-40 | fold -1 | sed 's/^a$/alpha/; s/^b$/bravo/; s/^c$/charlie/; s/^d$/delta/; s/^e$/echo/; s/^f$/foxtrot/'; \
|
||||
sha256sum | cut -c1-64 | fold -1 | sed 's/^a$/alpha/; s/^b$/bravo/; s/^c$/charlie/; s/^d$/delta/; s/^e$/echo/; s/^f$/foxtrot/'; \
|
||||
echo '")' ) | festival
|
||||
done
|
||||
|
||||
|
|
@ -23,8 +23,8 @@ function offlinesign { # $1 <-- INPFILE ; $2 <---SIGFILE
|
|||
echo HASH FOR SIGNING:
|
||||
SIGFILEBALL="$2.lzma.base64"
|
||||
#echo "(place the resulting raw binary signature in $SIGFILEBALL)"
|
||||
sha1sum $1
|
||||
echo metahash for confirmation only $(sha1sum $1 |cut -d' ' -f1 | tr -d '\n' | sha1sum | cut -c1-6) ...
|
||||
sha256sum $1
|
||||
echo metahash for confirmation only $(sha256sum $1 |cut -d' ' -f1 | tr -d '\n' | sha256sum | cut -c1-6) ...
|
||||
echo
|
||||
sayhash $1 $SIGFILEBALL
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,10 +171,10 @@ while ! openssl dgst -sha256 -verify $RELEASE_OPENSSL_PUBKEY -signature \
|
|||
read -p "Please correctly sign letsencrypt-auto with offline-signrequest.sh"
|
||||
done
|
||||
|
||||
git add letsencrypt-auto-source
|
||||
git diff --cached
|
||||
git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"
|
||||
git tag --local-user "$RELEASE_GPG_KEY" \
|
||||
--sign --message "Release $version" "$tag"
|
||||
git tag --local-user "$RELEASE_GPG_KEY" --sign --message "Release $version" "$tag"
|
||||
|
||||
cd ..
|
||||
echo Now in $PWD
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
export VENV_ARGS="--python python2"
|
||||
|
||||
./bootstrap/dev/_venv_common.sh \
|
||||
./tools/_venv_common.sh \
|
||||
-e acme[testing] \
|
||||
-e .[dev,docs,testing] \
|
||||
-e letsencrypt-apache \
|
||||
|
|
@ -4,5 +4,5 @@
|
|||
export VENV_NAME="${VENV_NAME:-venv3}"
|
||||
export VENV_ARGS="--python python3"
|
||||
|
||||
./bootstrap/dev/_venv_common.sh \
|
||||
./tools/_venv_common.sh \
|
||||
-e acme[testing] \
|
||||
Loading…
Reference in a new issue