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

This commit is contained in:
Joona Hoikkala
2015-12-09 15:29:05 +02:00
35 changed files with 427 additions and 214 deletions
-1
View File
@@ -1 +0,0 @@
letsencrypt/DISCLAIMER
+1 -1
View File
@@ -33,7 +33,7 @@ RUN /opt/letsencrypt/src/ubuntu.sh && \
# Dockerfile we make sure we cache as much as possible
# py26reqs.txt not installed!
COPY setup.py README.rst CHANGES.rst MANIFEST.in DISCLAIMER linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/letsencrypt/src/
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/letsencrypt/src/
# all above files are necessary for setup.py, however, package source
# code directory has to be copied separately to a subdirectory...
-1
View File
@@ -4,7 +4,6 @@ include CHANGES.rst
include CONTRIBUTING.md
include LICENSE.txt
include linter_plugin.py
include letsencrypt/DISCLAIMER
recursive-include docs *
recursive-include examples *
recursive-include letsencrypt/tests/testdata *
+20 -2
View File
@@ -27,7 +27,7 @@ If ``letsencrypt`` is packaged for your OS, you can install it from there, and
run it by typing ``letsencrypt``. Because not all operating systems have
packages yet, we provide a temporary solution via the ``letsencrypt-auto``
wrapper script, which obtains some dependencies from your OS and puts others
in an python virtual environment::
in a python virtual environment::
user@webserver:~$ git clone https://github.com/letsencrypt/letsencrypt
user@webserver:~$ cd letsencrypt
@@ -118,6 +118,24 @@ email to client-dev+subscribe@letsencrypt.org)
.. _watch demo video: https://www.youtube.com/watch?v=Gas_sSB-5SU
System Requirements
===================
The Let's Encrypt Client presently only runs on Unix-ish OSes that include
Python 2.6 or 2.7; Python 3.x support will be added after the Public Beta
launch. The client requires root access in order to write to
``/etc/letsencrypt``, ``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to
bind to ports 80 and 443 (if you use the ``standalone`` plugin) and to read and
modify webserver configurations (if you use the ``apache`` or ``nginx``
plugins). If none of these apply to you, it is theoretically possible to run
without root privileges, but for most users who want to avoid running an ACME
client as root, either `letsencrypt-nosudo
<https://github.com/diafygi/letsencrypt-nosudo>`_ or `simp_le
<https://github.com/kuba/simp_le>`_ are more appropriate choices.
The Apache plugin currently requires a Debian-based OS with augeas version
1.0; this includes Ubuntu 12.04+ and Debian 7+.
Current Features
================
@@ -145,5 +163,5 @@ Current Features
* Free and Open Source Software, made with Python.
.. _Freenode: https://freenode.net
.. _Freenode: https://webchat.freenode.net?channels=%23letsencrypt
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
+1 -1
View File
@@ -194,7 +194,7 @@ class JSONDeSerializable(object):
:rtype: str
"""
return self.json_dumps(sort_keys=True, indent=4)
return self.json_dumps(sort_keys=True, indent=4, separators=(',', ': '))
@classmethod
def json_dump_default(cls, python_object):
+1 -4
View File
@@ -1,8 +1,6 @@
"""Tests for acme.jose.interfaces."""
import unittest
import six
class JSONDeSerializableTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
@@ -92,9 +90,8 @@ class JSONDeSerializableTest(unittest.TestCase):
self.assertEqual('["foo1", "foo2"]', self.seq.json_dumps())
def test_json_dumps_pretty(self):
filler = ' ' if six.PY2 else ''
self.assertEqual(self.seq.json_dumps_pretty(),
'[\n "foo1",{0}\n "foo2"\n]'.format(filler))
'[\n "foo1",\n "foo2"\n]')
def test_json_dump_default(self):
from acme.jose.interfaces import JSONDeSerializable
+4 -2
View File
@@ -22,12 +22,14 @@ class Error(jose.JSONObjectWithFields, errors.Error):
('urn:acme:error:' + name, description) for name, description in (
('badCSR', 'The CSR is unacceptable (e.g., due to a short key)'),
('badNonce', 'The client sent an unacceptable anti-replay nonce'),
('connection', 'The server could not connect to the client for DV'),
('connection', 'The server could not connect to the client to '
'verify the domain'),
('dnssec', 'The server could not validate a DNSSEC signed domain'),
('malformed', 'The request message was malformed'),
('rateLimited', 'There were too many requests of a given type'),
('serverInternal', 'The server experienced an internal error'),
('tls', 'The server experienced a TLS error during DV'),
('tls', 'The server experienced a TLS error during domain '
'verification'),
('unauthorized', 'The client lacks sufficient authorization'),
('unknownHost', 'The server could not resolve a domain name'),
)
+1 -1
View File
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
version = '0.2.0.dev0'
install_requires = [
# load_pem_private/public_key (>=0.6)
+1 -1
View File
@@ -2,7 +2,7 @@
# Tested with:
# - Fedora 22, 23 (x64)
# - Centos 7 (x64: onD igitalOcean droplet)
# - Centos 7 (x64: on DigitalOcean droplet)
if type dnf 2>/dev/null
then
+150 -103
View File
@@ -28,13 +28,8 @@ Firstly, please `install Git`_ and run the following commands:
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
.. warning:: Alternatively you could `download the ZIP archive`_ and
extract the snapshot of our repository, but it's strongly
recommended to use the above method instead.
.. _`install Git`: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
.. _`download the ZIP archive`:
https://github.com/letsencrypt/letsencrypt/archive/master.zip
To install and run the client you just need to type:
@@ -61,107 +56,48 @@ or for full help, type:
./letsencrypt-auto --help all
Running with Docker
-------------------
Docker_ is an amazingly simple and quick way to obtain a
certificate. However, this mode of operation is unable to install
certificates or configure your webserver, because our installer
plugins cannot reach it from inside the Docker container.
You should definitely read the :ref:`where-certs` section, in order to
know how to manage the certs
manually. https://github.com/letsencrypt/letsencrypt/wiki/Ciphersuite-guidance
provides some information about recommended ciphersuites. If none of
these make much sense to you, you should definitely use the
letsencrypt-auto_ method, which enables you to use installer plugins
that cover both of those hard topics.
If you're still not convinced and have decided to use this method,
from the server that the domain you're requesting a cert for resolves
to, `install Docker`_, then issue the following command:
.. code-block:: shell
sudo docker run -it --rm -p 443:443 -p 80:80 --name letsencrypt \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
quay.io/letsencrypt/letsencrypt:latest auth
and follow the instructions (note that ``auth`` command is explicitly
used - no installer plugins involved). Your new cert will be available
in ``/etc/letsencrypt/live`` on the host.
.. _Docker: https://docker.com
.. _`install Docker`: https://docs.docker.com/userguide/
Operating System Packages
--------------------------
**FreeBSD**
* Port: ``cd /usr/ports/security/py-letsencrypt && make install clean``
* Package: ``pkg install py27-letsencrypt``
**Arch Linux**
.. code-block:: shell
sudo pacman -S letsencrypt letsencrypt-apache
**Other Operating Systems**
Unfortunately, this is an ongoing effort. If you'd like to package
Let's Encrypt client for your distribution of choice please have a
look at the :doc:`packaging`.
From source
-----------
Installation from source is only supported for developers and the
whole process is described in the :doc:`contributing`.
.. warning:: Please do **not** use ``python setup.py install`` or
``python pip install .``. Please do **not** attempt the
installation commands as superuser/root and/or without virtual
environment, e.g. ``sudo python setup.py install``, ``sudo pip
install``, ``sudo ./venv/bin/...``. These modes of operation might
corrupt your operating system and are **not supported** by the
Let's Encrypt team!
Comparison of different methods
-------------------------------
Unless you have a very specific requirements, we kindly ask you to use
the letsencrypt-auto_ method. It's the fastest, the most thoroughly
tested and the most reliable way of getting our software and the free
SSL certificates!
``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
experimental, Arch linux and FreeBSD 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
the :doc:`contributing`. Some `other methods of installation`_ are discussed
below.
Plugins
=======
=========== = = ===============================================================
Plugin A I 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.
webroot_ Y N Obtains a cert using an already running webserver.
manual_ Y N Helps you obtain a cert by giving you instructions to perform
domain validation yourself.
nginx_ Y Y Very experimental and not included in letsencrypt-auto_.
=========== = = ===============================================================
The Let's Encrypt client supports a number of different "plugins" that can be
used to obtain and/or install certificates. Plugins that can obtain a cert
are called "authenticators" and can be used with the "certonly" command.
Plugins that can install a cert are called "installers". Plugins that do both
can be used with the "letsencrypt run" command, which is the default.
=========== ==== ==== ===============================================================
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.
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
domain validation yourself.
nginx_ Y Y Very experimental and not included in letsencrypt-auto_.
=========== ==== ==== ===============================================================
Future plugins for IMAP servers, SMTP servers, IRC servers, etc, are likely to
be installers but not authenticators.
Apache
------
If you're running Apache 2.4 on a Debian-based OS with version 1.0+ of
the ``libaugeas0`` package available, you can use the Apache plugin.
This automates both obtaining and installing certs on an Apache
This automates both obtaining *and* installing certs on an Apache
webserver. To specify this plugin on the command line, simply include
``--apache``.
@@ -184,13 +120,22 @@ 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`` with the root
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 multiple domains are specified, they must all use the same path.
Additionally, your server must be configured to serve files from
hidden directories.
If you're getting a certificate for many domains at once, each domain will use
the most recent ``--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
``/var/www/example`` webroot directory for the first two, and
``/var/www/eg`` for the second two.
Note that to use the webroot plugin, your server must be configured to serve
files from hidden directories.
Manual
------
@@ -228,10 +173,11 @@ Renewal
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`. 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).
`--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).
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.
@@ -341,7 +287,7 @@ get support on our `forums <https://community.letsencrypt.org>`_.
If you find a bug in the software, please do report it in our `issue
tracker
<https://github.com/letsencrypt/letsencrypt/issues>`_. Remember to
give us us as much information as possible:
give us as much information as possible:
- copy and paste exact command line used and the output (though mind
that the latter might include some personally identifiable
@@ -352,6 +298,107 @@ give us us as much information as possible:
- your operating system, including specific version
- specify which installation_ method you've chosen
Other methods of installation
=============================
Running with Docker
-------------------
Docker_ is an amazingly simple and quick way to obtain a
certificate. However, this mode of operation is unable to install
certificates or configure your webserver, because our installer
plugins cannot reach it from inside the Docker container.
You should definitely read the :ref:`where-certs` section, in order to
know how to manage the certs
manually. https://github.com/letsencrypt/letsencrypt/wiki/Ciphersuite-guidance
provides some information about recommended ciphersuites. If none of
these make much sense to you, you should definitely use the
letsencrypt-auto_ method, which enables you to use installer plugins
that cover both of those hard topics.
If you're still not convinced and have decided to use this method,
from the server that the domain you're requesting a cert for resolves
to, `install Docker`_, then issue the following command:
.. code-block:: shell
sudo docker run -it --rm -p 443:443 -p 80:80 --name letsencrypt \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
quay.io/letsencrypt/letsencrypt:latest auth
and follow the instructions (note that ``auth`` command is explicitly
used - no installer plugins involved). Your new cert will be available
in ``/etc/letsencrypt/live`` on the host.
.. _Docker: https://docker.com
.. _`install Docker`: https://docs.docker.com/userguide/
Operating System Packages
--------------------------
**FreeBSD**
* Port: ``cd /usr/ports/security/py-letsencrypt && make install clean``
* Package: ``pkg install py27-letsencrypt``
**Arch Linux**
.. code-block:: shell
sudo pacman -S letsencrypt letsencrypt-apache
**Debian Experimental**
If you run Debian unstable, you can install experimental letsencrypt packages.
Add the line ``deb http://ftp.us.debian.org/debian/ experimental main`` (or
the equivalent for your country) to ``/etc/apt/sources.list``, then run
.. code-block:: shell
sudo apt-get update
sudo apt-get -t experimental install letsencrypt python-letsencrypt-apache
If you don't want to use the Apache plugin, you can ommit the
``python-letsencrypt-apache`` package.
**Other Operating Systems**
OS packaging is an ongoing effort. If you'd like to package
Let's Encrypt client for your distribution of choice please have a
look at the :doc:`packaging`.
From source
-----------
Installation from source is only supported for developers and the
whole process is described in the :doc:`contributing`.
.. warning:: Please do **not** use ``python setup.py install`` or
``python pip install .``. Please do **not** attempt the
installation commands as superuser/root and/or without virtual
environment, e.g. ``sudo python setup.py install``, ``sudo pip
install``, ``sudo ./venv/bin/...``. These modes of operation might
corrupt your operating system and are **not supported** by the
Let's Encrypt team!
Comparison of different methods
-------------------------------
Unless you have a very specific requirements, we kindly ask you to use
the letsencrypt-auto_ method. It's the fastest, the most thoroughly
tested and the most reliable way of getting our software and the free
SSL certificates!
Beyond the methods discussed here, other methods may be possible, such as
installing Let's Encrypt directly with pip from PyPI or downloading a ZIP
archive from GitHub may be technically possible but are not presently
recommended or supported.
.. rubric:: Footnotes
+4
View File
@@ -11,6 +11,10 @@ server = https://acme-staging.api.letsencrypt.org/directory
# Uncomment and update to register with the specified e-mail address
# email = foo@example.com
# Uncomment and update to generate certificates for the specified
# domains.
# domains = example.com, www.example.com
# Uncomment to use a text interface instead of ncurses
# text = True
-1
View File
@@ -8,7 +8,6 @@ email = foo@example.com
domains = example.com
text = True
agree-dev-preview = True
agree-tos = True
debug = True
# Unfortunately, it's not possible to specify "verbose" multiple times
@@ -1,2 +1,2 @@
Let's Encrypt includes the very latest Augeas lenses in order to ship bug fixes
to Apacche configuration handling bugs as quickly as possible
to Apache configuration handling bugs as quickly as possible
@@ -59,8 +59,10 @@ let empty = Util.empty_dos
let indent = Util.indent
(* borrowed from shellvars.aug *)
let char_arg_dir = /([^\\ '"\t\r\n]|[^\\ '"\t\r\n][^ '"\t\r\n]*[^\\ '"\t\r\n])|\\\\"|\\\\'/
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ '"\t\r\n])|\\\\"|\\\\'/
let char_arg_sec = /[^ '"\t\r\n>]|\\\\"|\\\\'/
let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/
let cdot = /\\\\./
let cl = /\\\\\n/
let dquot =
@@ -77,11 +79,19 @@ let comp = /[<>=]?=/
let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ]
let arg_wl = [ label "arg" . store (char_arg_wl+|dquot|squot) ]
(* comma-separated wordlist as permitted in the SSLRequire directive *)
let arg_wordlist =
let wl_start = Util.del_str "{" in
let wl_end = Util.del_str "}" in
let wl_sep = del /[ \t]*,[ \t]*/ ", "
in [ label "wordlist" . wl_start . arg_wl . (wl_sep . arg_wl)* . wl_end ]
let argv (l:lens) = l . (sep_spc . l)*
let directive = [ indent . label "directive" . store word .
(sep_spc . argv arg_dir)? . eol ]
(sep_spc . argv (arg_dir|arg_wordlist))? . eol ]
let section (body:lens) =
(* opt_eol includes empty lines *)
@@ -91,7 +101,7 @@ let section (body:lens) =
indent . dels "</" in
let kword = key word in
let dword = del word "a" in
[ indent . dels "<" . square kword inner dword . del ">" ">" . eol ]
[ indent . dels "<" . square kword inner dword . del />[ \t\n\r]*/ ">\n" ]
let rec content = section (content|directive)
@@ -131,7 +131,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.version = version
self.vhosts = None
self._enhance_func = {"redirect": self._enable_redirect,
"ensure-http-header": self._set_http_header}
"ensure-http-header": self._set_http_header}
@property
def mod_ssl_conf(self):
@@ -558,21 +558,43 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Check for Listen <port>
# Note: This could be made to also look for ip:443 combo
if not self.parser.find_dir("Listen", port):
logger.debug("No Listen %s directive found. Setting the "
"Apache Server to Listen on port %s", port, port)
if port == "443":
args = [port]
listens = [self.parser.get_arg(x).split()[0] for x in self.parser.find_dir("Listen")]
# In case no Listens are set (which really is a broken apache config)
if not listens:
listens = ["80"]
for listen in listens:
# For any listen statement, check if the machine also listens on Port 443.
# If not, add such a listen statement.
if len(listen.split(":")) == 1:
# Its listening to all interfaces
if port not in listens:
if port == "443":
args = [port]
else:
# Non-standard ports should specify https protocol
args = [port, "https"]
self.parser.add_dir_to_ifmodssl(
parser.get_aug_path(
self.parser.loc["listen"]), "Listen", args)
self.save_notes += "Added Listen %s directive to %s\n" % (
port, self.parser.loc["listen"])
listens.append(port)
else:
# Non-standard ports should specify https protocol
args = [port, "https"]
self.parser.add_dir_to_ifmodssl(
parser.get_aug_path(
self.parser.loc["listen"]), "Listen", args)
self.save_notes += "Added Listen %s directive to %s\n" % (
port, self.parser.loc["listen"])
# The Listen statement specifies an ip
_, ip = listen[::-1].split(":", 1)
ip = ip[::-1]
if "%s:%s" % (ip, port) not in listens:
if port == "443":
args = ["%s:%s" % (ip, port)]
else:
# Non-standard ports should specify https protocol
args = ["%s:%s" % (ip, port), "https"]
self.parser.add_dir_to_ifmodssl(
parser.get_aug_path(
self.parser.loc["listen"]), "Listen", args)
self.save_notes += "Added Listen %s:%s directive to %s\n" % (
ip, port, self.parser.loc["listen"])
listens.append("%s:%s" % (ip, port))
def make_addrs_sni_ready(self, addrs):
"""Checks to see if the server is ready for SNI challenges.
@@ -391,6 +391,39 @@ class TwoVhost80Test(util.ApacheTest):
self.assertEqual(mock_add_dir.call_count, 2)
def test_prepare_server_https_named_listen(self):
mock_find = mock.Mock()
mock_find.return_value = ["test1", "test2", "test3"]
mock_get = mock.Mock()
mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
mock_add_dir = mock.Mock()
mock_enable = mock.Mock()
self.config.parser.find_dir = mock_find
self.config.parser.get_arg = mock_get
self.config.parser.add_dir_to_ifmodssl = mock_add_dir
self.config.enable_mod = mock_enable
# Test Listen statements with specific ip listeed
self.config.prepare_server_https("443")
# Should only be 2 here, as the third interface already listens to the correct port
self.assertEqual(mock_add_dir.call_count, 2)
# Check argument to new Listen statements
self.assertEqual(mock_add_dir.call_args_list[0][0][2], ["1.2.3.4:443"])
self.assertEqual(mock_add_dir.call_args_list[1][0][2], ["[::1]:443"])
# Reset return lists and inputs
mock_add_dir.reset_mock()
mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"]
# Test
self.config.prepare_server_https("8080", temp=True)
self.assertEqual(mock_add_dir.call_count, 3)
self.assertEqual(mock_add_dir.call_args_list[0][0][2], ["1.2.3.4:8080", "https"])
self.assertEqual(mock_add_dir.call_args_list[1][0][2], ["[::1]:8080", "https"])
self.assertEqual(mock_add_dir.call_args_list[2][0][2], ["1.1.1.1:8080", "https"])
def test_make_vhost_ssl(self):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
+1 -1
View File
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
version = '0.2.0.dev0'
install_requires = [
'acme=={0}'.format(version),
+2 -2
View File
@@ -147,9 +147,9 @@ then
elif uname | grep -iq FreeBSD ; then
ExperimentalBootstrap "FreeBSD" freebsd.sh "$SUDO"
elif uname | grep -iq Darwin ; then
ExperimentalBootstrap "Mac OS X" mac.sh
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
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
+1 -1
View File
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
version = '0.2.0.dev0'
install_requires = [
'letsencrypt=={0}'.format(version),
+1 -1
View File
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
version = '0.2.0.dev0'
install_requires = [
'acme=={0}'.format(version),
-5
View File
@@ -1,5 +0,0 @@
This is a PREVIEW RELEASE of a client application for the Let's Encrypt certificate authority and other services using the ACME protocol. The Let's Encrypt certificate authority is NOT YET ISSUING CERTIFICATES TO THE PUBLIC.
Until publicly-trusted certificates can be issued by Let's Encrypt, this software CANNOT OBTAIN A PUBLICLY-TRUSTED CERTIFICATE FOR YOUR WEB SERVER. You should only use this program if you are a developer interested in experimenting with the ACME protocol or in helping to improve this software. If you want to configure your web site with HTTPS in the meantime, please obtain a certificate from a different authority.
For updates on the status of Let's Encrypt, please visit the Let's Encrypt home page at https://letsencrypt.org/.
+1 -1
View File
@@ -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.1.0.dev0'
__version__ = '0.2.0.dev0'
+31 -14
View File
@@ -9,7 +9,6 @@ import json
import logging
import logging.handlers
import os
import pkg_resources
import sys
import time
import traceback
@@ -304,6 +303,14 @@ def _report_new_cert(cert_path, fullchain_path):
.format(and_chain, path, expiry))
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
def _suggest_donate():
"Suggest a donation to support Let's Encrypt"
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 _auth_from_domains(le_client, config, domains):
"""Authenticate and enroll certificate."""
@@ -473,6 +480,8 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo
else:
display_ops.success_renewal(domains)
_suggest_donate()
def obtain_cert(args, config, plugins):
"""Authenticate & obtain cert, but do not install it."""
@@ -502,6 +511,8 @@ def obtain_cert(args, config, plugins):
domains = _find_domains(args, installer)
_auth_from_domains(le_client, config, domains)
_suggest_donate()
def install(args, config, plugins):
"""Install a previously obtained cert in a server."""
@@ -750,6 +761,20 @@ class HelpfulArgumentParser(object):
kwargs["help"] = argparse.SUPPRESS
self.parser.add_argument(*args, **kwargs)
def add_deprecated_argument(self, argument_name, num_args):
"""Adds a deprecated argument with the name argument_name.
Deprecated arguments are not shown in the help. If they are used
on the command line, a warning is shown stating that the
argument is deprecated and no other action is taken.
:param str argument_name: Name of deprecated argument.
:param int nargs: Number of arguments the option takes.
"""
le_util.add_deprecated_argument(
self.parser.add_argument, argument_name, num_args)
def add_group(self, topic, **kwargs):
"""
@@ -830,7 +855,7 @@ def prepare_and_parse_args(plugins, args):
"email address. This is strongly discouraged, because in the "
"event of key loss or account compromise you will irrevocably "
"lose access to your account. You will also be unable to receive "
"notice about impending expiration of revocation of your "
"notice about impending expiration or revocation of your "
"certificates. Updates to the Subscriber Agreement will still "
"affect you, and will be effective 14 days after posting an "
"update to the web site.")
@@ -858,10 +883,7 @@ def prepare_and_parse_args(plugins, args):
helpful.add(
"automation", "--renew-by-default", action="store_true",
help="Select renewal by default when domains are a superset of a "
"a previously attained cert")
helpful.add(
"automation", "--agree-dev-preview", action="store_true",
help="Agree to the Let's Encrypt Developer Preview Disclaimer")
"previously attained cert")
helpful.add(
"automation", "--agree-tos", dest="tos", action="store_true",
help="Agree to the Let's Encrypt Subscriber Agreement")
@@ -926,6 +948,8 @@ 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_deprecated_argument("--agree-dev-preview", 0)
_create_subparsers(helpful)
_paths_parser(helpful)
# _plugins_parsing should be the last thing to act upon the main
@@ -1055,7 +1079,7 @@ def _plugins_parsing(helpful, plugins):
helpful.add("webroot", "-w", "--webroot-path", 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"
" precededed it. For instance: `-w /var/www/example -d example.com -d "
" 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`")
parse_dict = lambda s: dict(json.loads(s))
# --webroot-map still has some awkward properties, so it is undocumented
@@ -1245,13 +1269,6 @@ def main(cli_args=sys.argv[1:]):
zope.component.provideUtility(report)
atexit.register(report.atexit_print_messages)
# TODO: remove developer preview prompt for the launch
if not config.agree_dev_preview:
disclaimer = pkg_resources.resource_string("letsencrypt", "DISCLAIMER")
if not zope.component.getUtility(interfaces.IDisplay).yesno(
disclaimer, "Agree", "Cancel"):
raise errors.Error("Must agree to TOS")
if not os.geteuid() == 0:
logger.warning(
"Root (sudo) is required to run most of letsencrypt functionality.")
+3 -29
View File
@@ -1,13 +1,13 @@
"""Let's Encrypt user-supplied configuration."""
import os
import urlparse
import re
import zope.interface
from letsencrypt import constants
from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt import le_util
class NamespaceConfig(object):
@@ -123,31 +123,5 @@ def check_config_sanity(config):
# Domain checks
if config.namespace.domains is not None:
_check_config_domain_sanity(config.namespace.domains)
def _check_config_domain_sanity(domains):
"""Helper method for check_config_sanity which validates
domain flag values and errors out if the requirements are not met.
:param domains: List of domains
:type domains: `list` of `string`
:raises ConfigurationError: for invalid domains and cases where Let's
Encrypt currently will not issue certificates
"""
# Check if there's a wildcard domain
if any(d.startswith("*.") for d in domains):
raise errors.ConfigurationError(
"Wildcard domains are not supported")
# Punycode
if any("xn--" in d for d in domains):
raise errors.ConfigurationError(
"Punycode domains are not supported")
# FQDN checks from
# http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
# Characters used, domain parts < 63 chars, tld > 1 < 64 chars
# first and last char is not "-"
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,63}$")
if any(True for d in domains if not fqdn.match(d)):
raise errors.ConfigurationError("Requested domain is not a FQDN")
for domain in config.namespace.domains:
le_util.check_domain_sanity(domain)
+1 -1
View File
@@ -16,7 +16,7 @@ CLI_DEFAULTS = dict(
"letsencrypt", "cli.ini"),
],
verbose_count=-(logging.WARNING / 10),
server="https://acme-staging.api.letsencrypt.org/directory",
server="https://acme-v01.api.letsencrypt.org/directory",
rsa_key_size=2048,
rollback_checkpoints=1,
config_dir="/etc/letsencrypt",
+20 -2
View File
@@ -4,6 +4,7 @@ import os
import zope.component
from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt import le_util
from letsencrypt.display import util as display_util
@@ -186,7 +187,8 @@ def choose_names(installer):
logger.debug("No installer, picking names manually")
return _choose_names_manually()
names = list(installer.get_all_names())
domains = list(installer.get_all_names())
names = get_valid_domains(domains)
if not names:
manual = util(interfaces.IDisplay).yesno(
@@ -207,6 +209,22 @@ def choose_names(installer):
else:
return []
def get_valid_domains(domains):
"""Helper method for choose_names that implements basic checks
on domain names
:param list domains: Domain names to validate
:return: List of valid domains
:rtype: list
"""
valid_domains = []
for domain in domains:
try:
le_util.check_domain_sanity(domain)
valid_domains.append(domain)
except errors.ConfigurationError:
continue
return valid_domains
def _filter_names(names):
"""Determine which names the user would like to select from a list.
@@ -245,7 +263,7 @@ def success_installation(domains):
"""
util(interfaces.IDisplay).notification(
"Congratulations! You have successfully enabled {0}!{1}{1}"
"Congratulations! You have successfully enabled {0}{1}{1}"
"You should test your configuration at:{1}{2}".format(
_gen_https_names(domains),
os.linesep,
+34
View File
@@ -280,3 +280,37 @@ def add_deprecated_argument(add_argument, argument_name, nargs):
add_argument(argument_name, action=ShowWarning,
help=argparse.SUPPRESS, nargs=nargs)
def check_domain_sanity(domain):
"""Method which validates domain value and errors out if
the requirements are not met.
:param domain: Domain to check
:type domains: `string`
:raises ConfigurationError: for invalid domains and cases where Let's
Encrypt currently will not issue certificates
"""
# Check if there's a wildcard domain
if domain.startswith("*."):
raise errors.ConfigurationError(
"Wildcard domains are not supported")
# Punycode
if "xn--" in domain:
raise errors.ConfigurationError(
"Punycode domains are not presently supported")
# Unicode
try:
domain.encode('ascii')
except UnicodeDecodeError:
raise errors.ConfigurationError(
"Internationalized domain names are not presently supported")
# FQDN checks from
# http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
# Characters used, domain parts < 63 chars, tld > 1 < 64 chars
# first and last char is not "-"
fqdn = re.compile("^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,63}$")
if not fqdn.match(domain):
raise errors.ConfigurationError("Requested domain is not a FQDN")
+22 -17
View File
@@ -39,25 +39,27 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.config_dir = os.path.join(self.tmp_dir, 'config')
self.work_dir = os.path.join(self.tmp_dir, 'work')
self.logs_dir = os.path.join(self.tmp_dir, 'logs')
self.standard_args = ['--text', '--config-dir', self.config_dir,
'--work-dir', self.work_dir, '--logs-dir',
self.logs_dir, '--agree-dev-preview']
self.standard_args = ['--config-dir', self.config_dir,
'--work-dir', self.work_dir,
'--logs-dir', self.logs_dir, '--text']
def tearDown(self):
shutil.rmtree(self.tmp_dir)
def _call(self, args):
"Run the cli with output streams and actual client mocked out"
with mock.patch('letsencrypt.cli.client') as client:
ret, stdout, stderr = self._call_no_clientmock(args)
return ret, stdout, stderr, client
with mock.patch('letsencrypt.cli._suggest_donate'):
with mock.patch('letsencrypt.cli.client') as client:
ret, stdout, stderr = self._call_no_clientmock(args)
return ret, stdout, stderr, client
def _call_no_clientmock(self, args):
"Run the client with output streams mocked out"
args = self.standard_args + args
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
with mock.patch('letsencrypt.cli._suggest_donate'):
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
return ret, stdout, stderr
def _call_stdout(self, args):
@@ -66,9 +68,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
caller.
"""
args = self.standard_args + args
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
with mock.patch('letsencrypt.cli._suggest_donate'):
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
return ret, None, stderr, client
def test_no_flags(self):
@@ -180,8 +183,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
def test_configurator_selection(self, mock_exe_exists):
mock_exe_exists.return_value = True
real_plugins = disco.PluginsRegistry.find_all()
args = ['--agree-dev-preview', '--apache',
'--authenticator', 'standalone']
args = ['--apache', '--authenticator', 'standalone']
# This needed two calls to find_all(), which we're avoiding for now
# because of possible side effects:
@@ -360,9 +362,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
namespace = cli.prepare_and_parse_args(plugins, webroot_map_args)
self.assertEqual(namespace.webroot_map, {u"eg.com": u"/tmp"})
@mock.patch('letsencrypt.cli._suggest_donate')
@mock.patch('letsencrypt.crypto_util.notAfter')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter):
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter, _suggest):
cert_path = '/etc/letsencrypt/live/foo.bar'
date = '1970-01-01'
mock_notAfter().date.return_value = date
@@ -391,10 +394,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_init.return_value = mock_client
self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly'])
@mock.patch('letsencrypt.cli._suggest_donate')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')
@mock.patch('letsencrypt.cli._init_le_client')
def test_certonly_renewal(self, mock_init, mock_renewal, mock_get_utility):
def test_certonly_renewal(self, mock_init, mock_renewal, mock_get_utility, _suggest):
cert_path = '/etc/letsencrypt/live/foo.bar/cert.pem'
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
@@ -416,13 +420,14 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue(
chain_path in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli._suggest_donate')
@mock.patch('letsencrypt.crypto_util.notAfter')
@mock.patch('letsencrypt.cli.display_ops.pick_installer')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._init_le_client')
@mock.patch('letsencrypt.cli.record_chosen_plugins')
def test_certonly_csr(self, _rec, mock_init, mock_get_utility,
mock_pick_installer, mock_notAfter):
mock_pick_installer, mock_notAfter, _suggest):
cert_path = '/etc/letsencrypt/live/blahcert.pem'
date = '1970-01-01'
mock_notAfter().date.return_value = date
+12
View File
@@ -1,3 +1,4 @@
# coding=utf-8
"""Test letsencrypt.display.ops."""
import os
import sys
@@ -385,6 +386,17 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(self._call(self.mock_install), [])
def test_get_valid_domains(self):
from letsencrypt.display.ops import get_valid_domains
all_valid = ["example.com", "second.example.com",
"also.example.com"]
all_invalid = ["xn--ls8h.tld", "*.wildcard.com", "notFQDN",
"uniçodé.com"]
two_valid = ["example.com", "xn--ls8h.tld", "also.example.com"]
self.assertEqual(get_valid_domains(all_valid), all_valid)
self.assertEqual(get_valid_domains(all_invalid), [])
self.assertEqual(len(get_valid_domains(two_valid)), 2)
class SuccessInstallationTest(unittest.TestCase):
# pylint: disable=too-few-public-methods
+1 -1
View File
@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
version = '0.2.0.dev0'
install_requires = [
'setuptools', # pkg_resources
@@ -0,0 +1 @@
SSLRequire %{SSL_CLIENT_S_DN_CN} in {"foo@bar.com", "bar@foo.com"}
@@ -0,0 +1,28 @@
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerAdmin info@somethingnewentertainment.com
ServerName somethingnewentertainment.com
DocumentRoot /var/www/html
ErrorLog /var/log/apache2/error.log
CustomLog /var/log/apache2/access.log combined
SSLEngine on
SSLProtocol all -SSLv2 -SSLv3
SSLHonorCipherOrder on
SSLCipherSuite "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EEC DH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRS A RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS !RC4"
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
BrowserMatch "MSIE [2-6]" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
</VirtualHost> </IfModule>
-1
View File
@@ -21,7 +21,6 @@ letsencrypt_test () {
$store_flags \
--text \
--no-redirect \
--agree-dev-preview \
--agree-tos \
--register-unsafely-without-email \
--renew-by-default \
+1 -1
View File
@@ -16,7 +16,7 @@ fi
cover () {
if [ "$1" = "letsencrypt" ]; then
min=98
min=97
elif [ "$1" = "acme" ]; then
min=100
elif [ "$1" = "letsencrypt_apache" ]; then