mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
Merge branch 'master' into autopeep
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ letsencrypt.log
|
||||
# letstest
|
||||
tests/letstest/letest-*/
|
||||
tests/letstest/*.pem
|
||||
tests/letstest/venv/
|
||||
|
||||
26
.travis.yml
26
.travis.yml
@@ -30,28 +30,27 @@ matrix:
|
||||
env: TOXENV=py26 BOULDER_INTEGRATION=1
|
||||
- python: "2.6"
|
||||
env: TOXENV=py26-oldest BOULDER_INTEGRATION=1
|
||||
# Disabled for now due to requiring sudo -> causing more boulder integration
|
||||
# DNS timeouts :(
|
||||
# - python: "2.7"
|
||||
# env: TOXENV=apacheconftest
|
||||
- python: "2.7"
|
||||
env: TOXENV=apacheconftest
|
||||
sudo: required
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27 BOULDER_INTEGRATION=1
|
||||
- python: "2.7"
|
||||
env: TOXENV=py27-oldest BOULDER_INTEGRATION=1
|
||||
- python: "2.7"
|
||||
env: TOXENV=cover
|
||||
- python: "2.7"
|
||||
env: TOXENV=lint
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto
|
||||
services: docker
|
||||
before_install:
|
||||
- python: "2.7"
|
||||
env: TOXENV=cover
|
||||
- python: "3.3"
|
||||
env: TOXENV=py33
|
||||
- python: "3.4"
|
||||
env: TOXENV=py34
|
||||
- python: "3.5"
|
||||
env: TOXENV=py35
|
||||
- sudo: required
|
||||
env: TOXENV=le_auto
|
||||
services: docker
|
||||
before_install:
|
||||
|
||||
# Only build pushes to the master branch, PRs, and branches beginning with
|
||||
# `test-`. This reduces the number of simultaneous Travis runs, which speeds
|
||||
@@ -65,9 +64,14 @@ branches:
|
||||
sudo: false
|
||||
|
||||
addons:
|
||||
# make sure simplehttp simple verification works (custom /etc/hosts)
|
||||
# Custom /etc/hosts required for simple verification of http-01
|
||||
# and tls-sni-01, and for letsencrypt_test_nginx
|
||||
hosts:
|
||||
- le.wtf
|
||||
- le1.wtf
|
||||
- le2.wtf
|
||||
- le3.wtf
|
||||
- nginx.wtf
|
||||
mariadb: "10.0"
|
||||
apt:
|
||||
sources:
|
||||
|
||||
@@ -58,7 +58,7 @@ RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \
|
||||
-e /opt/letsencrypt/src/letsencrypt-nginx \
|
||||
-e /opt/letsencrypt/src/letshelp-letsencrypt \
|
||||
-e /opt/letsencrypt/src/letsencrypt-compatibility-test \
|
||||
-e /opt/letsencrypt/src[dev,docs,testing]
|
||||
-e /opt/letsencrypt/src[dev,docs]
|
||||
|
||||
# install in editable mode (-e) to save space: it's not possible to
|
||||
# "rm -rf /opt/letsencrypt/src" (it's stays in the underlaying image);
|
||||
|
||||
@@ -51,11 +51,11 @@ client will guide you through the process of obtaining and installing certs
|
||||
interactively.
|
||||
|
||||
You can also tell it exactly what you want it to do from the command line.
|
||||
For instance, if you want to obtain a cert for ``thing.com``,
|
||||
``www.thing.com``, and ``otherthing.net``, using the Apache plugin to both
|
||||
For instance, if you want to obtain a cert for ``example.com``,
|
||||
``www.example.com``, and ``other.example.net``, using the Apache plugin to both
|
||||
obtain and install the certs, you could do this::
|
||||
|
||||
./letsencrypt-auto --apache -d thing.com -d www.thing.com -d otherthing.net
|
||||
./letsencrypt-auto --apache -d example.com -d www.example.com -d other.example.net
|
||||
|
||||
(The first time you run the command, it will make an account, and ask for an
|
||||
email and agreement to the Let's Encrypt Subscriber Agreement; you can
|
||||
@@ -64,7 +64,7 @@ automate those with ``--email`` and ``--agree-tos``)
|
||||
If you want to use a webserver that doesn't have full plugin support yet, you
|
||||
can still use "standalone" or "webroot" plugins to obtain a certificate::
|
||||
|
||||
./letsencrypt-auto certonly --standalone --email admin@thing.com -d thing.com -d www.thing.com -d otherthing.net
|
||||
./letsencrypt-auto certonly --standalone --email admin@example.com -d example.com -d www.example.com -d other.example.net
|
||||
|
||||
|
||||
Understanding the client in more depth
|
||||
|
||||
9
Vagrantfile
vendored
9
Vagrantfile
vendored
@@ -5,10 +5,19 @@
|
||||
VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
# Setup instructions from docs/contributing.rst
|
||||
# Script installs dependencies for tox and boulder integration
|
||||
$ubuntu_setup_script = <<SETUP_SCRIPT
|
||||
cd /vagrant
|
||||
./letsencrypt-auto-source/letsencrypt-auto --os-packages-only
|
||||
./tools/venv.sh
|
||||
wget https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz -P /tmp/
|
||||
sudo tar -C /usr/local -xzf /tmp/go1.5.3.linux-amd64.tar.gz
|
||||
if ! grep -Fxq "export GOROOT=/usr/local/go" /home/vagrant/.profile ; then echo "export GOROOT=/usr/local/go" >> /home/vagrant/.profile; fi
|
||||
if ! grep -Fxq "export PATH=\\$GOROOT/bin:\\$PATH" /home/vagrant/.profile ; then echo "export PATH=\\$GOROOT/bin:\\$PATH" >> /home/vagrant/.profile; fi
|
||||
if ! grep -Fxq "export GOPATH=\\$HOME/go" /home/vagrant/.profile ; then echo "export GOPATH=\\$HOME/go" >> /home/vagrant/.profile; fi
|
||||
if ! grep -Fxq "cd /vagrant/; ./tests/boulder-start.sh &" /etc/rc.local ; then sed -i -e '$i \cd /vagrant/; ./tests/boulder-start.sh &\n' /etc/rc.local; fi
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
sudo -E apt-get -q -y install git make libltdl-dev mariadb-server rabbitmq-server nginx-light
|
||||
SETUP_SCRIPT
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
4
acme/.pep8
Normal file
4
acme/.pep8
Normal file
@@ -0,0 +1,4 @@
|
||||
[pep8]
|
||||
# E265 block comment should start with '# '
|
||||
# E501 line too long (X > 79 characters)
|
||||
ignore = E265,E501
|
||||
@@ -1,6 +1,7 @@
|
||||
"""ACME client API."""
|
||||
import collections
|
||||
import datetime
|
||||
from email.utils import parsedate_tz
|
||||
import heapq
|
||||
import logging
|
||||
import time
|
||||
@@ -11,7 +12,6 @@ from six.moves import http_client # pylint: disable=import-error
|
||||
import OpenSSL
|
||||
import requests
|
||||
import sys
|
||||
import werkzeug
|
||||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
@@ -180,40 +180,41 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
||||
raise errors.UnexpectedUpdate(authzr)
|
||||
return authzr
|
||||
|
||||
def request_challenges(self, identifier, new_authzr_uri):
|
||||
def request_challenges(self, identifier, new_authzr_uri=None):
|
||||
"""Request challenges.
|
||||
|
||||
:param identifier: Identifier to be challenged.
|
||||
:type identifier: `.messages.Identifier`
|
||||
|
||||
:param str new_authzr_uri: new-authorization URI
|
||||
:param .messages.Identifier identifier: Identifier to be challenged.
|
||||
:param str new_authzr_uri: ``new-authorization`` URI. If omitted,
|
||||
will default to value found in ``directory``.
|
||||
|
||||
:returns: Authorization Resource.
|
||||
:rtype: `.AuthorizationResource`
|
||||
|
||||
"""
|
||||
new_authz = messages.NewAuthorization(identifier=identifier)
|
||||
response = self.net.post(new_authzr_uri, new_authz)
|
||||
response = self.net.post(self.directory.new_authz
|
||||
if new_authzr_uri is None else new_authzr_uri,
|
||||
new_authz)
|
||||
# TODO: handle errors
|
||||
assert response.status_code == http_client.CREATED
|
||||
return self._authzr_from_response(response, identifier)
|
||||
|
||||
def request_domain_challenges(self, domain, new_authz_uri):
|
||||
def request_domain_challenges(self, domain, new_authzr_uri=None):
|
||||
"""Request challenges for domain names.
|
||||
|
||||
This is simply a convenience function that wraps around
|
||||
`request_challenges`, but works with domain names instead of
|
||||
generic identifiers.
|
||||
generic identifiers. See ``request_challenges`` for more
|
||||
documentation.
|
||||
|
||||
:param str domain: Domain name to be challenged.
|
||||
:param str new_authzr_uri: new-authorization URI
|
||||
|
||||
:returns: Authorization Resource.
|
||||
:rtype: `.AuthorizationResource`
|
||||
|
||||
"""
|
||||
return self.request_challenges(messages.Identifier(
|
||||
typ=messages.IDENTIFIER_FQDN, value=domain), new_authz_uri)
|
||||
typ=messages.IDENTIFIER_FQDN, value=domain), new_authzr_uri)
|
||||
|
||||
def answer_challenge(self, challb, response):
|
||||
"""Answer challenge.
|
||||
@@ -247,6 +248,9 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
||||
def retry_after(cls, response, default):
|
||||
"""Compute next `poll` time based on response ``Retry-After`` header.
|
||||
|
||||
Handles integers and various datestring formats per
|
||||
https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.37
|
||||
|
||||
:param requests.Response response: Response from `poll`.
|
||||
:param int default: Default value (in seconds), used when
|
||||
``Retry-After`` header is not present or invalid.
|
||||
@@ -259,12 +263,16 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
||||
try:
|
||||
seconds = int(retry_after)
|
||||
except ValueError:
|
||||
# pylint: disable=no-member
|
||||
decoded = werkzeug.parse_date(retry_after) # RFC1123
|
||||
if decoded is None:
|
||||
seconds = default
|
||||
else:
|
||||
return decoded
|
||||
# The RFC 2822 parser handles all of RFC 2616's cases in modern
|
||||
# environments (primarily HTTP 1.1+ but also py27+)
|
||||
when = parsedate_tz(retry_after)
|
||||
if when is not None:
|
||||
try:
|
||||
tz_secs = datetime.timedelta(when[-1] if when[-1] else 0)
|
||||
return datetime.datetime(*when[:7]) - tz_secs
|
||||
except (ValueError, OverflowError):
|
||||
pass
|
||||
seconds = default
|
||||
|
||||
return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
|
||||
|
||||
@@ -356,7 +364,7 @@ class Client(object): # pylint: disable=too-many-instance-attributes
|
||||
attempts = collections.defaultdict(int)
|
||||
exhausted = set()
|
||||
|
||||
# priority queue with datetime (based on Retry-After) as key,
|
||||
# priority queue with datetime.datetime (based on Retry-After) as key,
|
||||
# and original Authorization Resource as value
|
||||
waiting = [(datetime.datetime.now(), authzr) for authzr in authzrs]
|
||||
# mapping between original Authorization Resource and the most
|
||||
|
||||
@@ -38,6 +38,8 @@ class ClientTest(unittest.TestCase):
|
||||
'https://www.letsencrypt-demo.org/acme/new-reg',
|
||||
messages.Revocation:
|
||||
'https://www.letsencrypt-demo.org/acme/revoke-cert',
|
||||
messages.NewAuthorization:
|
||||
'https://www.letsencrypt-demo.org/acme/new-authz',
|
||||
})
|
||||
|
||||
from acme.client import Client
|
||||
@@ -142,7 +144,7 @@ class ClientTest(unittest.TestCase):
|
||||
regr = self.client.update_registration.call_args[0][0]
|
||||
self.assertEqual(self.regr.terms_of_service, regr.body.agreement)
|
||||
|
||||
def test_request_challenges(self):
|
||||
def _prepare_response_for_request_challenges(self):
|
||||
self.response.status_code = http_client.CREATED
|
||||
self.response.headers['Location'] = self.authzr.uri
|
||||
self.response.json.return_value = self.authz.to_json()
|
||||
@@ -150,10 +152,20 @@ class ClientTest(unittest.TestCase):
|
||||
'next': {'url': self.authzr.new_cert_uri},
|
||||
}
|
||||
|
||||
self.client.request_challenges(self.identifier, self.authzr.uri)
|
||||
# TODO: test POST call arguments
|
||||
def test_request_challenges(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.client.request_challenges(self.identifier)
|
||||
self.net.post.assert_called_once_with(
|
||||
self.directory.new_authz,
|
||||
messages.NewAuthorization(identifier=self.identifier))
|
||||
|
||||
# TODO: split here and separate test
|
||||
def test_requets_challenges_custom_uri(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.client.request_challenges(self.identifier, 'URI')
|
||||
self.net.post.assert_called_once_with('URI', mock.ANY)
|
||||
|
||||
def test_request_challenges_unexpected_update(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.response.json.return_value = self.authz.update(
|
||||
identifier=self.identifier.update(value='foo')).to_json()
|
||||
self.assertRaises(
|
||||
@@ -162,15 +174,20 @@ class ClientTest(unittest.TestCase):
|
||||
|
||||
def test_request_challenges_missing_next(self):
|
||||
self.response.status_code = http_client.CREATED
|
||||
self.assertRaises(
|
||||
errors.ClientError, self.client.request_challenges,
|
||||
self.identifier, self.regr)
|
||||
self.assertRaises(errors.ClientError, self.client.request_challenges,
|
||||
self.identifier)
|
||||
|
||||
def test_request_domain_challenges(self):
|
||||
self.client.request_challenges = mock.MagicMock()
|
||||
self.assertEqual(
|
||||
self.client.request_challenges(self.identifier),
|
||||
self.client.request_domain_challenges('example.com', self.regr))
|
||||
self.client.request_domain_challenges('example.com'))
|
||||
|
||||
def test_request_domain_challenges_custom_uri(self):
|
||||
self.client.request_challenges = mock.MagicMock()
|
||||
self.assertEqual(
|
||||
self.client.request_challenges(self.identifier, 'URI'),
|
||||
self.client.request_domain_challenges('example.com', 'URI'))
|
||||
|
||||
def test_answer_challenge(self):
|
||||
self.response.links['up'] = {'url': self.challr.authzr_uri}
|
||||
@@ -205,6 +222,17 @@ class ClientTest(unittest.TestCase):
|
||||
datetime.datetime(2015, 3, 27, 0, 0, 10),
|
||||
self.client.retry_after(response=self.response, default=10))
|
||||
|
||||
@mock.patch('acme.client.datetime')
|
||||
def test_retry_after_overflow(self, dt_mock):
|
||||
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
|
||||
dt_mock.timedelta = datetime.timedelta
|
||||
dt_mock.datetime.side_effect = datetime.datetime
|
||||
|
||||
self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST"
|
||||
self.assertEqual(
|
||||
datetime.datetime(2015, 3, 27, 0, 0, 10),
|
||||
self.client.retry_after(response=self.response, default=10))
|
||||
|
||||
@mock.patch('acme.client.datetime')
|
||||
def test_retry_after_seconds(self, dt_mock):
|
||||
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
|
||||
|
||||
@@ -42,7 +42,7 @@ csr = OpenSSL.crypto.load_certificate_request(
|
||||
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
|
||||
'acme', os.path.join('testdata', 'csr.der')))
|
||||
try:
|
||||
acme.request_issuance(csr, (authzr,))
|
||||
acme.request_issuance(jose.util.ComparableX509(csr), (authzr,))
|
||||
except messages.Error as error:
|
||||
print ("This script is doomed to fail as no authorization "
|
||||
"challenges are ever solved. Error from server: {0}".format(error))
|
||||
|
||||
@@ -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 = [
|
||||
@@ -20,7 +20,6 @@ install_requires = [
|
||||
'requests',
|
||||
'setuptools', # pkg_resources
|
||||
'six',
|
||||
'werkzeug',
|
||||
]
|
||||
|
||||
# env markers in extras_require cause problems with older pip: #517
|
||||
@@ -34,17 +33,18 @@ if sys.version_info < (2, 7):
|
||||
else:
|
||||
install_requires.append('mock')
|
||||
|
||||
dev_extras = [
|
||||
'nose',
|
||||
'pep8',
|
||||
'tox',
|
||||
]
|
||||
|
||||
docs_extras = [
|
||||
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
|
||||
'sphinx_rtd_theme',
|
||||
'sphinxcontrib-programoutput',
|
||||
]
|
||||
|
||||
testing_extras = [
|
||||
'nose',
|
||||
'tox',
|
||||
]
|
||||
|
||||
|
||||
setup(
|
||||
name='acme',
|
||||
@@ -74,8 +74,8 @@ setup(
|
||||
include_package_data=True,
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'dev': dev_extras,
|
||||
'docs': docs_extras,
|
||||
'testing': testing_extras,
|
||||
},
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
|
||||
@@ -139,7 +139,7 @@ client's priorities. The Mozilla security team is likely to have more
|
||||
resources and expertise to bring to bear on evaluating reasons why its
|
||||
recommendations should be updated.
|
||||
|
||||
The Let's Encrpyt project will entertain proposals to create a *very*
|
||||
The Let's Encrypt project will entertain proposals to create a *very*
|
||||
small number of alternative configurations (apart from Modern,
|
||||
Intermediate, and Old) that there's reason to believe would be widely
|
||||
used by sysadmins; this would usually be a preferable course to modifying
|
||||
|
||||
@@ -96,6 +96,14 @@ Integration testing with the boulder CA
|
||||
Generally it is sufficient to open a pull request and let Github and Travis run
|
||||
integration tests for you.
|
||||
|
||||
However, if you prefer to run tests, you can use Vagrant, using the Vagrantfile
|
||||
in Let's Encrypt's repository. To execute the tests on a Vagrant box, the only
|
||||
command you are required to run is::
|
||||
|
||||
./tests/boulder-integration.sh
|
||||
|
||||
Otherwise, please follow the following instructions.
|
||||
|
||||
Mac OS X users: Run ``./tests/mac-bootstrap.sh`` instead of
|
||||
``boulder-start.sh`` to install dependencies, configure the
|
||||
environment, and start boulder.
|
||||
@@ -127,9 +135,9 @@ Afterwards, you'd be able to start Boulder_ using the following command::
|
||||
|
||||
The script will download, compile and run the executable; please be
|
||||
patient - it will take some time... Once its ready, you will see
|
||||
``Server running, listening on 127.0.0.1:4000...``. Add an
|
||||
``/etc/hosts`` entry pointing ``le.wtf`` to 127.0.0.1. You may now
|
||||
run (in a separate terminal)::
|
||||
``Server running, listening on 127.0.0.1:4000...``. Add ``/etc/hosts``
|
||||
entries pointing ``le.wtf``, ``le1.wtf``, ``le2.wtf``, ``le3.wtf``
|
||||
and ``nginx.wtf`` to 127.0.0.1. You may now run (in a separate terminal)::
|
||||
|
||||
./tests/boulder-integration.sh && echo OK || echo FAIL
|
||||
|
||||
|
||||
202
docs/using.rst
202
docs/using.rst
@@ -49,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
|
||||
@@ -71,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
|
||||
@@ -91,6 +93,46 @@ This automates both obtaining *and* installing certs on an Apache
|
||||
webserver. To specify this plugin on the command line, simply include
|
||||
``--apache``.
|
||||
|
||||
Webroot
|
||||
-------
|
||||
|
||||
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, 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/other -d other.example.net -d another.other.example.net``
|
||||
|
||||
would obtain a single certificate for all of those names, using the
|
||||
``/var/www/example`` webroot directory for the first two, and
|
||||
``/var/www/other`` for the second two.
|
||||
|
||||
The webroot plugin works by creating a temporary file for each of your requested
|
||||
domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's
|
||||
Encrypt validation server makes HTTP requests to validate that the DNS for each
|
||||
requested domain resolves to the server running letsencrypt. An example request
|
||||
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. If ``/.well-known`` is treated specially by
|
||||
your webserver configuration, you might need to modify the configuration
|
||||
to ensure that files inside ``/.well-known/acme-challenge`` are served by
|
||||
the webserver.
|
||||
|
||||
Standalone
|
||||
----------
|
||||
|
||||
@@ -104,39 +146,10 @@ 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 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.
|
||||
|
||||
The webroot plugin works by creating a temporary file for each of your requested
|
||||
domains in ``${webroot-path}/.well-known/acme-challenge``. Then the Let's
|
||||
Encrypt validation server makes HTTP requests to validate that the DNS for each
|
||||
requested domain resolves to the server running letsencrypt. An example request
|
||||
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.
|
||||
|
||||
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
|
||||
------
|
||||
@@ -146,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
|
||||
-----
|
||||
@@ -157,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
|
||||
@@ -171,21 +185,61 @@ Renewal
|
||||
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 ``--force-renew``
|
||||
flag may be helpful for automating renewal; it causes the expiration time
|
||||
of the certificate(s) to be ignored when considering 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:
|
||||
@@ -375,6 +429,58 @@ 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.
|
||||
|
||||
**Fedora**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sudo dnf install letsencrypt
|
||||
|
||||
**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
|
||||
|
||||
@@ -9,10 +9,10 @@ from letsencrypt import interfaces
|
||||
from letsencrypt.plugins import common
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(common.Plugin):
|
||||
"""Example Authenticator."""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Example Authenticator plugin"
|
||||
|
||||
@@ -20,10 +20,10 @@ class Authenticator(common.Plugin):
|
||||
# "self" as first argument, e.g. def prepare(self)...
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IInstaller)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Installer(common.Plugin):
|
||||
"""Example Installer."""
|
||||
zope.interface.implements(interfaces.IInstaller)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Example Installer plugin"
|
||||
|
||||
|
||||
@@ -45,9 +45,8 @@ autoload xfm
|
||||
let dels (s:string) = del s s
|
||||
|
||||
(* deal with continuation lines *)
|
||||
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "
|
||||
|
||||
let sep_osp = Sep.opt_space
|
||||
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)/ " "
|
||||
let sep_osp = del /([ \t]*|[ \t]*\\\\\r?\n[ \t]*)/ ""
|
||||
let sep_eq = del /[ \t]*=[ \t]*/ "="
|
||||
|
||||
let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
|
||||
@@ -60,7 +59,7 @@ let indent = Util.indent
|
||||
|
||||
(* borrowed from shellvars.aug *)
|
||||
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'/
|
||||
let char_arg_sec = /[^ '"\t\r\n>]|\\\\"|\\\\'/
|
||||
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'/
|
||||
let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/
|
||||
|
||||
let cdot = /\\\\./
|
||||
|
||||
@@ -60,6 +60,8 @@ logger = logging.getLogger(__name__)
|
||||
# sites-available doesn't allow immediate find_dir search even with save()
|
||||
# and load()
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""Apache configurator.
|
||||
@@ -80,8 +82,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
:ivar dict assoc: Mapping between domains and vhosts
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Apache Web Server - Alpha"
|
||||
|
||||
@@ -342,6 +342,21 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
self.assoc[target_name] = vhost
|
||||
return vhost
|
||||
|
||||
def included_in_wildcard(self, names, target_name):
|
||||
"""Helper function to see if alias is covered by wildcard"""
|
||||
target_name = target_name.split(".")[::-1]
|
||||
wildcards = [domain.split(".")[1:] for domain in names if domain.startswith("*")]
|
||||
for wildcard in wildcards:
|
||||
if len(wildcard) > len(target_name):
|
||||
continue
|
||||
for idx, segment in enumerate(wildcard[::-1]):
|
||||
if segment != target_name[idx]:
|
||||
break
|
||||
else:
|
||||
# https://docs.python.org/2/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops
|
||||
return True
|
||||
return False
|
||||
|
||||
def _find_best_vhost(self, target_name):
|
||||
"""Finds the best vhost for a target_name.
|
||||
|
||||
@@ -351,16 +366,21 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
:returns: VHost or None
|
||||
|
||||
"""
|
||||
# Points 4 - Servername SSL
|
||||
# Points 3 - Address name with SSL
|
||||
# Points 2 - Servername no SSL
|
||||
# Points 6 - Servername SSL
|
||||
# Points 5 - Wildcard SSL
|
||||
# Points 4 - Address name with SSL
|
||||
# Points 3 - Servername no SSL
|
||||
# Points 2 - Wildcard no SSL
|
||||
# Points 1 - Address name with no SSL
|
||||
best_candidate = None
|
||||
best_points = 0
|
||||
for vhost in self.vhosts:
|
||||
if vhost.modmacro is True:
|
||||
continue
|
||||
if target_name in vhost.get_names():
|
||||
names = vhost.get_names()
|
||||
if target_name in names:
|
||||
points = 3
|
||||
elif self.included_in_wildcard(names, target_name):
|
||||
points = 2
|
||||
elif any(addr.get_addr() == target_name for addr in vhost.addrs):
|
||||
points = 1
|
||||
@@ -370,7 +390,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
continue # pragma: no cover
|
||||
|
||||
if vhost.ssl:
|
||||
points += 2
|
||||
points += 3
|
||||
|
||||
if points > best_points:
|
||||
best_points = points
|
||||
@@ -895,9 +915,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
for addr in vhost.addrs:
|
||||
# In Apache 2.2, when a NameVirtualHost directive is not
|
||||
# set, "*" and "_default_" will conflict when sharing a port
|
||||
addrs = set((addr,))
|
||||
if addr.get_addr() in ("*", "_default_"):
|
||||
addrs = [obj.Addr((a, addr.get_port(),))
|
||||
for a in ("*", "_default_")]
|
||||
addrs.update(obj.Addr((a, addr.get_port(),))
|
||||
for a in ("*", "_default_"))
|
||||
|
||||
for test_vh in self.vhosts:
|
||||
if (vhost.filep != test_vh.filep and
|
||||
@@ -907,6 +928,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
self.add_name_vhost(addr)
|
||||
logger.info("Enabling NameVirtualHosts on %s", addr)
|
||||
need_to_save = True
|
||||
break
|
||||
|
||||
if need_to_save:
|
||||
self.save()
|
||||
@@ -1073,7 +1095,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
# even with save() and load()
|
||||
if not self._is_rewrite_engine_on(general_vh):
|
||||
self.parser.add_dir(general_vh.path, "RewriteEngine", "on")
|
||||
|
||||
names = ssl_vhost.get_names()
|
||||
for idx, name in enumerate(names):
|
||||
args = ["%{SERVER_NAME}", "={0}".format(name), "[OR]"]
|
||||
if idx == len(names) - 1:
|
||||
args.pop()
|
||||
self.parser.add_dir(general_vh.path, "RewriteCond", args)
|
||||
if self.get_version() >= (2, 3, 9):
|
||||
self.parser.add_dir(general_vh.path, "RewriteRule",
|
||||
constants.REWRITE_HTTPS_ARGS_WITH_END)
|
||||
@@ -1243,6 +1270,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
for http_vh in candidate_http_vhs:
|
||||
if http_vh.same_server(ssl_vhost):
|
||||
return http_vh
|
||||
# Third filter - if none with same names, return generic
|
||||
for http_vh in candidate_http_vhs:
|
||||
if http_vh.same_server(ssl_vhost, generic=True):
|
||||
return http_vh
|
||||
|
||||
return None
|
||||
|
||||
@@ -1429,7 +1460,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
||||
|
||||
"""
|
||||
self.config_test()
|
||||
logger.debug(self.reverter.view_config_changes(for_logging=True))
|
||||
self._reload()
|
||||
|
||||
def _reload(self):
|
||||
|
||||
@@ -54,6 +54,23 @@ CLI_DEFAULTS_GENTOO = dict(
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"letsencrypt_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
CLI_DEFAULTS_DARWIN = dict(
|
||||
server_root="/etc/apache2",
|
||||
vhost_root="/etc/apache2/other",
|
||||
vhost_files="*.conf",
|
||||
version_cmd=['/usr/sbin/httpd', '-v'],
|
||||
define_cmd=['/usr/sbin/httpd', '-t', '-D', 'DUMP_RUN_CFG'],
|
||||
restart_cmd=['apachectl', 'graceful'],
|
||||
conftest_cmd=['apachectl', 'configtest'],
|
||||
enmod=None,
|
||||
dismod=None,
|
||||
le_vhost_ext="-le-ssl.conf",
|
||||
handle_mods=False,
|
||||
handle_sites=False,
|
||||
challenge_location="/etc/apache2/other",
|
||||
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
|
||||
"letsencrypt_apache", "options-ssl-apache.conf")
|
||||
)
|
||||
CLI_DEFAULTS = {
|
||||
"debian": CLI_DEFAULTS_DEBIAN,
|
||||
"ubuntu": CLI_DEFAULTS_DEBIAN,
|
||||
@@ -61,7 +78,8 @@ CLI_DEFAULTS = {
|
||||
"centos linux": CLI_DEFAULTS_CENTOS,
|
||||
"fedora": CLI_DEFAULTS_CENTOS,
|
||||
"red hat enterprise linux server": CLI_DEFAULTS_CENTOS,
|
||||
"gentoo base system": CLI_DEFAULTS_GENTOO
|
||||
"gentoo base system": CLI_DEFAULTS_GENTOO,
|
||||
"darwin": CLI_DEFAULTS_DARWIN,
|
||||
}
|
||||
"""CLI defaults."""
|
||||
|
||||
|
||||
@@ -189,22 +189,28 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
|
||||
return True
|
||||
return False
|
||||
|
||||
def same_server(self, vhost):
|
||||
def same_server(self, vhost, generic=False):
|
||||
"""Determines if the vhost is the same 'server'.
|
||||
|
||||
Used in redirection - indicates whether or not the two virtual hosts
|
||||
serve on the exact same IP combinations, but different ports.
|
||||
The generic flag indicates that that we're trying to match to a
|
||||
default or generic vhost
|
||||
|
||||
.. todo:: Handle _default_
|
||||
|
||||
"""
|
||||
|
||||
if vhost.get_names() != self.get_names():
|
||||
return False
|
||||
if not generic:
|
||||
if vhost.get_names() != self.get_names():
|
||||
return False
|
||||
|
||||
# If equal and set is not empty... assume same server
|
||||
if self.name is not None or self.aliases:
|
||||
return True
|
||||
# If equal and set is not empty... assume same server
|
||||
if self.name is not None or self.aliases:
|
||||
return True
|
||||
# If we're looking for a generic vhost, don't return one with a ServerName
|
||||
elif self.name:
|
||||
return False
|
||||
|
||||
# Both sets of names are empty.
|
||||
|
||||
|
||||
@@ -477,7 +477,7 @@ class ApacheParser(object):
|
||||
# Note: This works for augeas globs, ie. *.conf
|
||||
if use_new:
|
||||
inc_test = self.aug.match(
|
||||
"/augeas/load/Httpd/incl [. ='%s']" % filepath)
|
||||
"/augeas/load/Httpd['%s' =~ glob(incl)]" % filepath)
|
||||
if not inc_test:
|
||||
# Load up files
|
||||
# This doesn't seem to work on TravisCI
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
|
||||
NameVirtualHost 0.0.0.0:7080
|
||||
NameVirtualHost [00000:000:000:000::0]:7080
|
||||
NameVirtualHost 0.0.0.0:7080
|
||||
|
||||
NameVirtualHost 127.0.0.1:7080
|
||||
NameVirtualHost 0.0.0.0:7081
|
||||
NameVirtualHost [0000:000:000:000::2]:7081
|
||||
NameVirtualHost 0.0.0.0:7081
|
||||
|
||||
NameVirtualHost 127.0.0.1:7081
|
||||
|
||||
ServerName "example.com"
|
||||
ServerAdmin "srv@example.com"
|
||||
|
||||
DocumentRoot /tmp
|
||||
|
||||
<IfModule mod_logio.c>
|
||||
LogFormat "%a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog
|
||||
</IfModule>
|
||||
<IfModule !mod_logio.c>
|
||||
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" plesklog
|
||||
</IfModule>
|
||||
|
||||
TraceEnable off
|
||||
|
||||
ServerTokens ProductOnly
|
||||
|
||||
<Directory "/var/www/vhosts">
|
||||
AllowOverride "All"
|
||||
Options SymLinksIfOwnerMatch
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
|
||||
<IfModule sapi_apache2.c>
|
||||
php_admin_flag engine off
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_admin_flag engine off
|
||||
</IfModule>
|
||||
|
||||
</Directory>
|
||||
|
||||
<Directory "/usr/lib/mailman">
|
||||
AllowOverride "All"
|
||||
Options SymLinksIfOwnerMatch
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
<IfModule sapi_apache2.c>
|
||||
php_admin_flag engine off
|
||||
</IfModule>
|
||||
<IfModule mod_php5.c>
|
||||
php_admin_flag engine off
|
||||
</IfModule>
|
||||
</Directory>
|
||||
|
||||
<IfModule mod_headers.c>
|
||||
Header add X-Powered-By PleskLin
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_security2.c>
|
||||
SecRuleEngine DetectionOnly
|
||||
SecRequestBodyAccess On
|
||||
SecRequestBodyLimit 134217728
|
||||
SecResponseBodyAccess Off
|
||||
SecResponseBodyLimit 524288
|
||||
SecAuditEngine On
|
||||
SecAuditLog "/var/log/modsec_audit.log"
|
||||
SecAuditLogType serial
|
||||
</IfModule>
|
||||
|
||||
#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf"
|
||||
|
||||
<VirtualHost \
|
||||
0.0.0.0:7080 \
|
||||
[00000:000:000:0000::2]:7080 \
|
||||
0.0.0.0:7080 \
|
||||
127.0.0.1:7080 \
|
||||
>
|
||||
ServerName "default"
|
||||
UseCanonicalName Off
|
||||
DocumentRoot /tmp
|
||||
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
|
||||
|
||||
<IfModule mod_ssl.c>
|
||||
SSLEngine off
|
||||
</IfModule>
|
||||
|
||||
<Directory "/var/www/vhosts/default/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
<Directory "/var/www/vhosts/default/htdocs">
|
||||
|
||||
<IfModule sapi_apache2.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
<IfModule mod_ssl.c>
|
||||
|
||||
<VirtualHost \
|
||||
0.0.0.0:7081 \
|
||||
127.0.0.1:7081 \
|
||||
>
|
||||
ServerName "default-0_0_0_0"
|
||||
UseCanonicalName Off
|
||||
DocumentRoot /tmp
|
||||
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
|
||||
|
||||
SSLEngine on
|
||||
SSLVerifyClient none
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
|
||||
<Directory "/var/www/vhosts/default/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
<Directory "/var/www/vhosts/default/htdocs">
|
||||
|
||||
<IfModule sapi_apache2.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
<VirtualHost \
|
||||
[00000:000:000:000::2]:7081 \
|
||||
127.0.0.1:7081 \
|
||||
>
|
||||
ServerName "default-0000_000_000_00000__2"
|
||||
UseCanonicalName Off
|
||||
DocumentRoot /tmp
|
||||
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
|
||||
|
||||
SSLEngine on
|
||||
SSLVerifyClient none
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
|
||||
<Directory "/var/www/vhosts/default/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
<Directory "/var/www/vhosts/default/htdocs">
|
||||
|
||||
<IfModule sapi_apache2.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
<VirtualHost \
|
||||
0.0.0.0:7081 \
|
||||
127.0.0.1:7081 \
|
||||
>
|
||||
ServerName "default-0_0_0_0"
|
||||
UseCanonicalName Off
|
||||
DocumentRoot /tmp
|
||||
ScriptAlias "/cgi-bin/" "/var/www/vhosts/default/cgi-bin"
|
||||
|
||||
SSLEngine on
|
||||
SSLVerifyClient none
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
|
||||
#SSLCACertificateFile "/usr/local/psa/var/certificates/cert-nLy6Z1"
|
||||
|
||||
<Directory "/var/www/vhosts/default/cgi-bin">
|
||||
AllowOverride None
|
||||
Options None
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
<Directory "/var/www/vhosts/default/htdocs">
|
||||
|
||||
<IfModule sapi_apache2.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_php5.c>
|
||||
php_admin_flag engine on
|
||||
</IfModule>
|
||||
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
</IfModule>
|
||||
|
||||
<VirtualHost \
|
||||
0.0.0.0:7080 \
|
||||
[0000:000:000:000::2]:7080 \
|
||||
0.0.0.0:7080 \
|
||||
127.0.0.1:7080 \
|
||||
>
|
||||
DocumentRoot /tmp
|
||||
ServerName lists
|
||||
ServerAlias lists.*
|
||||
UseCanonicalName Off
|
||||
|
||||
ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/"
|
||||
|
||||
Alias "/icons/" "/var/www/icons/"
|
||||
Alias "/pipermail/" "/var/lib/mailman/archives/public/"
|
||||
|
||||
<IfModule mod_ssl.c>
|
||||
SSLEngine off
|
||||
</IfModule>
|
||||
|
||||
<Directory "/var/lib/mailman/archives/">
|
||||
Options FollowSymLinks
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
<IfModule mod_ssl.c>
|
||||
<VirtualHost \
|
||||
0.0.0.0:7081 \
|
||||
[00000:000:000:0000::2]:7081 \
|
||||
0.0.0.0:7081 \
|
||||
127.0.0.1:7081 \
|
||||
>
|
||||
DocumentRoot /tmp
|
||||
ServerName lists
|
||||
ServerAlias lists.*
|
||||
UseCanonicalName Off
|
||||
|
||||
ScriptAlias "/mailman/" "/usr/lib/mailman/cgi-bin/"
|
||||
|
||||
Alias "/icons/" "/var/www/icons/"
|
||||
Alias "/pipermail/" "/var/lib/mailman/archives/public/"
|
||||
|
||||
SSLEngine on
|
||||
SSLVerifyClient none
|
||||
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
|
||||
|
||||
<Directory "/var/lib/mailman/archives/">
|
||||
Options FollowSymLinks
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
</VirtualHost>
|
||||
</IfModule>
|
||||
|
||||
<IfModule mod_rpaf.c>
|
||||
RPAFproxy_ips 0.0.0.0 [00000:000:000:00000::2] 0.0.0.0
|
||||
</IfModule>
|
||||
<IfModule mod_rpaf-2.0.c>
|
||||
RPAFproxy_ips 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0
|
||||
</IfModule>
|
||||
<IfModule mod_remoteip.c>
|
||||
RemoteIPInternalProxy 0.0.0.0 [0000:000:000:0000::2] 0.0.0.0
|
||||
RemoteIPHeader X-Forwarded-For
|
||||
</IfModule>
|
||||
@@ -85,7 +85,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
mock_getutility.notification = mock.MagicMock(return_value=True)
|
||||
names = self.config.get_all_names()
|
||||
self.assertEqual(names, set(
|
||||
["letsencrypt.demo", "encryption-example.demo", "ip-172-30-0-17"]))
|
||||
["letsencrypt.demo", "encryption-example.demo", "ip-172-30-0-17", "*.blue.purple.com"]))
|
||||
|
||||
@mock.patch("zope.component.getUtility")
|
||||
@mock.patch("letsencrypt_apache.configurator.socket.gethostbyaddr")
|
||||
@@ -103,7 +103,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.config.vhosts.append(vhost)
|
||||
|
||||
names = self.config.get_all_names()
|
||||
self.assertEqual(len(names), 5)
|
||||
self.assertEqual(len(names), 6)
|
||||
self.assertTrue("zombo.com" in names)
|
||||
self.assertTrue("google.com" in names)
|
||||
self.assertTrue("letsencrypt.demo" in names)
|
||||
@@ -124,7 +124,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
|
||||
"""
|
||||
vhs = self.config.get_virtual_hosts()
|
||||
self.assertEqual(len(vhs), 6)
|
||||
self.assertEqual(len(vhs), 7)
|
||||
found = 0
|
||||
|
||||
for vhost in vhs:
|
||||
@@ -135,7 +135,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
else:
|
||||
raise Exception("Missed: %s" % vhost) # pragma: no cover
|
||||
|
||||
self.assertEqual(found, 6)
|
||||
self.assertEqual(found, 7)
|
||||
|
||||
# Handle case of non-debian layout get_virtual_hosts
|
||||
with mock.patch(
|
||||
@@ -143,7 +143,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
) as mock_conf:
|
||||
mock_conf.return_value = False
|
||||
vhs = self.config.get_virtual_hosts()
|
||||
self.assertEqual(len(vhs), 6)
|
||||
self.assertEqual(len(vhs), 7)
|
||||
|
||||
@mock.patch("letsencrypt_apache.display_ops.select_vhost")
|
||||
def test_choose_vhost_none_avail(self, mock_select):
|
||||
@@ -186,6 +186,20 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.assertRaises(
|
||||
errors.PluginError, self.config.choose_vhost, "none.com")
|
||||
|
||||
def test_choosevhost_select_vhost_with_wildcard(self):
|
||||
chosen_vhost = self.config.choose_vhost("blue.purple.com", temp=True)
|
||||
self.assertEqual(self.vh_truth[6], chosen_vhost)
|
||||
|
||||
def test_findbest_continues_on_short_domain(self):
|
||||
# pylint: disable=protected-access
|
||||
chosen_vhost = self.config._find_best_vhost("purple.com")
|
||||
self.assertEqual(None, chosen_vhost)
|
||||
|
||||
def test_findbest_continues_on_long_domain(self):
|
||||
# pylint: disable=protected-access
|
||||
chosen_vhost = self.config._find_best_vhost("green.red.purple.com")
|
||||
self.assertEqual(None, chosen_vhost)
|
||||
|
||||
def test_find_best_vhost(self):
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(
|
||||
@@ -211,6 +225,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.config.vhosts = [
|
||||
vh for vh in self.config.vhosts
|
||||
if vh.name not in ["letsencrypt.demo", "encryption-example.demo"]
|
||||
and "*.blue.purple.com" not in vh.aliases
|
||||
]
|
||||
|
||||
self.assertEqual(
|
||||
@@ -218,7 +233,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
|
||||
def test_non_default_vhosts(self):
|
||||
# pylint: disable=protected-access
|
||||
self.assertEqual(len(self.config._non_default_vhosts()), 4)
|
||||
self.assertEqual(len(self.config._non_default_vhosts()), 5)
|
||||
|
||||
def test_is_site_enabled(self):
|
||||
"""Test if site is enabled.
|
||||
@@ -524,7 +539,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]),
|
||||
self.config.is_name_vhost(ssl_vhost))
|
||||
|
||||
self.assertEqual(len(self.config.vhosts), 7)
|
||||
self.assertEqual(len(self.config.vhosts), 8)
|
||||
|
||||
def test_clean_vhost_ssl(self):
|
||||
# pylint: disable=protected-access
|
||||
@@ -746,9 +761,9 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
mock_exe.return_value = True
|
||||
ssl_vh = obj.VirtualHost(
|
||||
"fp", "ap", set([obj.Addr(("*", "443")),
|
||||
obj.Addr(("satoshi.com",))]),
|
||||
"fp", "ap", set([obj.Addr(("*", "443"))]),
|
||||
True, False)
|
||||
ssl_vh.name = "satoshi.com"
|
||||
self.config.vhosts.append(ssl_vh)
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
@@ -942,7 +957,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.config._enable_redirect(self.vh_truth[1], "")
|
||||
self.assertEqual(len(self.config.vhosts), 7)
|
||||
self.assertEqual(len(self.config.vhosts), 8)
|
||||
|
||||
def test_create_own_redirect_for_old_apache_version(self):
|
||||
self.config.parser.modules.add("rewrite_module")
|
||||
@@ -953,7 +968,7 @@ class TwoVhost80Test(util.ApacheTest):
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.config._enable_redirect(self.vh_truth[1], "")
|
||||
self.assertEqual(len(self.config.vhosts), 7)
|
||||
self.assertEqual(len(self.config.vhosts), 8)
|
||||
|
||||
def test_sift_line(self):
|
||||
# pylint: disable=protected-access
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Depends: dav_svn
|
||||
<IfModule !mod_dav_svn.c>
|
||||
Include mods-enabled/dav_svn.load
|
||||
</IfModule>
|
||||
LoadModule authz_svn_module /usr/lib/apache2/modules/mod_authz_svn.so
|
||||
@@ -0,0 +1,3 @@
|
||||
<IfModule !mod_dav.c>
|
||||
LoadModule dav_module /usr/lib/apache2/modules/mod_dav.so
|
||||
</IfModule>
|
||||
@@ -0,0 +1,56 @@
|
||||
# dav_svn.conf - Example Subversion/Apache configuration
|
||||
#
|
||||
# For details and further options see the Apache user manual and
|
||||
# the Subversion book.
|
||||
#
|
||||
# NOTE: for a setup with multiple vhosts, you will want to do this
|
||||
# configuration in /etc/apache2/sites-available/*, not here.
|
||||
|
||||
# <Location URL> ... </Location>
|
||||
# URL controls how the repository appears to the outside world.
|
||||
# In this example clients access the repository as http://hostname/svn/
|
||||
# Note, a literal /svn should NOT exist in your document root.
|
||||
#<Location /svn>
|
||||
|
||||
# Uncomment this to enable the repository
|
||||
#DAV svn
|
||||
|
||||
# Set this to the path to your repository
|
||||
#SVNPath /var/lib/svn
|
||||
# Alternatively, use SVNParentPath if you have multiple repositories under
|
||||
# under a single directory (/var/lib/svn/repo1, /var/lib/svn/repo2, ...).
|
||||
# You need either SVNPath and SVNParentPath, but not both.
|
||||
#SVNParentPath /var/lib/svn
|
||||
|
||||
# Access control is done at 3 levels: (1) Apache authentication, via
|
||||
# any of several methods. A "Basic Auth" section is commented out
|
||||
# below. (2) Apache <Limit> and <LimitExcept>, also commented out
|
||||
# below. (3) mod_authz_svn is a svn-specific authorization module
|
||||
# which offers fine-grained read/write access control for paths
|
||||
# within a repository. (The first two layers are coarse-grained; you
|
||||
# can only enable/disable access to an entire repository.) Note that
|
||||
# mod_authz_svn is noticeably slower than the other two layers, so if
|
||||
# you don't need the fine-grained control, don't configure it.
|
||||
|
||||
# Basic Authentication is repository-wide. It is not secure unless
|
||||
# you are using https. See the 'htpasswd' command to create and
|
||||
# manage the password file - and the documentation for the
|
||||
# 'auth_basic' and 'authn_file' modules, which you will need for this
|
||||
# (enable them with 'a2enmod').
|
||||
#AuthType Basic
|
||||
#AuthName "Subversion Repository"
|
||||
#AuthUserFile /etc/apache2/dav_svn.passwd
|
||||
|
||||
# To enable authorization via mod_authz_svn (enable that module separately):
|
||||
#<IfModule mod_authz_svn.c>
|
||||
#AuthzSVNAccessFile /etc/apache2/dav_svn.authz
|
||||
#</IfModule>
|
||||
|
||||
# The following three lines allow anonymous read, but make
|
||||
# committers authenticate themselves. It requires the 'authz_user'
|
||||
# module (enable it with 'a2enmod').
|
||||
#<LimitExcept GET PROPFIND OPTIONS REPORT>
|
||||
#Require valid-user
|
||||
#</LimitExcept>
|
||||
|
||||
#</Location>
|
||||
@@ -0,0 +1,7 @@
|
||||
# Depends: dav
|
||||
<IfModule !mod_dav_svn.c>
|
||||
<IfModule !mod_dav.c>
|
||||
Include mods-enabled/dav.load
|
||||
</IfModule>
|
||||
LoadModule dav_svn_module /usr/lib/apache2/modules/mod_dav_svn.so
|
||||
</IfModule>
|
||||
@@ -0,0 +1 @@
|
||||
../mods-available/authz_svn.load
|
||||
@@ -0,0 +1 @@
|
||||
../mods-available/dav.load
|
||||
@@ -0,0 +1 @@
|
||||
../mods-available/dav_svn.conf
|
||||
@@ -0,0 +1 @@
|
||||
../mods-available/dav_svn.load
|
||||
@@ -0,0 +1,13 @@
|
||||
<VirtualHost *:80>
|
||||
|
||||
ServerName ip-172-30-0-17
|
||||
ServerAdmin webmaster@localhost
|
||||
DocumentRoot /var/www/html
|
||||
ServerAlias *.blue.purple.com
|
||||
|
||||
ErrorLog ${APACHE_LOG_DIR}/error.log
|
||||
CustomLog ${APACHE_LOG_DIR}/access.log combined
|
||||
|
||||
</VirtualHost>
|
||||
|
||||
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
|
||||
@@ -150,7 +150,12 @@ def get_vh_truth(temp_dir, config_name):
|
||||
os.path.join(prefix, "default-ssl-port-only.conf"),
|
||||
os.path.join(aug_pre, ("default-ssl-port-only.conf/"
|
||||
"IfModule/VirtualHost")),
|
||||
set([obj.Addr.fromstring("_default_:443")]), True, False)
|
||||
set([obj.Addr.fromstring("_default_:443")]), True, False),
|
||||
obj.VirtualHost(
|
||||
os.path.join(prefix, "wildcard.conf"),
|
||||
os.path.join(aug_pre, "wildcard.conf/VirtualHost"),
|
||||
set([obj.Addr.fromstring("*:80")]), False, False,
|
||||
"ip-172-30-0-17", aliases=["*.blue.purple.com"])
|
||||
]
|
||||
return vh_truth
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ class ApacheTlsSni01(common.TLSSNI01):
|
||||
return []
|
||||
# Save any changes to the configuration as a precaution
|
||||
# About to make temporary changes to the config
|
||||
self.configurator.save()
|
||||
self.configurator.save("Changes before challenge setup", True)
|
||||
|
||||
# Prepare the server for HTTPS
|
||||
self.configurator.prepare_server_https(
|
||||
@@ -108,7 +108,7 @@ class ApacheTlsSni01(common.TLSSNI01):
|
||||
self.configurator.reverter.register_file_creation(
|
||||
True, self.challenge_conf)
|
||||
|
||||
logger.debug("writing a config file with text: %s", config_text)
|
||||
logger.debug("writing a config file with text:\n %s", config_text)
|
||||
with open(self.challenge_conf, "w") as new_conf:
|
||||
new_conf.write(config_text)
|
||||
|
||||
@@ -144,6 +144,8 @@ class ApacheTlsSni01(common.TLSSNI01):
|
||||
if len(self.configurator.parser.find_dir(
|
||||
parser.case_i("Include"), self.challenge_conf)) == 0:
|
||||
# print "Including challenge virtual host(s)"
|
||||
logger.debug("Adding Include %s to %s",
|
||||
self.challenge_conf, parser.get_aug_path(main_config))
|
||||
self.configurator.parser.add_dir(
|
||||
parser.get_aug_path(main_config),
|
||||
"Include", self.challenge_conf)
|
||||
|
||||
@@ -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 +0,0 @@
|
||||
letsencrypt-auto-source/letsencrypt-auto
|
||||
1809
letsencrypt-auto
Executable file
1809
letsencrypt-auto
Executable file
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,7 @@ RUN apt-get update && \
|
||||
apt-get clean
|
||||
RUN pip install nose
|
||||
|
||||
RUN mkdir -p /home/lea/letsencrypt/letsencrypt
|
||||
RUN mkdir -p /home/lea/letsencrypt
|
||||
|
||||
# Install fake testing CA:
|
||||
COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/
|
||||
|
||||
@@ -18,25 +18,31 @@ set -e # Work even if somebody does "sh thisscript.sh".
|
||||
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"
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
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
|
||||
# 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" = "--no-self-upgrade" ] ; then
|
||||
# Do not upgrade this script (also prevents client upgrades, because each
|
||||
# copy of the script pins a hash of the python client)
|
||||
NO_SELF_UPGRADE=1
|
||||
elif [ "$arg" = "--os-packages-only" ] ; then
|
||||
OS_PACKAGES_ONLY=1
|
||||
elif [ "$arg" = "--debug" ]; then
|
||||
DEBUG=1
|
||||
fi
|
||||
case "$arg" in
|
||||
--debug)
|
||||
DEBUG=1;;
|
||||
--os-packages-only)
|
||||
OS_PACKAGES_ONLY=1;;
|
||||
--no-self-upgrade)
|
||||
# Do not upgrade this script (also prevents client upgrades, because each
|
||||
# copy of the script pins a hash of the python client)
|
||||
NO_SELF_UPGRADE=1;;
|
||||
--verbose)
|
||||
VERBOSE=1;;
|
||||
[!-]*|-*[!v]*|-)
|
||||
# Anything that isn't -v, -vv, etc.: that is, anything that does not
|
||||
# start with a -, contains anything that's not a v, or is just "-"
|
||||
;;
|
||||
*) # -v+ remains.
|
||||
VERBOSE=1;;
|
||||
esac
|
||||
done
|
||||
|
||||
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
|
||||
@@ -91,21 +97,18 @@ ExperimentalBootstrap() {
|
||||
}
|
||||
|
||||
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!"
|
||||
for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do
|
||||
# Break (while keeping the LE_PYTHON value) if found.
|
||||
command -v "$LE_PYTHON" > /dev/null && break
|
||||
done
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Cannot find any Pythons; please install one!"
|
||||
exit 1
|
||||
fi
|
||||
export LE_PYTHON
|
||||
|
||||
PYVER=`"$LE_PYTHON" --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
|
||||
if [ $PYVER -lt 26 ]; then
|
||||
PYVER=`"$LE_PYTHON" -V 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
|
||||
@@ -165,7 +168,7 @@ BootstrapDebCommon() {
|
||||
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
|
||||
fi
|
||||
|
||||
sudo sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO apt-get update
|
||||
fi
|
||||
fi
|
||||
@@ -304,10 +307,11 @@ BootstrapArchCommon() {
|
||||
pkg-config
|
||||
"
|
||||
|
||||
missing=$("$SUDO" pacman -T $deps)
|
||||
# pacman -T exits with 127 if there are missing dependencies
|
||||
missing=$($SUDO pacman -T $deps) || true
|
||||
|
||||
if [ "$missing" ]; then
|
||||
"$SUDO" pacman -S --needed $missing
|
||||
$SUDO pacman -S --needed $missing
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -324,19 +328,19 @@ 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
|
||||
}
|
||||
|
||||
BootstrapFreeBsd() {
|
||||
"$SUDO" pkg install -Ay \
|
||||
$SUDO pkg install -Ay \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
augeas \
|
||||
@@ -345,20 +349,27 @@ BootstrapFreeBsd() {
|
||||
|
||||
BootstrapMac() {
|
||||
if ! hash brew 2>/dev/null; then
|
||||
echo "Homebrew Not Installed\nDownloading..."
|
||||
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 [ -z "$(brew list --versions augeas)" ]; then
|
||||
echo "augeas not installed.\nInstalling augeas from Homebrew..."
|
||||
brew install augeas
|
||||
fi
|
||||
|
||||
if ! hash pip 2>/dev/null; then
|
||||
echo "pip Not Installed\nInstalling python from Homebrew..."
|
||||
if [ -z "$(brew list --versions dialog)" ]; then
|
||||
echo "dialog not installed.\nInstalling dialog from Homebrew..."
|
||||
brew install dialog
|
||||
fi
|
||||
|
||||
if [ -z "$(brew list --versions python)" ]; then
|
||||
echo "python not installed.\nInstalling python from Homebrew..."
|
||||
brew install python
|
||||
fi
|
||||
|
||||
if ! hash virtualenv 2>/dev/null; then
|
||||
echo "virtualenv Not Installed\nInstalling with pip"
|
||||
echo "virtualenv not installed.\nInstalling with pip..."
|
||||
pip install virtualenv
|
||||
fi
|
||||
}
|
||||
@@ -412,9 +423,10 @@ TempDir() {
|
||||
|
||||
|
||||
|
||||
if [ "$NO_SELF_UPGRADE" = 1 ]; then
|
||||
if [ "$1" = "--le-auto-phase2" ]; then
|
||||
# Phase 2: Create venv, install LE, and run.
|
||||
|
||||
shift 1 # the --le-auto-phase2 arg
|
||||
if [ -f "$VENV_BIN/letsencrypt" ]; then
|
||||
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | cut -d " " -f 2)
|
||||
else
|
||||
@@ -609,10 +621,6 @@ traceback2==1.4.0
|
||||
# sha256: IogqDkGMKE4fcYqCKzsCKUTVPS2QjhaQsxmp0-ssBXk
|
||||
unittest2==1.1.0
|
||||
|
||||
# sha256: aUkbUwUVfDxuDwSnAZhNaud_1yn8HJrNJQd_HfOFMms
|
||||
# sha256: 619wCpv8lkILBVY1r5AC02YuQ9gMP_0x8iTCW8DV9GI
|
||||
Werkzeug==0.11.3
|
||||
|
||||
# sha256: KCwRK1XdjjyGmjVx-GdnwVCrEoSprOK97CJsWSrK-Bo
|
||||
zope.component==4.2.2
|
||||
|
||||
@@ -638,17 +646,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
|
||||
@@ -745,6 +753,7 @@ except ImportError:
|
||||
from pip.util import url_to_path # 0.7.0
|
||||
except ImportError:
|
||||
from pip.util import url_to_filename as url_to_path # 0.6.2
|
||||
from pip.exceptions import InstallationError
|
||||
from pip.index import PackageFinder, Link
|
||||
try:
|
||||
from pip.log import logger
|
||||
@@ -763,7 +772,7 @@ except ImportError:
|
||||
|
||||
DownloadProgressBar = DownloadProgressSpinner = NullProgressBar
|
||||
|
||||
__version__ = 3, 0, 0
|
||||
__version__ = 3, 1, 1
|
||||
|
||||
try:
|
||||
from pip.index import FormatControl # noqa
|
||||
@@ -781,6 +790,7 @@ ITS_FINE_ITS_FINE = 0
|
||||
SOMETHING_WENT_WRONG = 1
|
||||
# "Traditional" for command-line errors according to optparse docs:
|
||||
COMMAND_LINE_ERROR = 2
|
||||
UNHANDLED_EXCEPTION = 3
|
||||
|
||||
ARCHIVE_EXTENSIONS = ('.tar.bz2', '.tar.gz', '.tgz', '.tar', '.zip')
|
||||
|
||||
@@ -1543,7 +1553,7 @@ def peep_install(argv):
|
||||
first_every_last(buckets[SatisfiedReq], *printers)
|
||||
|
||||
return ITS_FINE_ITS_FINE
|
||||
except (UnsupportedRequirementError, DownloadError) as exc:
|
||||
except (UnsupportedRequirementError, InstallationError, DownloadError) as exc:
|
||||
out(str(exc))
|
||||
return SOMETHING_WENT_WRONG
|
||||
finally:
|
||||
@@ -1563,16 +1573,23 @@ def peep_port(paths):
|
||||
print('Please specify one or more requirements files so I have '
|
||||
'something to port.\n')
|
||||
return COMMAND_LINE_ERROR
|
||||
|
||||
comes_from = None
|
||||
for req in chain.from_iterable(
|
||||
_parse_requirements(path, package_finder(argv)) for path in paths):
|
||||
req_path, req_line = path_and_line(req)
|
||||
hashes = [hexlify(urlsafe_b64decode((hash + '=').encode('ascii'))).decode('ascii')
|
||||
for hash in hashes_above(*path_and_line(req))]
|
||||
for hash in hashes_above(req_path, req_line)]
|
||||
if req_path != comes_from:
|
||||
print()
|
||||
print('# from %s' % req_path)
|
||||
print()
|
||||
comes_from = req_path
|
||||
|
||||
if not hashes:
|
||||
print(req.req)
|
||||
elif len(hashes) == 1:
|
||||
print('%s --hash=sha256:%s' % (req.req, hashes[0]))
|
||||
else:
|
||||
print('%s' % req.req, end='')
|
||||
print('%s' % (req.link if getattr(req, 'link', None) else req.req), end='')
|
||||
for hash in hashes:
|
||||
print(' \\')
|
||||
print(' --hash=sha256:%s' % hash, end='')
|
||||
@@ -1617,7 +1634,7 @@ if __name__ == '__main__':
|
||||
exit(main())
|
||||
except Exception:
|
||||
exception_handler(*sys.exc_info())
|
||||
exit(SOMETHING_WENT_WRONG)
|
||||
exit(UNHANDLED_EXCEPTION)
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
@@ -1630,8 +1647,10 @@ UNLIKELY_EOF
|
||||
# Report error. (Otherwise, be quiet.)
|
||||
echo "Had a problem while downloading and verifying Python packages:"
|
||||
echo "$PEEP_OUT"
|
||||
rm -rf "$VENV_PATH"
|
||||
exit 1
|
||||
fi
|
||||
echo "Installation succeeded."
|
||||
fi
|
||||
echo "Requesting root privileges to run letsencrypt..."
|
||||
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
|
||||
@@ -1653,10 +1672,11 @@ else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Checking for new version..."
|
||||
TEMP_DIR=$(TempDir)
|
||||
# ---------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
|
||||
if [ "$NO_SELF_UPGRADE" != 1 ]; then
|
||||
echo "Checking for new version..."
|
||||
TEMP_DIR=$(TempDir)
|
||||
# ---------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
|
||||
"""Do downloading and JSON parsing without additional dependencies. ::
|
||||
|
||||
# Print latest released version of LE to stdout:
|
||||
@@ -1785,25 +1805,36 @@ if __name__ == '__main__':
|
||||
exit(main())
|
||||
|
||||
UNLIKELY_EOF
|
||||
# ---------------------------------------------------------------------------
|
||||
DeterminePythonVersion
|
||||
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
|
||||
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
|
||||
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
|
||||
# ---------------------------------------------------------------------------
|
||||
DeterminePythonVersion
|
||||
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
|
||||
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
|
||||
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
|
||||
|
||||
# Now we drop into Python so we don't have to install even more
|
||||
# dependencies (curl, etc.), for better flow control, and for the option of
|
||||
# future Windows compatibility.
|
||||
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
|
||||
# Now we drop into Python so we don't have to install even more
|
||||
# dependencies (curl, etc.), for better flow control, and for the option of
|
||||
# future Windows compatibility.
|
||||
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
|
||||
|
||||
# Install new copy of letsencrypt-auto. This preserves permissions and
|
||||
# ownership from the old copy.
|
||||
# TODO: Deal with quotes in pathnames.
|
||||
echo "Replacing letsencrypt-auto..."
|
||||
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
|
||||
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
|
||||
# TODO: Clean up temp dir safely, even if it has quotes in its path.
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi # should upgrade
|
||||
"$0" --no-self-upgrade "$@"
|
||||
# Install new copy of letsencrypt-auto.
|
||||
# TODO: Deal with quotes in pathnames.
|
||||
echo "Replacing letsencrypt-auto..."
|
||||
# Clone permissions with cp. chmod and chown don't have a --reference
|
||||
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
|
||||
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
# Using mv rather than cp leaves the old file descriptor pointing to the
|
||||
# original copy so the shell can continue to read it unmolested. mv across
|
||||
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
|
||||
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
|
||||
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
|
||||
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
|
||||
# TODO: Clean up temp dir safely, even if it has quotes in its path.
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi # A newer version is available.
|
||||
fi # Self-upgrading is allowed.
|
||||
|
||||
"$0" --le-auto-phase2 "$@"
|
||||
fi
|
||||
|
||||
Binary file not shown.
@@ -18,25 +18,31 @@ set -e # Work even if somebody does "sh thisscript.sh".
|
||||
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
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}"
|
||||
|
||||
# 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" = "--no-self-upgrade" ] ; then
|
||||
# Do not upgrade this script (also prevents client upgrades, because each
|
||||
# copy of the script pins a hash of the python client)
|
||||
NO_SELF_UPGRADE=1
|
||||
elif [ "$arg" = "--os-packages-only" ] ; then
|
||||
OS_PACKAGES_ONLY=1
|
||||
elif [ "$arg" = "--debug" ]; then
|
||||
DEBUG=1
|
||||
fi
|
||||
case "$arg" in
|
||||
--debug)
|
||||
DEBUG=1;;
|
||||
--os-packages-only)
|
||||
OS_PACKAGES_ONLY=1;;
|
||||
--no-self-upgrade)
|
||||
# Do not upgrade this script (also prevents client upgrades, because each
|
||||
# copy of the script pins a hash of the python client)
|
||||
NO_SELF_UPGRADE=1;;
|
||||
--verbose)
|
||||
VERBOSE=1;;
|
||||
[!-]*|-*[!v]*|-)
|
||||
# Anything that isn't -v, -vv, etc.: that is, anything that does not
|
||||
# start with a -, contains anything that's not a v, or is just "-"
|
||||
;;
|
||||
*) # -v+ remains.
|
||||
VERBOSE=1;;
|
||||
esac
|
||||
done
|
||||
|
||||
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
|
||||
@@ -91,21 +97,18 @@ ExperimentalBootstrap() {
|
||||
}
|
||||
|
||||
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!"
|
||||
for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do
|
||||
# Break (while keeping the LE_PYTHON value) if found.
|
||||
command -v "$LE_PYTHON" > /dev/null && break
|
||||
done
|
||||
if [ "$?" != "0" ]; then
|
||||
echo "Cannot find any Pythons; please install one!"
|
||||
exit 1
|
||||
fi
|
||||
export LE_PYTHON
|
||||
|
||||
PYVER=`"$LE_PYTHON" --version 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//'`
|
||||
if [ $PYVER -lt 26 ]; then
|
||||
PYVER=`"$LE_PYTHON" -V 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
|
||||
@@ -168,9 +171,10 @@ TempDir() {
|
||||
|
||||
|
||||
|
||||
if [ "$NO_SELF_UPGRADE" = 1 ]; then
|
||||
if [ "$1" = "--le-auto-phase2" ]; then
|
||||
# Phase 2: Create venv, install LE, and run.
|
||||
|
||||
shift 1 # the --le-auto-phase2 arg
|
||||
if [ -f "$VENV_BIN/letsencrypt" ]; then
|
||||
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | cut -d " " -f 2)
|
||||
else
|
||||
@@ -207,8 +211,10 @@ UNLIKELY_EOF
|
||||
# Report error. (Otherwise, be quiet.)
|
||||
echo "Had a problem while downloading and verifying Python packages:"
|
||||
echo "$PEEP_OUT"
|
||||
rm -rf "$VENV_PATH"
|
||||
exit 1
|
||||
fi
|
||||
echo "Installation succeeded."
|
||||
fi
|
||||
echo "Requesting root privileges to run letsencrypt..."
|
||||
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
|
||||
@@ -230,31 +236,43 @@ else
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Checking for new version..."
|
||||
TEMP_DIR=$(TempDir)
|
||||
# ---------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
|
||||
if [ "$NO_SELF_UPGRADE" != 1 ]; then
|
||||
echo "Checking for new version..."
|
||||
TEMP_DIR=$(TempDir)
|
||||
# ---------------------------------------------------------------------------
|
||||
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
|
||||
{{ fetch.py }}
|
||||
UNLIKELY_EOF
|
||||
# ---------------------------------------------------------------------------
|
||||
DeterminePythonVersion
|
||||
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
|
||||
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
|
||||
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
|
||||
# ---------------------------------------------------------------------------
|
||||
DeterminePythonVersion
|
||||
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
|
||||
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
|
||||
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
|
||||
|
||||
# Now we drop into Python so we don't have to install even more
|
||||
# dependencies (curl, etc.), for better flow control, and for the option of
|
||||
# future Windows compatibility.
|
||||
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
|
||||
# Now we drop into Python so we don't have to install even more
|
||||
# dependencies (curl, etc.), for better flow control, and for the option of
|
||||
# future Windows compatibility.
|
||||
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
|
||||
|
||||
# Install new copy of letsencrypt-auto. This preserves permissions and
|
||||
# ownership from the old copy.
|
||||
# TODO: Deal with quotes in pathnames.
|
||||
echo "Replacing letsencrypt-auto..."
|
||||
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
|
||||
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$0"
|
||||
# TODO: Clean up temp dir safely, even if it has quotes in its path.
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi # should upgrade
|
||||
"$0" --no-self-upgrade "$@"
|
||||
# Install new copy of letsencrypt-auto.
|
||||
# TODO: Deal with quotes in pathnames.
|
||||
echo "Replacing letsencrypt-auto..."
|
||||
# Clone permissions with cp. chmod and chown don't have a --reference
|
||||
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
|
||||
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
|
||||
# Using mv rather than cp leaves the old file descriptor pointing to the
|
||||
# original copy so the shell can continue to read it unmolested. mv across
|
||||
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
|
||||
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
|
||||
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
|
||||
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
|
||||
# TODO: Clean up temp dir safely, even if it has quotes in its path.
|
||||
rm -rf "$TEMP_DIR"
|
||||
fi # A newer version is available.
|
||||
fi # Self-upgrading is allowed.
|
||||
|
||||
"$0" --le-auto-phase2 "$@"
|
||||
fi
|
||||
|
||||
@@ -18,9 +18,10 @@ BootstrapArchCommon() {
|
||||
pkg-config
|
||||
"
|
||||
|
||||
missing=$("$SUDO" pacman -T $deps)
|
||||
# pacman -T exits with 127 if there are missing dependencies
|
||||
missing=$($SUDO pacman -T $deps) || true
|
||||
|
||||
if [ "$missing" ]; then
|
||||
"$SUDO" pacman -S --needed $missing
|
||||
$SUDO pacman -S --needed $missing
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ BootstrapDebCommon() {
|
||||
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
|
||||
fi
|
||||
|
||||
sudo sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO apt-get update
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
BootstrapFreeBsd() {
|
||||
"$SUDO" pkg install -Ay \
|
||||
$SUDO pkg install -Ay \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
augeas \
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
BootstrapMac() {
|
||||
if ! hash brew 2>/dev/null; then
|
||||
echo "Homebrew Not Installed\nDownloading..."
|
||||
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 [ -z "$(brew list --versions augeas)" ]; then
|
||||
echo "augeas not installed.\nInstalling augeas from Homebrew..."
|
||||
brew install augeas
|
||||
fi
|
||||
|
||||
if ! hash pip 2>/dev/null; then
|
||||
echo "pip Not Installed\nInstalling python from Homebrew..."
|
||||
if [ -z "$(brew list --versions dialog)" ]; then
|
||||
echo "dialog not installed.\nInstalling dialog from Homebrew..."
|
||||
brew install dialog
|
||||
fi
|
||||
|
||||
if [ -z "$(brew list --versions python)" ]; then
|
||||
echo "python not installed.\nInstalling python from Homebrew..."
|
||||
brew install python
|
||||
fi
|
||||
|
||||
if ! hash virtualenv 2>/dev/null; then
|
||||
echo "virtualenv Not Installed\nInstalling with pip"
|
||||
echo "virtualenv not installed.\nInstalling with pip..."
|
||||
pip install virtualenv
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -172,10 +172,6 @@ traceback2==1.4.0
|
||||
# sha256: IogqDkGMKE4fcYqCKzsCKUTVPS2QjhaQsxmp0-ssBXk
|
||||
unittest2==1.1.0
|
||||
|
||||
# sha256: aUkbUwUVfDxuDwSnAZhNaud_1yn8HJrNJQd_HfOFMms
|
||||
# sha256: 619wCpv8lkILBVY1r5AC02YuQ9gMP_0x8iTCW8DV9GI
|
||||
Werkzeug==0.11.3
|
||||
|
||||
# sha256: KCwRK1XdjjyGmjVx-GdnwVCrEoSprOK97CJsWSrK-Bo
|
||||
zope.component==4.2.2
|
||||
|
||||
@@ -208,14 +204,14 @@ mock==1.0.1
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT,
|
||||
# ADD ALL DEPENDENCIES ABOVE
|
||||
|
||||
# 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
|
||||
|
||||
@@ -86,6 +86,7 @@ except ImportError:
|
||||
from pip.util import url_to_path # 0.7.0
|
||||
except ImportError:
|
||||
from pip.util import url_to_filename as url_to_path # 0.6.2
|
||||
from pip.exceptions import InstallationError
|
||||
from pip.index import PackageFinder, Link
|
||||
try:
|
||||
from pip.log import logger
|
||||
@@ -104,7 +105,7 @@ except ImportError:
|
||||
|
||||
DownloadProgressBar = DownloadProgressSpinner = NullProgressBar
|
||||
|
||||
__version__ = 3, 0, 0
|
||||
__version__ = 3, 1, 1
|
||||
|
||||
try:
|
||||
from pip.index import FormatControl # noqa
|
||||
@@ -122,6 +123,7 @@ ITS_FINE_ITS_FINE = 0
|
||||
SOMETHING_WENT_WRONG = 1
|
||||
# "Traditional" for command-line errors according to optparse docs:
|
||||
COMMAND_LINE_ERROR = 2
|
||||
UNHANDLED_EXCEPTION = 3
|
||||
|
||||
ARCHIVE_EXTENSIONS = ('.tar.bz2', '.tar.gz', '.tgz', '.tar', '.zip')
|
||||
|
||||
@@ -884,7 +886,7 @@ def peep_install(argv):
|
||||
first_every_last(buckets[SatisfiedReq], *printers)
|
||||
|
||||
return ITS_FINE_ITS_FINE
|
||||
except (UnsupportedRequirementError, DownloadError) as exc:
|
||||
except (UnsupportedRequirementError, InstallationError, DownloadError) as exc:
|
||||
out(str(exc))
|
||||
return SOMETHING_WENT_WRONG
|
||||
finally:
|
||||
@@ -904,16 +906,23 @@ def peep_port(paths):
|
||||
print('Please specify one or more requirements files so I have '
|
||||
'something to port.\n')
|
||||
return COMMAND_LINE_ERROR
|
||||
|
||||
comes_from = None
|
||||
for req in chain.from_iterable(
|
||||
_parse_requirements(path, package_finder(argv)) for path in paths):
|
||||
req_path, req_line = path_and_line(req)
|
||||
hashes = [hexlify(urlsafe_b64decode((hash + '=').encode('ascii'))).decode('ascii')
|
||||
for hash in hashes_above(*path_and_line(req))]
|
||||
for hash in hashes_above(req_path, req_line)]
|
||||
if req_path != comes_from:
|
||||
print()
|
||||
print('# from %s' % req_path)
|
||||
print()
|
||||
comes_from = req_path
|
||||
|
||||
if not hashes:
|
||||
print(req.req)
|
||||
elif len(hashes) == 1:
|
||||
print('%s --hash=sha256:%s' % (req.req, hashes[0]))
|
||||
else:
|
||||
print('%s' % req.req, end='')
|
||||
print('%s' % (req.link if getattr(req, 'link', None) else req.req), end='')
|
||||
for hash in hashes:
|
||||
print(' \\')
|
||||
print(' --hash=sha256:%s' % hash, end='')
|
||||
@@ -958,4 +967,4 @@ if __name__ == '__main__':
|
||||
exit(main())
|
||||
except Exception:
|
||||
exception_handler(*sys.exc_info())
|
||||
exit(SOMETHING_WENT_WRONG)
|
||||
exit(UNHANDLED_EXCEPTION)
|
||||
|
||||
@@ -5,7 +5,7 @@ from contextlib import contextmanager
|
||||
from functools import partial
|
||||
from json import dumps
|
||||
from os import chmod, environ
|
||||
from os.path import abspath, dirname, join
|
||||
from os.path import abspath, dirname, exists, join
|
||||
import re
|
||||
from shutil import copy, rmtree
|
||||
import socket
|
||||
@@ -338,6 +338,12 @@ class AutoTests(TestCase):
|
||||
self.assertIn("THE FOLLOWING PACKAGES DIDN'T MATCH THE "
|
||||
"HASHES SPECIFIED IN THE REQUIREMENTS",
|
||||
exc.output)
|
||||
ok_(not exists(join(venv_dir, 'letsencrypt')),
|
||||
msg="The virtualenv was left around, even though "
|
||||
"installation didn't succeed. We shouldn't do "
|
||||
"this, as it foils our detection of whether we "
|
||||
"need to recreate the virtualenv, which hinges "
|
||||
"on the presence of $VENV_BIN/letsencrypt.")
|
||||
else:
|
||||
self.fail("Peep didn't detect a bad hash and stop the "
|
||||
"installation.")
|
||||
|
||||
@@ -34,11 +34,10 @@ SHARED_MODULES = {
|
||||
"vhost_alias"}
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IConfiguratorProxy)
|
||||
class Proxy(apache_common.Proxy):
|
||||
"""Wraps the ApacheConfigurator for Apache 2.4 tests"""
|
||||
|
||||
zope.interface.implements(interfaces.IConfiguratorProxy)
|
||||
|
||||
def __init__(self, args):
|
||||
"""Initializes the plugin with the given command line args"""
|
||||
super(Proxy, self).__init__(args)
|
||||
|
||||
@@ -19,12 +19,11 @@ APACHE_VERSION_REGEX = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
|
||||
APACHE_COMMANDS = ["apachectl", "a2enmod", "a2dismod"]
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IConfiguratorProxy)
|
||||
class Proxy(configurators_common.Proxy):
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
"""A common base for Apache test configurators"""
|
||||
|
||||
zope.interface.implements(interfaces.IConfiguratorProxy)
|
||||
|
||||
def __init__(self, args):
|
||||
"""Initializes the plugin with the given command line args"""
|
||||
super(Proxy, self).__init__(args)
|
||||
|
||||
@@ -12,10 +12,10 @@ from letsencrypt import interfaces
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IValidator)
|
||||
class Validator(object):
|
||||
# pylint: disable=no-self-use
|
||||
"""Collection of functions to test a live webserver's configuration"""
|
||||
zope.interface.implements(interfaces.IValidator)
|
||||
|
||||
def certificate(self, cert, name, alt_host=None, port=443):
|
||||
"""Verifies the certificate presented at name is cert"""
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -31,6 +31,8 @@ from letsencrypt_nginx import parser
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class NginxConfigurator(common.Plugin):
|
||||
# pylint: disable=too-many-instance-attributes,too-many-public-methods
|
||||
"""Nginx configurator.
|
||||
@@ -52,8 +54,6 @@ class NginxConfigurator(common.Plugin):
|
||||
:ivar tup version: version of Nginx
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Nginx Web Server - currently doesn't work"
|
||||
|
||||
@@ -190,6 +190,12 @@ class NginxConfigurator(common.Plugin):
|
||||
", ".join(str(addr) for addr in vhost.addrs)))
|
||||
self.save_notes += "\tssl_certificate %s\n" % fullchain_path
|
||||
self.save_notes += "\tssl_certificate_key %s\n" % key_path
|
||||
if len(stapling_directives) > 0:
|
||||
self.save_notes += "\tssl_trusted_certificate %s\n" % chain_path
|
||||
self.save_notes += "\tssl_stapling on\n"
|
||||
self.save_notes += "\tssl_stapling_verify on\n"
|
||||
|
||||
|
||||
|
||||
#######################
|
||||
# Vhost parsing methods
|
||||
@@ -512,21 +518,33 @@ class NginxConfigurator(common.Plugin):
|
||||
:param bool temporary: Indicates whether the changes made will
|
||||
be quickly reversed in the future (ie. challenges)
|
||||
|
||||
:raises .errors.PluginError: If there was an error in
|
||||
an attempt to save the configuration, or an error creating a
|
||||
checkpoint
|
||||
|
||||
"""
|
||||
save_files = set(self.parser.parsed.keys())
|
||||
|
||||
# Create Checkpoint
|
||||
if temporary:
|
||||
self.reverter.add_to_temp_checkpoint(
|
||||
save_files, self.save_notes)
|
||||
else:
|
||||
self.reverter.add_to_checkpoint(save_files,
|
||||
try:
|
||||
# Create Checkpoint
|
||||
if temporary:
|
||||
self.reverter.add_to_temp_checkpoint(
|
||||
save_files, self.save_notes)
|
||||
else:
|
||||
self.reverter.add_to_checkpoint(save_files,
|
||||
self.save_notes)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
self.save_notes = ""
|
||||
|
||||
# Change 'ext' to something else to not override existing conf files
|
||||
self.parser.filedump(ext='')
|
||||
if title and not temporary:
|
||||
self.reverter.finalize_checkpoint(title)
|
||||
try:
|
||||
self.reverter.finalize_checkpoint(title)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
return True
|
||||
|
||||
@@ -535,13 +553,25 @@ class NginxConfigurator(common.Plugin):
|
||||
|
||||
Reverts all modified files that have not been saved as a checkpoint
|
||||
|
||||
:raises .errors.PluginError: If unable to recover the configuration
|
||||
|
||||
"""
|
||||
self.reverter.recovery_routine()
|
||||
try:
|
||||
self.reverter.recovery_routine()
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
self.parser.load()
|
||||
|
||||
def revert_challenge_config(self):
|
||||
"""Used to cleanup challenge configurations."""
|
||||
self.reverter.revert_temporary_config()
|
||||
"""Used to cleanup challenge configurations.
|
||||
|
||||
:raises .errors.PluginError: If unable to revert the challenge config.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.reverter.revert_temporary_config()
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
self.parser.load()
|
||||
|
||||
def rollback_checkpoints(self, rollback=1):
|
||||
@@ -549,13 +579,27 @@ class NginxConfigurator(common.Plugin):
|
||||
|
||||
:param int rollback: Number of checkpoints to revert
|
||||
|
||||
:raises .errors.PluginError: If there is a problem with the input or
|
||||
the function is unable to correctly revert the configuration
|
||||
|
||||
"""
|
||||
self.reverter.rollback_checkpoints(rollback)
|
||||
try:
|
||||
self.reverter.rollback_checkpoints(rollback)
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
self.parser.load()
|
||||
|
||||
def view_config_changes(self):
|
||||
"""Show all of the configuration changes that have taken place."""
|
||||
self.reverter.view_config_changes()
|
||||
"""Show all of the configuration changes that have taken place.
|
||||
|
||||
:raises .errors.PluginError: If there is a problem while processing
|
||||
the checkpoints directories.
|
||||
|
||||
"""
|
||||
try:
|
||||
self.reverter.view_config_changes()
|
||||
except errors.ReverterError as err:
|
||||
raise errors.PluginError(str(err))
|
||||
|
||||
###########################################################################
|
||||
# Challenges Section for IAuthenticator
|
||||
|
||||
@@ -371,6 +371,31 @@ class NginxConfiguratorTest(util.NginxTest):
|
||||
mock_run_script.side_effect = errors.SubprocessError
|
||||
self.assertRaises(errors.MisconfigurationError, self.config.config_test)
|
||||
|
||||
@mock.patch("letsencrypt.reverter.Reverter.recovery_routine")
|
||||
def test_recovery_routine_throws_error_from_reverter(self, mock_recovery_routine):
|
||||
mock_recovery_routine.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.recovery_routine)
|
||||
|
||||
@mock.patch("letsencrypt.reverter.Reverter.view_config_changes")
|
||||
def test_view_config_changes_throws_error_from_reverter(self, mock_view_config_changes):
|
||||
mock_view_config_changes.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.view_config_changes)
|
||||
|
||||
@mock.patch("letsencrypt.reverter.Reverter.rollback_checkpoints")
|
||||
def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints):
|
||||
mock_rollback_checkpoints.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.rollback_checkpoints)
|
||||
|
||||
@mock.patch("letsencrypt.reverter.Reverter.revert_temporary_config")
|
||||
def test_revert_challenge_config_throws_error_from_reverter(self, mock_revert_temporary_config):
|
||||
mock_revert_temporary_config.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.revert_challenge_config)
|
||||
|
||||
@mock.patch("letsencrypt.reverter.Reverter.add_to_checkpoint")
|
||||
def test_save_throws_error_from_reverter(self, mock_add_to_checkpoint):
|
||||
mock_add_to_checkpoint.side_effect = errors.ReverterError("foo")
|
||||
self.assertRaises(errors.PluginError, self.config.save)
|
||||
|
||||
def test_get_snakeoil_paths(self):
|
||||
# pylint: disable=protected-access
|
||||
cert, key = self.config._get_snakeoil_paths()
|
||||
|
||||
@@ -230,23 +230,23 @@ class NginxParserTest(util.NginxTest):
|
||||
|
||||
def test_parse_server_ssl(self):
|
||||
server = parser.parse_server([
|
||||
['listen', '443']
|
||||
])
|
||||
['listen', '443']
|
||||
])
|
||||
self.assertFalse(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443 ssl']
|
||||
])
|
||||
['listen', '443 ssl']
|
||||
])
|
||||
self.assertTrue(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443'], ['ssl', 'off']
|
||||
])
|
||||
['listen', '443'], ['ssl', 'off']
|
||||
])
|
||||
self.assertFalse(server['ssl'])
|
||||
|
||||
server = parser.parse_server([
|
||||
['listen', '443'], ['ssl', 'on']
|
||||
])
|
||||
['listen', '443'], ['ssl', 'on']
|
||||
])
|
||||
self.assertTrue(server['ssl'])
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -319,12 +319,11 @@ def _handle_identical_cert_request(config, cert):
|
||||
elif config.verb == "certonly":
|
||||
keep_opt = "Keep the existing certificate for now"
|
||||
choices = [keep_opt,
|
||||
"Renew & replace the cert (limit ~5 per 7 days)",
|
||||
"Cancel this operation and do nothing"]
|
||||
"Renew & replace the cert (limit ~5 per 7 days)"]
|
||||
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
response = display.menu(question, choices, "OK", "Cancel", default=0)
|
||||
if response[0] == "cancel" or response[1] == 2:
|
||||
if response[0] == display_util.CANCEL:
|
||||
# TODO: Add notification related to command-line options for
|
||||
# skipping the menu for this case.
|
||||
raise errors.Error(
|
||||
@@ -873,7 +872,10 @@ def _restore_webroot_config(config, renewalparams):
|
||||
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"])
|
||||
wp = renewalparams["webroot_path"]
|
||||
if isinstance(wp, str): # prior to 0.1.0, webroot_path was a string
|
||||
wp = [wp]
|
||||
setattr(config.namespace, "webroot_path", wp)
|
||||
|
||||
|
||||
def _reconstitute(config, full_path):
|
||||
@@ -984,10 +986,6 @@ def renew(config, unused_plugins):
|
||||
"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 = []
|
||||
@@ -1031,6 +1029,12 @@ def renew(config, unused_plugins):
|
||||
_renew_describe_results(config, renew_successes, renew_failures,
|
||||
renew_skipped, parse_failures)
|
||||
|
||||
if renew_failures or parse_failures:
|
||||
raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format(
|
||||
len(renew_failures), len(parse_failures)))
|
||||
else:
|
||||
logger.debug("no renewal failures")
|
||||
|
||||
|
||||
def revoke(config, unused_plugins): # TODO: coop with renewal config
|
||||
"""Revoke a previously obtained certificate."""
|
||||
@@ -1245,6 +1249,12 @@ class HelpfulArgumentParser(object):
|
||||
Process a --csr flag. This needs to happen early enough that the
|
||||
webroot plugin can know about the calls to _process_domain
|
||||
"""
|
||||
if parsed_args.verb != "certonly":
|
||||
raise errors.Error("Currently, a CSR file may only be specified "
|
||||
"when obtaining a new or replacement "
|
||||
"via the certonly command. Please try the "
|
||||
"certonly command instead.")
|
||||
|
||||
try:
|
||||
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
|
||||
typ = OpenSSL.crypto.FILETYPE_ASN1
|
||||
@@ -1973,17 +1983,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__":
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Let's Encrypt user-supplied configuration."""
|
||||
import copy
|
||||
import os
|
||||
import urlparse
|
||||
|
||||
from six.moves.urllib import parse # pylint: disable=import-error
|
||||
import zope.interface
|
||||
|
||||
from letsencrypt import constants
|
||||
@@ -11,6 +11,7 @@ from letsencrypt import interfaces
|
||||
from letsencrypt import le_util
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IConfig)
|
||||
class NamespaceConfig(object):
|
||||
"""Configuration wrapper around :class:`argparse.Namespace`.
|
||||
|
||||
@@ -32,7 +33,6 @@ class NamespaceConfig(object):
|
||||
:type namespace: :class:`argparse.Namespace`
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IConfig)
|
||||
|
||||
def __init__(self, namespace):
|
||||
self.namespace = namespace
|
||||
@@ -50,7 +50,7 @@ class NamespaceConfig(object):
|
||||
@property
|
||||
def server_path(self):
|
||||
"""File path based on ``server``."""
|
||||
parsed = urlparse.urlparse(self.namespace.server)
|
||||
parsed = parse.urlparse(self.namespace.server)
|
||||
return (parsed.netloc + parsed.path).replace('/', os.path.sep)
|
||||
|
||||
@property
|
||||
|
||||
@@ -9,6 +9,7 @@ from letsencrypt import interfaces
|
||||
from letsencrypt import proof_of_possession
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
class ContinuityAuthenticator(object):
|
||||
"""IAuthenticator for
|
||||
:const:`~acme.challenges.ContinuityChallenge` class challenges.
|
||||
@@ -18,7 +19,6 @@ class ContinuityAuthenticator(object):
|
||||
:class:`letsencrypt.proof_of_possession.Proof_of_Possession`
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
|
||||
# This will have an installer soon for get_key/cert purposes
|
||||
def __init__(self, config, installer): # pylint: disable=unused-argument
|
||||
|
||||
@@ -118,6 +118,7 @@ def make_csr(key_str, domains):
|
||||
value=", ".join("DNS:%s" % d for d in domains)
|
||||
),
|
||||
])
|
||||
req.set_version(2)
|
||||
req.set_pubkey(pkey)
|
||||
req.sign(pkey, "sha256")
|
||||
return tuple(OpenSSL.crypto.dump_certificate_request(method, req)
|
||||
|
||||
@@ -36,11 +36,10 @@ def _wrap_lines(msg):
|
||||
fixed_l.append(textwrap.fill(line, 80))
|
||||
return os.linesep.join(fixed_l)
|
||||
|
||||
@zope.interface.implementer(interfaces.IDisplay)
|
||||
class NcursesDisplay(object):
|
||||
"""Ncurses-based display."""
|
||||
|
||||
zope.interface.implements(interfaces.IDisplay)
|
||||
|
||||
def __init__(self, width=WIDTH, height=HEIGHT):
|
||||
super(NcursesDisplay, self).__init__()
|
||||
self.dialog = dialog.Dialog()
|
||||
@@ -176,11 +175,10 @@ class NcursesDisplay(object):
|
||||
message, width=self.width, height=self.height, choices=choices)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IDisplay)
|
||||
class FileDisplay(object):
|
||||
"""File-based display."""
|
||||
|
||||
zope.interface.implements(interfaces.IDisplay)
|
||||
|
||||
def __init__(self, outfile):
|
||||
super(FileDisplay, self).__init__()
|
||||
self.outfile = outfile
|
||||
@@ -411,11 +409,10 @@ class FileDisplay(object):
|
||||
|
||||
return OK, selection
|
||||
|
||||
@zope.interface.implementer(interfaces.IDisplay)
|
||||
class NoninteractiveDisplay(object):
|
||||
"""An iDisplay implementation that never asks for interactive user input"""
|
||||
|
||||
zope.interface.implements(interfaces.IDisplay)
|
||||
|
||||
def __init__(self, outfile):
|
||||
super(NoninteractiveDisplay, self).__init__()
|
||||
self.outfile = outfile
|
||||
|
||||
@@ -169,7 +169,9 @@ class IAuthenticator(IPlugin):
|
||||
Authenticator will never be able to perform (error).
|
||||
|
||||
:rtype: :class:`list` of
|
||||
:class:`acme.challenges.ChallengeResponse`
|
||||
:class:`acme.challenges.ChallengeResponse`,
|
||||
where responses are required to be returned in
|
||||
the same order as corresponding input challenges
|
||||
|
||||
:raises .PluginError: If challenges cannot be performed
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import logging
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import socket
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -317,6 +318,18 @@ def enforce_domain_sanity(domain):
|
||||
# Remove trailing dot
|
||||
domain = domain[:-1] if domain.endswith('.') else domain
|
||||
|
||||
# Explain separately that IP addresses aren't allowed (apart from not
|
||||
# being FQDNs) because hope springs eternal concerning this point
|
||||
try:
|
||||
socket.inet_aton(domain)
|
||||
raise errors.ConfigurationError(
|
||||
"Requested name {0} is an IP address. The Let's Encrypt "
|
||||
"certificate authority will not issue certificates for a "
|
||||
"bare IP address.".format(domain))
|
||||
except socket.error:
|
||||
# It wasn't an IP address, so that's good
|
||||
pass
|
||||
|
||||
# FQDN checks from
|
||||
# http://www.mkyong.com/regular-expressions/domain-name-regular-expression-example/
|
||||
# Characters used, domain parts < 63 chars, tld > 1 < 64 chars
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"""Plugin common functions."""
|
||||
import os
|
||||
import pkg_resources
|
||||
import re
|
||||
import shutil
|
||||
import tempfile
|
||||
|
||||
import OpenSSL
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from acme.jose import util as jose_util
|
||||
@@ -31,11 +31,11 @@ hostname_regex = re.compile(
|
||||
r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IPlugin)
|
||||
class Plugin(object):
|
||||
"""Generic plugin."""
|
||||
zope.interface.implements(interfaces.IPlugin)
|
||||
# classProvides is not inherited, subclasses must define it on their own
|
||||
#zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
# provider is not inherited, subclasses must define it on their own
|
||||
# @zope.interface.provider(interfaces.IPluginFactory)
|
||||
|
||||
def __init__(self, config, name):
|
||||
self.config = config
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
"""Tests for letsencrypt.plugins.disco."""
|
||||
import pkg_resources
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
import pkg_resources
|
||||
import zope.interface
|
||||
|
||||
from letsencrypt import errors
|
||||
|
||||
@@ -23,6 +23,8 @@ from letsencrypt.plugins import common
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(common.Plugin):
|
||||
"""Manual Authenticator.
|
||||
|
||||
@@ -34,8 +36,6 @@ class Authenticator(common.Plugin):
|
||||
.. todo:: Support for `~.challenges.TLSSNI01`.
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
hidden = True
|
||||
|
||||
description = "Manually configure an HTTP server"
|
||||
|
||||
@@ -11,10 +11,10 @@ from letsencrypt.plugins import common
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IInstaller)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Installer(common.Plugin):
|
||||
"""Null installer."""
|
||||
zope.interface.implements(interfaces.IInstaller)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Null Installer"
|
||||
hidden = True
|
||||
|
||||
@@ -135,6 +135,8 @@ def supported_challenges_validator(data):
|
||||
return data
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(common.Plugin):
|
||||
"""Standalone Authenticator.
|
||||
|
||||
@@ -143,8 +145,6 @@ class Authenticator(common.Plugin):
|
||||
challenges from the certificate authority. Therefore, it does not
|
||||
rely on any existing server program.
|
||||
"""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Automatically use a temporary webserver"
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
import errno
|
||||
import logging
|
||||
import os
|
||||
from collections import defaultdict
|
||||
|
||||
import zope.interface
|
||||
import six
|
||||
|
||||
from acme import challenges
|
||||
|
||||
@@ -15,10 +17,10 @@ from letsencrypt.plugins import common
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
class Authenticator(common.Plugin):
|
||||
"""Webroot Authenticator."""
|
||||
zope.interface.implements(interfaces.IAuthenticator)
|
||||
zope.interface.classProvides(interfaces.IPluginFactory)
|
||||
|
||||
description = "Webroot Authenticator"
|
||||
|
||||
@@ -44,6 +46,7 @@ to serve all files under specified web root ({0})."""
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Authenticator, self).__init__(*args, **kwargs)
|
||||
self.full_roots = {}
|
||||
self.performed = defaultdict(set)
|
||||
|
||||
def prepare(self): # pylint: disable=missing-docstring
|
||||
path_map = self.conf("map")
|
||||
@@ -97,7 +100,7 @@ to serve all files under specified web root ({0})."""
|
||||
assert self.full_roots, "Webroot plugin appears to be missing webroot map"
|
||||
return [self._perform_single(achall) for achall in achalls]
|
||||
|
||||
def _path_for_achall(self, achall):
|
||||
def _get_root_path(self, achall):
|
||||
try:
|
||||
path = self.full_roots[achall.domain]
|
||||
except KeyError:
|
||||
@@ -106,27 +109,48 @@ to serve all files under specified web root ({0})."""
|
||||
if not os.path.exists(path):
|
||||
raise errors.PluginError("Mysteriously missing path {0} for domain: {1}"
|
||||
.format(path, achall.domain))
|
||||
return os.path.join(path, achall.chall.encode("token"))
|
||||
return path
|
||||
|
||||
def _get_validation_path(self, root_path, achall):
|
||||
return os.path.join(root_path, achall.chall.encode("token"))
|
||||
|
||||
def _perform_single(self, achall):
|
||||
response, validation = achall.response_and_validation()
|
||||
|
||||
path = self._path_for_achall(achall)
|
||||
logger.debug("Attempting to save validation to %s", path)
|
||||
root_path = self._get_root_path(achall)
|
||||
validation_path = self._get_validation_path(root_path, achall)
|
||||
logger.debug("Attempting to save validation to %s", validation_path)
|
||||
|
||||
# Change permissions to be world-readable, owner-writable (GH #1795)
|
||||
old_umask = os.umask(0o022)
|
||||
|
||||
try:
|
||||
with open(path, "w") as validation_file:
|
||||
with open(validation_path, "w") as validation_file:
|
||||
validation_file.write(validation.encode())
|
||||
finally:
|
||||
os.umask(old_umask)
|
||||
|
||||
self.performed[root_path].add(achall)
|
||||
|
||||
return response
|
||||
|
||||
def cleanup(self, achalls): # pylint: disable=missing-docstring
|
||||
for achall in achalls:
|
||||
path = self._path_for_achall(achall)
|
||||
logger.debug("Removing %s", path)
|
||||
os.remove(path)
|
||||
root_path = self._get_root_path(achall)
|
||||
validation_path = self._get_validation_path(root_path, achall)
|
||||
logger.debug("Removing %s", validation_path)
|
||||
os.remove(validation_path)
|
||||
self.performed[root_path].remove(achall)
|
||||
|
||||
for root_path, achalls in six.iteritems(self.performed):
|
||||
if not achalls:
|
||||
try:
|
||||
os.rmdir(root_path)
|
||||
logger.debug("All challenges cleaned up, removing %s",
|
||||
root_path)
|
||||
except OSError as exc:
|
||||
if exc.errno == errno.ENOTEMPTY:
|
||||
logger.debug("Challenges cleaned up but %s not empty",
|
||||
root_path)
|
||||
else:
|
||||
raise
|
||||
|
||||
@@ -30,8 +30,10 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
from letsencrypt.plugins.webroot import Authenticator
|
||||
self.path = tempfile.mkdtemp()
|
||||
self.root_challenge_path = os.path.join(
|
||||
self.path, ".well-known", "acme-challenge")
|
||||
self.validation_path = os.path.join(
|
||||
self.path, ".well-known", "acme-challenge",
|
||||
self.root_challenge_path,
|
||||
"ZXZhR3hmQURzNnBTUmIyTEF2OUlaZjE3RHQzanV4R0orUEN0OTJ3citvQQ")
|
||||
self.config = mock.MagicMock(webroot_path=self.path,
|
||||
webroot_map={"thing.com": self.path})
|
||||
@@ -137,6 +139,33 @@ class AuthenticatorTest(unittest.TestCase):
|
||||
|
||||
self.auth.cleanup([self.achall])
|
||||
self.assertFalse(os.path.exists(self.validation_path))
|
||||
self.assertFalse(os.path.exists(self.root_challenge_path))
|
||||
|
||||
def test_cleanup_leftovers(self):
|
||||
self.auth.prepare()
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
leftover_path = os.path.join(self.root_challenge_path, 'leftover')
|
||||
os.mkdir(leftover_path)
|
||||
|
||||
self.auth.cleanup([self.achall])
|
||||
self.assertFalse(os.path.exists(self.validation_path))
|
||||
self.assertTrue(os.path.exists(self.root_challenge_path))
|
||||
|
||||
os.rmdir(leftover_path)
|
||||
|
||||
@mock.patch('os.rmdir')
|
||||
def test_cleanup_oserror(self, mock_rmdir):
|
||||
self.auth.prepare()
|
||||
self.auth.perform([self.achall])
|
||||
|
||||
os_error = OSError()
|
||||
os_error.errno = errno.EACCES
|
||||
mock_rmdir.side_effect = os_error
|
||||
|
||||
self.assertRaises(OSError, self.auth.cleanup, [self.achall])
|
||||
self.assertFalse(os.path.exists(self.validation_path))
|
||||
self.assertTrue(os.path.exists(self.root_challenge_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -17,6 +17,7 @@ from letsencrypt import le_util
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@zope.interface.implementer(interfaces.IReporter)
|
||||
class Reporter(object):
|
||||
"""Collects and displays information to the user.
|
||||
|
||||
@@ -24,7 +25,6 @@ class Reporter(object):
|
||||
the user.
|
||||
|
||||
"""
|
||||
zope.interface.implements(interfaces.IReporter)
|
||||
|
||||
HIGH_PRIORITY = 0
|
||||
"""High priority constant. See `add_message`."""
|
||||
|
||||
@@ -112,6 +112,21 @@ def update_configuration(lineagename, target, cli_config):
|
||||
return configobj.ConfigObj(config_filename)
|
||||
|
||||
|
||||
def get_link_target(link):
|
||||
"""Get an absolute path to the target of link.
|
||||
|
||||
:param str link: Path to a symbolic link
|
||||
|
||||
:returns: Absolute path to the target of link
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
target = os.readlink(link)
|
||||
if not os.path.isabs(target):
|
||||
target = os.path.join(os.path.dirname(link), target)
|
||||
return os.path.abspath(target)
|
||||
|
||||
|
||||
class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
"""Renewable certificate.
|
||||
|
||||
@@ -194,13 +209,15 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
|
||||
def _check_symlinks(self):
|
||||
"""Raises an exception if a symlink doesn't exist"""
|
||||
def check(link):
|
||||
"""Checks if symlink points to a file that exists"""
|
||||
return os.path.exists(os.path.realpath(link))
|
||||
for kind in ALL_FOUR:
|
||||
if not check(getattr(self, kind)):
|
||||
link = getattr(self, kind)
|
||||
if not os.path.islink(link):
|
||||
raise errors.CertStorageError(
|
||||
"link: {0} does not exist".format(getattr(self, kind)))
|
||||
"expected {0} to be a symlink".format(link))
|
||||
target = get_link_target(link)
|
||||
if not os.path.exists(target):
|
||||
raise errors.CertStorageError("target {0} of symlink {1} does "
|
||||
"not exist".format(target, link))
|
||||
|
||||
def _consistent(self):
|
||||
"""Are the files associated with this lineage self-consistent?
|
||||
@@ -225,10 +242,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
return False
|
||||
for kind in ALL_FOUR:
|
||||
link = getattr(self, kind)
|
||||
where = os.path.dirname(link)
|
||||
target = os.readlink(link)
|
||||
if not os.path.isabs(target):
|
||||
target = os.path.join(where, target)
|
||||
target = get_link_target(link)
|
||||
|
||||
# Each element's link must point within the cert lineage's
|
||||
# directory within the official archive directory
|
||||
@@ -343,10 +357,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
|
||||
logger.debug("Expected symlink %s for %s does not exist.",
|
||||
link, kind)
|
||||
return None
|
||||
target = os.readlink(link)
|
||||
if not os.path.isabs(target):
|
||||
target = os.path.join(os.path.dirname(link), target)
|
||||
return os.path.abspath(target)
|
||||
return get_link_target(link)
|
||||
|
||||
def current_version(self, kind):
|
||||
"""Returns numerical version of the specified item.
|
||||
|
||||
@@ -20,6 +20,7 @@ from letsencrypt import constants
|
||||
from letsencrypt import crypto_util
|
||||
from letsencrypt import errors
|
||||
from letsencrypt import le_util
|
||||
from letsencrypt import storage
|
||||
|
||||
from letsencrypt.plugins import disco
|
||||
from letsencrypt.plugins import manual
|
||||
@@ -351,6 +352,20 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
self._call,
|
||||
['-d', '*.wildcard.tld'])
|
||||
|
||||
# Bare IP address (this is actually a different error message now)
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
self._call,
|
||||
['-d', '204.11.231.35'])
|
||||
|
||||
def test_run_with_csr(self):
|
||||
# This is an error because you can only use --csr with certonly
|
||||
try:
|
||||
self._call(['--csr', CSR])
|
||||
except errors.Error as e:
|
||||
assert "Please try the certonly" in e.message
|
||||
return
|
||||
assert False, "Expected supplying --csr to fail with default verb"
|
||||
|
||||
def _get_argument_parser(self):
|
||||
plugins = disco.PluginsRegistry.find_all()
|
||||
return functools.partial(cli.prepare_and_parse_args, plugins)
|
||||
@@ -535,8 +550,8 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
self._certonly_new_request_common, mock_client)
|
||||
|
||||
def _test_renewal_common(self, due_for_renewal, extra_args, log_out=None,
|
||||
args=None, renew=True):
|
||||
# pylint: disable=too-many-locals
|
||||
args=None, renew=True, error_expected=False):
|
||||
# pylint: disable=too-many-locals,too-many-arguments
|
||||
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)
|
||||
@@ -562,11 +577,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly']
|
||||
if extra_args:
|
||||
args += extra_args
|
||||
self._call(args)
|
||||
|
||||
if log_out:
|
||||
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
|
||||
self.assertTrue(log_out in lf.read())
|
||||
try:
|
||||
ret, _, _, _ = self._call(args)
|
||||
if ret:
|
||||
print "Returned", ret
|
||||
raise AssertionError(ret)
|
||||
assert not error_expected, "renewal should have errored"
|
||||
except: # pylint: disable=bare-except
|
||||
if not error_expected:
|
||||
raise AssertionError(
|
||||
"Unexpected renewal error:\n" +
|
||||
traceback.format_exc())
|
||||
|
||||
if renew:
|
||||
mock_client.obtain_certificate.assert_called_once_with(['isnot.org'])
|
||||
@@ -575,6 +596,10 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
except:
|
||||
self._dump_log()
|
||||
raise
|
||||
finally:
|
||||
if log_out:
|
||||
with open(os.path.join(self.logs_dir, "letsencrypt.log")) as lf:
|
||||
self.assertTrue(log_out in lf.read())
|
||||
|
||||
return mock_lineage, mock_get_utility
|
||||
|
||||
@@ -606,8 +631,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
print "Logs:"
|
||||
print lf.read()
|
||||
|
||||
def test_renew_verb(self):
|
||||
with open(test_util.vector_path('sample-renewal.conf')) as src:
|
||||
|
||||
def _make_test_renewal_conf(self, testfile):
|
||||
with open(test_util.vector_path(testfile)) 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")
|
||||
@@ -616,15 +642,34 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
rc = os.path.join(rd, "sample-renewal.conf")
|
||||
with open(rc, "w") as dest:
|
||||
dest.write(renewal_conf)
|
||||
return rc
|
||||
|
||||
def test_renew_verb(self):
|
||||
self._make_test_renewal_conf('sample-renewal.conf')
|
||||
args = ["renew", "--dry-run", "-tvv"]
|
||||
self._test_renewal_common(True, [], args=args, renew=True)
|
||||
|
||||
@mock.patch("letsencrypt.cli._set_by_cli")
|
||||
def test_ancient_webroot_renewal_conf(self, mock_set_by_cli):
|
||||
mock_set_by_cli.return_value = False
|
||||
rc_path = self._make_test_renewal_conf('sample-renewal-ancient.conf')
|
||||
args = mock.MagicMock(account=None, email=None, webroot_path=None)
|
||||
config = configuration.NamespaceConfig(args)
|
||||
lineage = storage.RenewableCert(rc_path,
|
||||
configuration.RenewerConfiguration(config))
|
||||
renewalparams = lineage.configuration["renewalparams"]
|
||||
# pylint: disable=protected-access
|
||||
cli._restore_webroot_config(config, renewalparams)
|
||||
self.assertEqual(config.webroot_path, ["/var/www/"])
|
||||
|
||||
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'):
|
||||
rd = os.path.join(self.config_dir, 'renewal')
|
||||
if not os.path.exists(rd):
|
||||
os.makedirs(rd)
|
||||
with open(os.path.join(rd, 'empty.conf'), 'w'):
|
||||
pass # leave the file empty
|
||||
self.test_renew_verb()
|
||||
args = ["renew", "--dry-run", "-tvv"]
|
||||
self._test_renewal_common(False, [], args=args, renew=False, error_expected=True)
|
||||
|
||||
def _make_dummy_renewal_config(self):
|
||||
renewer_configs_dir = os.path.join(self.config_dir, 'renewal')
|
||||
@@ -632,7 +677,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
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,
|
||||
def _test_renew_common(self, renewalparams=None, error_expected=False,
|
||||
names=None, assert_oc_called=None):
|
||||
self._make_dummy_renewal_config()
|
||||
with mock.patch('letsencrypt.storage.RenewableCert') as mock_rc:
|
||||
@@ -644,7 +689,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
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,
|
||||
self._test_renewal_common(True, None, error_expected=error_expected,
|
||||
args=['renew'], renew=False)
|
||||
if assert_oc_called is not None:
|
||||
if assert_oc_called:
|
||||
@@ -653,21 +698,22 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
self.assertFalse(mock_obtain_cert.called)
|
||||
|
||||
def test_renew_no_renewalparams(self):
|
||||
self._test_renew_common(assert_oc_called=False)
|
||||
self._test_renew_common(assert_oc_called=False, error_expected=True)
|
||||
|
||||
def test_renew_no_authenticator(self):
|
||||
self._test_renew_common(renewalparams={}, assert_oc_called=False)
|
||||
self._test_renew_common(renewalparams={}, assert_oc_called=False,
|
||||
error_expected=True)
|
||||
|
||||
def test_renew_with_bad_int(self):
|
||||
renewalparams = {'authenticator': 'webroot',
|
||||
'rsa_key_size': 'over 9000'}
|
||||
self._test_renew_common(renewalparams=renewalparams,
|
||||
self._test_renew_common(renewalparams=renewalparams, error_expected=True,
|
||||
assert_oc_called=False)
|
||||
|
||||
def test_renew_with_bad_domain(self):
|
||||
renewalparams = {'authenticator': 'webroot'}
|
||||
names = ['*.example.com']
|
||||
self._test_renew_common(renewalparams=renewalparams,
|
||||
self._test_renew_common(renewalparams=renewalparams, error_expected=True,
|
||||
names=names, assert_oc_called=False)
|
||||
|
||||
def test_renew_plugin_config_restoration(self):
|
||||
@@ -681,7 +727,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
# 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)
|
||||
self._test_renew_common(assert_oc_called=False, error_expected=True)
|
||||
|
||||
def test_renew_obtain_cert_error(self):
|
||||
self._make_dummy_renewal_config()
|
||||
@@ -693,15 +739,14 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
||||
'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,
|
||||
self._test_renewal_common(True, None, error_expected=True,
|
||||
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)
|
||||
self._test_renewal_common(True, None, args='renew -d example.com'.split(),
|
||||
renew=False, error_expected=True)
|
||||
self._test_renewal_common(True, None, args='renew --csr {0}'.format(CSR).split(),
|
||||
renew=False, error_expected=True)
|
||||
|
||||
@mock.patch('letsencrypt.cli.zope.component.getUtility')
|
||||
@mock.patch('letsencrypt.cli._treat_as_renewal')
|
||||
|
||||
@@ -74,6 +74,23 @@ class RegisterTest(unittest.TestCase):
|
||||
self.config.email = None
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
|
||||
@mock.patch("letsencrypt.client.logger")
|
||||
def test_without_email(self, mock_logger):
|
||||
with mock.patch("letsencrypt.client.acme_client.Client"):
|
||||
with mock.patch("letsencrypt.account.report_new_account"):
|
||||
self.config.email = None
|
||||
self.config.register_unsafely_without_email = True
|
||||
self._call()
|
||||
mock_logger.warn.assert_called_once_with(mock.ANY)
|
||||
|
||||
def test_unsupported_error(self):
|
||||
from acme import messages
|
||||
msg = "Test"
|
||||
mx_err = messages.Error(detail=msg, typ="malformed", title="title")
|
||||
with mock.patch("letsencrypt.client.acme_client.Client") as mock_client:
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self.assertRaises(messages.Error, self._call)
|
||||
|
||||
class ClientTest(unittest.TestCase):
|
||||
"""Tests for letsencrypt.client.Client."""
|
||||
|
||||
@@ -110,12 +127,16 @@ class ClientTest(unittest.TestCase):
|
||||
self.acme.fetch_chain.assert_called_once_with(mock.sentinel.certr)
|
||||
|
||||
# FIXME move parts of this to test_cli.py...
|
||||
@mock.patch("letsencrypt.client.logger")
|
||||
@mock.patch("letsencrypt.cli._process_domain")
|
||||
def test_obtain_certificate_from_csr(self, mock_process_domain):
|
||||
def test_obtain_certificate_from_csr(self, mock_process_domain, mock_logger):
|
||||
self._mock_obtain_certificate()
|
||||
from letsencrypt import cli
|
||||
test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
|
||||
mock_parsed_args = mock.MagicMock()
|
||||
# The CLI should believe that this is a certonly request, because
|
||||
# a CSR would not be allowed with other kinds of requests!
|
||||
mock_parsed_args.verb = "certonly"
|
||||
with mock.patch("letsencrypt.client.le_util.CSR") as mock_CSR:
|
||||
mock_CSR.return_value = test_csr
|
||||
mock_parsed_args.domains = self.eg_domains[:]
|
||||
@@ -136,6 +157,14 @@ class ClientTest(unittest.TestCase):
|
||||
# and that the cert was obtained correctly
|
||||
self._check_obtain_certificate()
|
||||
|
||||
# Test for no auth_handler
|
||||
self.client.auth_handler = None
|
||||
self.assertRaises(
|
||||
errors.Error,
|
||||
self.client.obtain_certificate_from_csr,
|
||||
self.eg_domains,
|
||||
test_csr)
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
|
||||
@mock.patch("letsencrypt.client.crypto_util")
|
||||
def test_obtain_certificate(self, mock_crypto_util):
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"""Tests for letsencrypt.storage."""
|
||||
import datetime
|
||||
import pytz
|
||||
import os
|
||||
import tempfile
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import configobj
|
||||
import mock
|
||||
import pytz
|
||||
|
||||
from letsencrypt import configuration
|
||||
from letsencrypt import errors
|
||||
@@ -687,6 +687,10 @@ class RenewableCertTests(BaseRenewableCertTest):
|
||||
self.assertRaises(errors.CertStorageError,
|
||||
storage.RenewableCert,
|
||||
self.config.filename, self.cli_config)
|
||||
os.symlink("missing", self.config[ALL_FOUR[0]])
|
||||
self.assertRaises(errors.CertStorageError,
|
||||
storage.RenewableCert,
|
||||
self.config.filename, self.cli_config)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
75
letsencrypt/tests/testdata/sample-renewal-ancient.conf
vendored
Executable file
75
letsencrypt/tests/testdata/sample-renewal-ancient.conf
vendored
Executable file
@@ -0,0 +1,75 @@
|
||||
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 = webroot
|
||||
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 = /var/www/
|
||||
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 = True
|
||||
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
|
||||
@@ -2,7 +2,7 @@ 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
|
||||
renew_before_expiry = 4 years
|
||||
|
||||
# Options and defaults used in the renewal process
|
||||
[renewalparams]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e # Fail fast
|
||||
|
||||
# PEP8 is not ignored in ACME
|
||||
pep8 --config=acme/.pep8 acme
|
||||
|
||||
pep8 \
|
||||
setup.py \
|
||||
acme \
|
||||
letsencrypt \
|
||||
letsencrypt-apache \
|
||||
letsencrypt-nginx \
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
[easy_install]
|
||||
zip_ok = false
|
||||
|
||||
[aliases]
|
||||
dev = develop easy_install letsencrypt[dev,docs,testing]
|
||||
|
||||
[nosetests]
|
||||
nocapture=1
|
||||
cover-package=letsencrypt,acme,letsencrypt_apache,letsencrypt_nginx
|
||||
|
||||
14
setup.py
14
setup.py
@@ -65,7 +65,12 @@ else:
|
||||
dev_extras = [
|
||||
# Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289
|
||||
'astroid==1.3.5',
|
||||
'coverage',
|
||||
'nose',
|
||||
'nosexcover',
|
||||
'pep8',
|
||||
'pylint==1.4.2', # upstream #248
|
||||
'tox',
|
||||
'twine',
|
||||
'wheel',
|
||||
]
|
||||
@@ -77,14 +82,6 @@ docs_extras = [
|
||||
'sphinxcontrib-programoutput',
|
||||
]
|
||||
|
||||
testing_extras = [
|
||||
'coverage',
|
||||
'nose',
|
||||
'nosexcover',
|
||||
'pep8',
|
||||
'tox',
|
||||
]
|
||||
|
||||
setup(
|
||||
name='letsencrypt',
|
||||
version=version,
|
||||
@@ -120,7 +117,6 @@ setup(
|
||||
extras_require={
|
||||
'dev': dev_extras,
|
||||
'docs': docs_extras,
|
||||
'testing': testing_extras,
|
||||
},
|
||||
|
||||
# to test all packages run "python setup.py test -s
|
||||
|
||||
@@ -68,7 +68,7 @@ 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"
|
||||
sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf"
|
||||
common_no_force_renew renew --rsa-key-size 2048
|
||||
CheckCertCount 3
|
||||
|
||||
|
||||
@@ -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:
|
||||
@@ -333,6 +333,55 @@ def create_client_instances(targetlist):
|
||||
print()
|
||||
return instances
|
||||
|
||||
|
||||
def test_client_process(inqueue, outqueue):
|
||||
cur_proc = mp.current_process()
|
||||
for inreq in iter(inqueue.get, SENTINEL):
|
||||
ii, target = inreq
|
||||
|
||||
#save all stdout to log file
|
||||
sys.stdout = open(LOGDIR+'/'+'%d_%s.log'%(ii,target['name']), 'w')
|
||||
|
||||
print("[%s : client %d %s %s]" % (cur_proc.name, ii, target['ami'], target['name']))
|
||||
instances[ii] = block_until_instance_ready(instances[ii])
|
||||
print("server %s at %s"%(instances[ii], instances[ii].public_ip_address))
|
||||
env.host_string = "%s@%s"%(target['user'], instances[ii].public_ip_address)
|
||||
print(env.host_string)
|
||||
|
||||
try:
|
||||
install_and_launch_letsencrypt(instances[ii], boulder_url, target)
|
||||
outqueue.put((ii, target, 'pass'))
|
||||
print("%s - %s SUCCESS"%(target['ami'], target['name']))
|
||||
except:
|
||||
outqueue.put((ii, target, 'fail'))
|
||||
print("%s - %s FAIL"%(target['ami'], target['name']))
|
||||
pass
|
||||
|
||||
# append server letsencrypt.log to each per-machine output log
|
||||
print("\n\nletsencrypt.log\n" + "-"*80 + "\n")
|
||||
try:
|
||||
execute(grab_letsencrypt_log)
|
||||
except:
|
||||
print("log fail\n")
|
||||
pass
|
||||
|
||||
|
||||
def cleanup(cl_args, instances, targetlist):
|
||||
print('Logs in ', LOGDIR)
|
||||
if not cl_args.saveinstances:
|
||||
print('Terminating EC2 Instances and Cleaning Dangling EBS Volumes')
|
||||
if cl_args.killboulder:
|
||||
boulder_server.terminate()
|
||||
terminate_and_clean(instances)
|
||||
else:
|
||||
# print login information for the boxes for debugging
|
||||
for ii, target in enumerate(targetlist):
|
||||
print(target['name'],
|
||||
target['ami'],
|
||||
"%s@%s"%(target['user'], instances[ii].public_ip_address))
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# SCRIPT BEGINS
|
||||
#-------------------------------------------------------------------------------
|
||||
@@ -413,124 +462,85 @@ else:
|
||||
#machine_type='t2.medium',
|
||||
security_groups=['letsencrypt_test'])
|
||||
|
||||
if not cl_args.boulderonly:
|
||||
instances = create_client_instances(targetlist)
|
||||
try:
|
||||
if not cl_args.boulderonly:
|
||||
instances = create_client_instances(targetlist)
|
||||
|
||||
# Configure and launch boulder server
|
||||
#-------------------------------------------------------------------------------
|
||||
print("Waiting on Boulder Server")
|
||||
boulder_server = block_until_instance_ready(boulder_server)
|
||||
print(" server %s"%boulder_server)
|
||||
# Configure and launch boulder server
|
||||
#-------------------------------------------------------------------------------
|
||||
print("Waiting on Boulder Server")
|
||||
boulder_server = block_until_instance_ready(boulder_server)
|
||||
print(" server %s"%boulder_server)
|
||||
|
||||
|
||||
# env.host_string defines the ssh user and host for connection
|
||||
env.host_string = "ubuntu@%s"%boulder_server.public_ip_address
|
||||
print("Boulder Server at (SSH):", env.host_string)
|
||||
if not boulder_preexists:
|
||||
print("Configuring and Launching Boulder")
|
||||
config_and_launch_boulder(boulder_server)
|
||||
# blocking often unnecessary, but cheap EC2 VMs can get very slow
|
||||
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
|
||||
wait_time=10, timeout=500)
|
||||
# env.host_string defines the ssh user and host for connection
|
||||
env.host_string = "ubuntu@%s"%boulder_server.public_ip_address
|
||||
print("Boulder Server at (SSH):", env.host_string)
|
||||
if not boulder_preexists:
|
||||
print("Configuring and Launching Boulder")
|
||||
config_and_launch_boulder(boulder_server)
|
||||
# blocking often unnecessary, but cheap EC2 VMs can get very slow
|
||||
block_until_http_ready('http://%s:4000'%boulder_server.public_ip_address,
|
||||
wait_time=10, timeout=500)
|
||||
|
||||
boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address
|
||||
print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address)
|
||||
print("Boulder Server at (EC2 private ip): %s"%boulder_url)
|
||||
boulder_url = "http://%s:4000/directory"%boulder_server.private_ip_address
|
||||
print("Boulder Server at (public ip): http://%s:4000/directory"%boulder_server.public_ip_address)
|
||||
print("Boulder Server at (EC2 private ip): %s"%boulder_url)
|
||||
|
||||
if cl_args.boulderonly:
|
||||
sys.exit(0)
|
||||
if cl_args.boulderonly:
|
||||
sys.exit(0)
|
||||
|
||||
# Install and launch client scripts in parallel
|
||||
#-------------------------------------------------------------------------------
|
||||
print("Uploading and running test script in parallel: %s"%cl_args.test_script)
|
||||
print("Output routed to log files in %s"%LOGDIR)
|
||||
# (Advice: always use Manager.Queue, never regular multiprocessing.Queue
|
||||
# the latter has implementation flaws that deadlock it in some circumstances)
|
||||
manager = Manager()
|
||||
outqueue = manager.Queue()
|
||||
inqueue = manager.Queue()
|
||||
SENTINEL = None #queue kill signal
|
||||
# Install and launch client scripts in parallel
|
||||
#-------------------------------------------------------------------------------
|
||||
print("Uploading and running test script in parallel: %s"%cl_args.test_script)
|
||||
print("Output routed to log files in %s"%LOGDIR)
|
||||
# (Advice: always use Manager.Queue, never regular multiprocessing.Queue
|
||||
# the latter has implementation flaws that deadlock it in some circumstances)
|
||||
manager = Manager()
|
||||
outqueue = manager.Queue()
|
||||
inqueue = manager.Queue()
|
||||
SENTINEL = None #queue kill signal
|
||||
|
||||
# launch as many processes as clients to test
|
||||
num_processes = len(targetlist)
|
||||
jobs = [] #keep a reference to current procs
|
||||
# launch as many processes as clients to test
|
||||
num_processes = len(targetlist)
|
||||
jobs = [] #keep a reference to current procs
|
||||
|
||||
def test_client_process(inqueue, outqueue):
|
||||
cur_proc = mp.current_process()
|
||||
for inreq in iter(inqueue.get, SENTINEL):
|
||||
ii, target = inreq
|
||||
|
||||
#save all stdout to log file
|
||||
sys.stdout = open(LOGDIR+'/'+'%d_%s.log'%(ii,target['name']), 'w')
|
||||
# initiate process execution
|
||||
for i in range(num_processes):
|
||||
p = mp.Process(target=test_client_process, args=(inqueue, outqueue))
|
||||
jobs.append(p)
|
||||
p.daemon = True # kills subprocesses if parent is killed
|
||||
p.start()
|
||||
|
||||
print("[%s : client %d %s %s]" % (cur_proc.name, ii, target['ami'], target['name']))
|
||||
instances[ii] = block_until_instance_ready(instances[ii])
|
||||
print("server %s at %s"%(instances[ii], instances[ii].public_ip_address))
|
||||
env.host_string = "%s@%s"%(target['user'], instances[ii].public_ip_address)
|
||||
print(env.host_string)
|
||||
|
||||
try:
|
||||
install_and_launch_letsencrypt(instances[ii], boulder_url, target)
|
||||
outqueue.put((ii, target, 'pass'))
|
||||
print("%s - %s SUCCESS"%(target['ami'], target['name']))
|
||||
except:
|
||||
outqueue.put((ii, target, 'fail'))
|
||||
print("%s - %s FAIL"%(target['ami'], target['name']))
|
||||
pass
|
||||
|
||||
# append server letsencrypt.log to each per-machine output log
|
||||
print("\n\nletsencrypt.log\n" + "-"*80 + "\n")
|
||||
try:
|
||||
execute(grab_letsencrypt_log)
|
||||
except:
|
||||
print("log fail\n")
|
||||
pass
|
||||
|
||||
# initiate process execution
|
||||
for i in range(num_processes):
|
||||
p = mp.Process(target=test_client_process, args=(inqueue, outqueue))
|
||||
jobs.append(p)
|
||||
p.daemon = True # kills subprocesses if parent is killed
|
||||
p.start()
|
||||
|
||||
# fill up work queue
|
||||
for ii, target in enumerate(targetlist):
|
||||
inqueue.put((ii, target))
|
||||
|
||||
# add SENTINELs to end client processes
|
||||
for i in range(num_processes):
|
||||
inqueue.put(SENTINEL)
|
||||
# wait on termination of client processes
|
||||
for p in jobs:
|
||||
p.join()
|
||||
# add SENTINEL to output queue
|
||||
outqueue.put(SENTINEL)
|
||||
|
||||
# clean up
|
||||
execute(local_repo_clean)
|
||||
|
||||
# print and save summary results
|
||||
results_file = open(LOGDIR+'/results', 'w')
|
||||
outputs = [outq for outq in iter(outqueue.get, SENTINEL)]
|
||||
outputs.sort(key=lambda x: x[0])
|
||||
for outq in outputs:
|
||||
ii, target, status = outq
|
||||
print('%d %s %s'%(ii, target['name'], status))
|
||||
results_file.write('%d %s %s\n'%(ii, target['name'], status))
|
||||
results_file.close()
|
||||
|
||||
if not cl_args.saveinstances:
|
||||
print('Logs in ', LOGDIR)
|
||||
print('Terminating EC2 Instances and Cleaning Dangling EBS Volumes')
|
||||
if cl_args.killboulder:
|
||||
boulder_server.terminate()
|
||||
terminate_and_clean(instances)
|
||||
else:
|
||||
# print login information for the boxes for debugging
|
||||
# fill up work queue
|
||||
for ii, target in enumerate(targetlist):
|
||||
print(target['name'],
|
||||
target['ami'],
|
||||
"%s@%s"%(target['user'], instances[ii].public_ip_address))
|
||||
inqueue.put((ii, target))
|
||||
|
||||
# kill any connections
|
||||
fabric.network.disconnect_all()
|
||||
# add SENTINELs to end client processes
|
||||
for i in range(num_processes):
|
||||
inqueue.put(SENTINEL)
|
||||
# wait on termination of client processes
|
||||
for p in jobs:
|
||||
p.join()
|
||||
# add SENTINEL to output queue
|
||||
outqueue.put(SENTINEL)
|
||||
|
||||
# clean up
|
||||
execute(local_repo_clean)
|
||||
|
||||
# print and save summary results
|
||||
results_file = open(LOGDIR+'/results', 'w')
|
||||
outputs = [outq for outq in iter(outqueue.get, SENTINEL)]
|
||||
outputs.sort(key=lambda x: x[0])
|
||||
for outq in outputs:
|
||||
ii, target, status = outq
|
||||
print('%d %s %s'%(ii, target['name'], status))
|
||||
results_file.write('%d %s %s\n'%(ii, target['name'], status))
|
||||
results_file.close()
|
||||
|
||||
finally:
|
||||
cleanup(cl_args, instances, targetlist)
|
||||
|
||||
# kill any connections
|
||||
fabric.network.disconnect_all()
|
||||
|
||||
@@ -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 --debug --version
|
||||
./letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \
|
||||
--text --agree-dev-preview --agree-tos \
|
||||
--renew-by-default --redirect \
|
||||
--register-unsafely-without-email \
|
||||
|
||||
@@ -11,7 +11,7 @@ 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.8)"; \
|
||||
echo -n '(SayText "'; \
|
||||
sha1sum | cut -c1-64 | 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
|
||||
|
||||
|
||||
@@ -188,10 +188,13 @@ while ! openssl dgst -sha256 -verify $RELEASE_OPENSSL_PUBKEY -signature \
|
||||
read -p "Please correctly sign letsencrypt-auto with offline-signrequest.sh"
|
||||
done
|
||||
|
||||
# copy leauto to the root, overwriting the previous release version
|
||||
cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto
|
||||
|
||||
git add letsencrypt-auto 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,9 +3,9 @@
|
||||
|
||||
export VENV_ARGS="--python python2"
|
||||
|
||||
./bootstrap/dev/_venv_common.sh \
|
||||
-e acme[testing] \
|
||||
-e .[dev,docs,testing] \
|
||||
./tools/_venv_common.sh \
|
||||
-e acme[dev] \
|
||||
-e .[dev,docs] \
|
||||
-e letsencrypt-apache \
|
||||
-e letsencrypt-nginx \
|
||||
-e letshelp-letsencrypt \
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
export VENV_NAME="${VENV_NAME:-venv3}"
|
||||
export VENV_ARGS="--python python3"
|
||||
|
||||
./bootstrap/dev/_venv_common.sh \
|
||||
-e acme[testing] \
|
||||
./tools/_venv_common.sh \
|
||||
-e acme[dev] \
|
||||
|
||||
14
tox.ini
14
tox.ini
@@ -15,9 +15,9 @@ envlist = py{26,27,33,34,35},py{26,27}-oldest,cover,lint
|
||||
# packages installed separately to ensure that downstream deps problems
|
||||
# are detected, c.f. #1002
|
||||
commands =
|
||||
pip install -e acme[testing]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme
|
||||
pip install -e .[testing]
|
||||
pip install -e .[dev]
|
||||
nosetests -v letsencrypt
|
||||
pip install -e letsencrypt-apache
|
||||
nosetests -v letsencrypt_apache
|
||||
@@ -40,23 +40,23 @@ deps =
|
||||
|
||||
[testenv:py33]
|
||||
commands =
|
||||
pip install -e acme[testing]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme
|
||||
|
||||
[testenv:py34]
|
||||
commands =
|
||||
pip install -e acme[testing]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme
|
||||
|
||||
[testenv:py35]
|
||||
commands =
|
||||
pip install -e acme[testing]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme
|
||||
|
||||
[testenv:cover]
|
||||
basepython = python2.7
|
||||
commands =
|
||||
pip install -e acme -e .[testing] -e letsencrypt-apache -e letsencrypt-nginx -e letshelp-letsencrypt
|
||||
pip install -e acme[dev] -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letshelp-letsencrypt
|
||||
./tox.cover.sh
|
||||
|
||||
[testenv:lint]
|
||||
@@ -66,7 +66,7 @@ basepython = python2.7
|
||||
# duplicate code checking; if one of the commands fails, others will
|
||||
# continue, but tox return code will reflect previous error
|
||||
commands =
|
||||
pip install -e acme -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt
|
||||
pip install -e acme[dev] -e .[dev] -e letsencrypt-apache -e letsencrypt-nginx -e letsencrypt-compatibility-test -e letshelp-letsencrypt
|
||||
./pep8.travis.sh
|
||||
pylint --rcfile=.pylintrc letsencrypt
|
||||
pylint --rcfile=acme/.pylintrc acme/acme
|
||||
|
||||
Reference in New Issue
Block a user