Fixing conflicts

This commit is contained in:
Amjad Mashaal 2016-05-25 23:04:27 +02:00
commit a1cb24b3ef
90 changed files with 3450 additions and 924 deletions

View file

@ -4,26 +4,18 @@ cache:
directories:
- $HOME/.cache/pip
services:
- rabbitmq
- mariadb
# apacheconftest
#- apache2
# This makes sure we get a host with docker-compose present.
dist: trusty
# http://docs.travis-ci.com/user/ci-environment/#CI-environment-OS
# gimme has to be kept in sync with Boulder's Go version setting in .travis.yml
before_install:
- 'dpkg -s libaugeas0'
- '[ "xxx$BOULDER_INTEGRATION" = "xxx" ] || eval "$(gimme 1.5.1)"'
# using separate envs with different TOXENVs creates 4x1 Travis build
# matrix, which allows us to clearly distinguish which component under
# test has failed
env:
global:
- GOPATH=/tmp/go
- PATH=$GOPATH/bin:$PATH
- GO15VENDOREXPERIMENT=1 # Fixes problems with vendor directories
- BOULDERPATH=$PWD/boulder/
matrix:
include:
@ -93,7 +85,6 @@ addons:
- boulder
- boulder-mysql
- boulder-rabbitmq
mariadb: "10.0"
apt:
sources:
- augeas
@ -109,13 +100,11 @@ addons:
# For certbot-nginx integration testing
- nginx-light
- openssl
# For Boulder integration testing
- rsyslog
# for apacheconftest
#- apache2
#- libapache2-mod-wsgi
#- libapache2-mod-macro
#- sudo
- apache2
- libapache2-mod-wsgi
- libapache2-mod-macro
- sudo
install: "travis_retry pip install tox coveralls"
script: 'travis_retry tox && ([ "xxx$BOULDER_INTEGRATION" = "xxx" ] || ./tests/travis-integration.sh)'

View file

@ -3,9 +3,9 @@ ChangeLog
Please note:
the change log will only get updated after first release - for now please use the
`commit log <https://github.com/letsencrypt/letsencrypt/commits/master>`_.
`commit log <https://github.com/certbot/certbot/commits/master>`_.
To see the changes in a given release, inspect the github milestone for the
release. For instance:
https://github.com/letsencrypt/letsencrypt/issues?utf8=%E2%9C%93&q=milestone%3A0.3.0
https://github.com/certbot/certbot/issues?utf8=%E2%9C%93&q=milestone%3A0.3.0

View file

@ -15,4 +15,4 @@ to the Sphinx generated docs is provided below.
-->
https://letsencrypt.readthedocs.org/en/latest/contributing.html
https://certbot.eff.org/docs/contributing.html

View file

@ -21,6 +21,7 @@ WORKDIR /opt/certbot
# If <dest> doesn't exist, it is created along with all missing
# directories in its path.
ENV DEBIAN_FRONTEND=noninteractive
COPY letsencrypt-auto-source/letsencrypt-auto /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto
RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \

View file

@ -3,51 +3,71 @@
Disclaimer
==========
The Let's Encrypt Client is **BETA SOFTWARE**. It contains plenty of bugs and
rough edges, and should be tested thoroughly in staging environments before use
on production systems.
Certbot (previously, the Let's Encrypt client) is **BETA SOFTWARE**. It
contains plenty of bugs and rough edges, and should be tested thoroughly in
staging environments before use on production systems.
For more information regarding the status of the project, please see
https://letsencrypt.org. Be sure to checkout the
`Frequently Asked Questions (FAQ) <https://community.letsencrypt.org/t/frequently-asked-questions-faq/26#topic-title>`_.
About the Let's Encrypt Client
About Certbot
==============================
The Let's Encrypt Client is a fully-featured, extensible client for the Let's
Certbot is a fully-featured, extensible client for the Let's
Encrypt CA (or any other CA that speaks the `ACME
<https://github.com/ietf-wg-acme/acme/blob/master/draft-ietf-acme-acme.md>`_
protocol) that can automate the tasks of obtaining certificates and
configuring webservers to use them. This client runs on Unix-based operating
systems.
Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``,
depending on install method. Instructions on the Internet, and some pieces of the
software, may still refer to this older name.
Contributing
------------
If you'd like to contribute to this project please read `Developer Guide
<https://certbot.eff.org/docs/contributing.html>`_.
.. _installation:
Installation
------------
If ``letsencrypt`` is packaged for your Unix OS, you can install it from
there, and run it by typing ``letsencrypt``. Because not all operating
systems have packages yet, we provide a temporary solution via the
``letsencrypt-auto`` wrapper script, which obtains some dependencies
from your OS and puts others in a python virtual environment::
If ``certbot`` (or ``letsencrypt``) is packaged for your Unix OS (visit
certbot.eff.org_ to find out), you can install it
from there, and run it by typing ``certbot`` (or ``letsencrypt``). Because
not all operating systems have packages yet, we provide a temporary solution
via the ``certbot-auto`` wrapper script, which obtains some dependencies from
your OS and puts others in a python virtual environment::
user@webserver:~$ git clone https://github.com/letsencrypt/letsencrypt
user@webserver:~$ cd letsencrypt
user@webserver:~/letsencrypt$ ./letsencrypt-auto --help
user@webserver:~$ wget https://dl.eff.org/certbot-auto
user@webserver:~$ chmod a+x ./certbot-auto
user@webserver:~$ ./certbot-auto --help
Or for full command line help, type::
.. hint:: The certbot-auto download is protected by HTTPS, which is pretty good, but if you'd like to
double check the integrity of the ``certbot-auto`` script, you can use these steps for verification before running it::
./letsencrypt-auto --help all
user@server:~$ wget -N https://dl.eff.org/certbot-auto.asc
user@server:~$ gpg2 --recv-key A2CFB51FA275A7286234E7B24D17C995CD9775F2
user@server:~$ gpg2 --trusted-key 4D17C995CD9775F2 --verify certbot-auto.asc certbot-auto
``letsencrypt-auto`` updates to the latest client release automatically. And
since ``letsencrypt-auto`` is a wrapper to ``letsencrypt``, it accepts exactly
And for full command line help, you can type::
./certbot-auto --help all
``certbot-auto`` updates to the latest client release automatically. And
since ``certbot-auto`` is a wrapper to ``certbot``, it accepts exactly
the same command line flags and arguments. More details about this script and
other installation methods can be found `in the User Guide
<https://letsencrypt.readthedocs.org/en/latest/using.html#installation>`_.
<https://certbot.eff.org/docs/using.html#installation>`_.
How to run the client
---------------------
In many cases, you can just run ``letsencrypt-auto`` or ``letsencrypt``, and the
In many cases, you can just run ``certbot-auto`` or ``certbot``, and the
client will guide you through the process of obtaining and installing certs
interactively.
@ -56,7 +76,7 @@ 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 example.com -d www.example.com -d other.example.net
./certbot-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
@ -65,7 +85,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@example.com -d example.com -d www.example.com -d other.example.net
./certbot-auto certonly --standalone --email admin@example.com -d example.com -d www.example.com -d other.example.net
Understanding the client in more depth
@ -73,21 +93,21 @@ Understanding the client in more depth
To understand what the client is doing in detail, it's important to
understand the way it uses plugins. Please see the `explanation of
plugins <https://letsencrypt.readthedocs.org/en/latest/using.html#plugins>`_ in
plugins <https://certbot.eff.org/docs/using.html#plugins>`_ in
the User Guide.
Links
=====
Documentation: https://letsencrypt.readthedocs.org
Documentation: https://certbot.eff.org/docs
Software project: https://github.com/letsencrypt/letsencrypt
Software project: https://github.com/certbot/certbot
Notes for developers: https://letsencrypt.readthedocs.org/en/latest/contributing.html
Notes for developers: https://certbot.eff.org/docs/contributing.html
Main Website: https://letsencrypt.org/
IRC Channel: #letsencrypt on `Freenode`_
IRC Channel: #letsencrypt on `Freenode`_ or #certbot on `OFTC`_
Community: https://community.letsencrypt.org
@ -103,12 +123,12 @@ email to client-dev+subscribe@letsencrypt.org)
.. |build-status| image:: https://travis-ci.org/letsencrypt/letsencrypt.svg?branch=master
:target: https://travis-ci.org/letsencrypt/letsencrypt
.. |build-status| image:: https://travis-ci.org/certbot/certbot.svg?branch=master
:target: https://travis-ci.org/certbot/certbot
:alt: Travis CI status
.. |coverage| image:: https://coveralls.io/repos/letsencrypt/letsencrypt/badge.svg?branch=master
:target: https://coveralls.io/r/letsencrypt/letsencrypt
.. |coverage| image:: https://coveralls.io/repos/certbot/certbot/badge.svg?branch=master
:target: https://coveralls.io/r/certbot/certbot
:alt: Coverage status
.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/
@ -128,16 +148,15 @@ System Requirements
===================
The Let's Encrypt Client presently only runs on Unix-ish OSes that include
Python 2.6 or 2.7; Python 3.x support will be added after the Public Beta
launch. The client requires root access in order to write to
``/etc/letsencrypt``, ``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to
bind to ports 80 and 443 (if you use the ``standalone`` plugin) and to read and
modify webserver configurations (if you use the ``apache`` or ``nginx``
plugins). If none of these apply to you, it is theoretically possible to run
without root privileges, but for most users who want to avoid running an ACME
client as root, either `letsencrypt-nosudo
<https://github.com/diafygi/letsencrypt-nosudo>`_ or `simp_le
<https://github.com/kuba/simp_le>`_ are more appropriate choices.
Python 2.6 or 2.7; Python 3.x support will hopefully be added in the future. The
client requires root access in order to write to ``/etc/letsencrypt``,
``/var/log/letsencrypt``, ``/var/lib/letsencrypt``; to bind to ports 80 and 443
(if you use the ``standalone`` plugin) and to read and modify webserver
configurations (if you use the ``apache`` or ``nginx`` plugins). If none of
these apply to you, it is theoretically possible to run without root privileges,
but for most users who want to avoid running an ACME client as root, either
`letsencrypt-nosudo <https://github.com/diafygi/letsencrypt-nosudo>`_ or
`simp_le <https://github.com/kuba/simp_le>`_ are more appropriate choices.
The Apache plugin currently requires a Debian-based OS with augeas version
1.0; this includes Ubuntu 12.04+ and Debian 7+.
@ -152,10 +171,10 @@ Current Features
- standalone (runs its own simple webserver to prove you control a domain)
- webroot (adds files to webroot directories in order to prove control of
domains and obtain certs)
- nginx/0.8.48+ (highly experimental, not included in letsencrypt-auto)
- nginx/0.8.48+ (highly experimental, not included in certbot-auto)
* The private key is generated locally on your system.
* Can talk to the Let's Encrypt CA or optionally to other ACME
* Can talk to the Let's Encrypt CA or optionally to other ACME
compliant services.
* Can get domain-validated (DV) certificates.
* Can revoke certificates.
@ -170,4 +189,6 @@ Current Features
.. _Freenode: https://webchat.freenode.net?channels=%23letsencrypt
.. _OFTC: https://webchat.oftc.net?channels=%23certbot
.. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev
.. _certbot.eff.org: https://certbot.eff.org/

View file

@ -512,6 +512,10 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
self.verify_ssl = verify_ssl
self._nonces = set()
self.user_agent = user_agent
self.session = requests.Session()
def __del__(self):
self.session.close()
def _wrap_in_jws(self, obj, nonce):
"""Wrap `JSONDeSerializable` object in JWS.
@ -606,7 +610,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
kwargs['verify'] = self.verify_ssl
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('User-Agent', self.user_agent)
response = requests.request(method, url, *args, **kwargs)
response = self.session.request(method, url, *args, **kwargs)
logging.debug('Received %s. Headers: %s. Content: %r',
response, response.headers, response.content)
return response

View file

@ -484,9 +484,11 @@ class ClientNetworkTest(unittest.TestCase):
def test_check_response_not_ok_jobj_no_error(self):
self.response.ok = False
self.response.json.return_value = {}
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
with mock.patch('acme.client.messages.Error.from_json') as from_json:
from_json.side_effect = jose.DeserializationError
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
def test_check_response_not_ok_jobj_error(self):
self.response.ok = False
@ -528,40 +530,49 @@ class ClientNetworkTest(unittest.TestCase):
self.assertEqual(
self.response, self.net._check_response(self.response))
@mock.patch('acme.client.requests')
def test_send_request(self, mock_requests):
mock_requests.request.return_value = self.response
def test_send_request(self):
self.net.session = mock.MagicMock()
self.net.session.request.return_value = self.response
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._send_request(
'HEAD', 'url', 'foo', bar='baz'))
mock_requests.request.assert_called_once_with(
'HEAD', 'url', 'foo', verify=mock.ANY, bar='baz', headers=mock.ANY)
'HEAD', 'http://example.com/', 'foo', bar='baz'))
self.net.session.request.assert_called_once_with(
'HEAD', 'http://example.com/', 'foo',
headers=mock.ANY, verify=mock.ANY, bar='baz')
@mock.patch('acme.client.requests')
def test_send_request_verify_ssl(self, mock_requests):
def test_send_request_verify_ssl(self):
# pylint: disable=protected-access
for verify in True, False:
mock_requests.request.reset_mock()
mock_requests.request.return_value = self.response
self.net.session = mock.MagicMock()
self.net.session.request.return_value = self.response
self.net.verify_ssl = verify
# pylint: disable=protected-access
self.assertEqual(
self.response, self.net._send_request('GET', 'url'))
mock_requests.request.assert_called_once_with(
'GET', 'url', verify=verify, headers=mock.ANY)
self.response,
self.net._send_request('GET', 'http://example.com/'))
self.net.session.request.assert_called_once_with(
'GET', 'http://example.com/', verify=verify, headers=mock.ANY)
@mock.patch('acme.client.requests')
def test_send_request_user_agent(self, mock_requests):
mock_requests.request.return_value = self.response
def test_send_request_user_agent(self):
self.net.session = mock.MagicMock()
# pylint: disable=protected-access
self.net._send_request('GET', 'url', headers={'bar': 'baz'})
mock_requests.request.assert_called_once_with(
'GET', 'url', verify=mock.ANY,
self.net._send_request('GET', 'http://example.com/',
headers={'bar': 'baz'})
self.net.session.request.assert_called_once_with(
'GET', 'http://example.com/', verify=mock.ANY,
headers={'User-Agent': 'acme-python-test', 'bar': 'baz'})
self.net._send_request('GET', 'url', headers={'User-Agent': 'foo2'})
mock_requests.request.assert_called_with(
'GET', 'url', verify=mock.ANY, headers={'User-Agent': 'foo2'})
self.net._send_request('GET', 'http://example.com/',
headers={'User-Agent': 'foo2'})
self.net.session.request.assert_called_with(
'GET', 'http://example.com/',
verify=mock.ANY, headers={'User-Agent': 'foo2'})
def test_del(self):
sess = mock.MagicMock()
self.net.session = sess
del self.net
sess.close.assert_called_once()
@mock.patch('acme.client.requests')
def test_requests_error_passthrough(self, mock_requests):
@ -614,14 +625,16 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
return self.checked_response
def test_head(self):
self.assertEqual(self.response, self.net.head('url', 'foo', bar='baz'))
self.assertEqual(self.response, self.net.head(
'http://example.com/', 'foo', bar='baz'))
self.send_request.assert_called_once_with(
'HEAD', 'url', 'foo', bar='baz')
'HEAD', 'http://example.com/', 'foo', bar='baz')
def test_get(self):
self.assertEqual(self.checked_response, self.net.get(
'url', content_type=self.content_type, bar='baz'))
self.send_request.assert_called_once_with('GET', 'url', bar='baz')
'http://example.com/', content_type=self.content_type, bar='baz'))
self.send_request.assert_called_once_with(
'GET', 'http://example.com/', bar='baz')
def test_post(self):
# pylint: disable=protected-access

View file

@ -1,4 +1,5 @@
"""Crypto utilities."""
import binascii
import contextlib
import logging
import re
@ -203,7 +204,7 @@ def gen_ss_cert(key, domains, not_before=None,
"""
assert domains, "Must provide one or more hostnames for the cert."
cert = OpenSSL.crypto.X509()
cert.set_serial_number(1337)
cert.set_serial_number(int(binascii.hexlify(OpenSSL.rand.bytes(16)), 16))
cert.set_version(2)
extensions = [

View file

@ -8,6 +8,8 @@ import unittest
import six
from six.moves import socketserver # pylint: disable=import-error
import OpenSSL
from acme import errors
from acme import jose
from acme import test_util
@ -126,5 +128,23 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
self._get_idn_names())
class RandomSnTest(unittest.TestCase):
"""Test for random certificate serial numbers."""
def setUp(self):
self.cert_count = 5
self.serial_num = []
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
def test_sn_collisions(self):
from acme.crypto_util import gen_ss_cert
for _ in range(self.cert_count):
cert = gen_ss_cert(self.key, ['dummy'], force_san=True)
self.serial_num.append(cert.get_serial_number())
self.assertTrue(len(set(self.serial_num)) > 1)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

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

View file

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

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.6.0.dev0'
version = '0.7.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -45,8 +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 = del /([ \t]*|[ \t]*\\\\\r?\n[ \t]*)/ ""
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:_.-]*/
@ -58,8 +58,8 @@ let empty = Util.empty_dos
let indent = Util.indent
(* borrowed from shellvars.aug *)
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'/
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'/
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'|\\\\ /
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'|\\\\ /
let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/
let cdot = /\\\\./

View file

@ -124,13 +124,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.assoc = dict()
# Outstanding challenges
self._chall_out = set()
# Maps enhancements to vhosts we've enabled the enhancement for
self._enhanced_vhosts = defaultdict(set)
# These will be set in the prepare function
self.parser = None
self.version = version
self.vhosts = None
self._enhance_func = {"redirect": self._enable_redirect,
"ensure-http-header": self._set_http_header}
"ensure-http-header": self._set_http_header,
"staple-ocsp": self._enable_ocsp_stapling}
@property
def mod_ssl_conf(self):
@ -593,8 +596,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:type addr: :class:`~certbot_apache.obj.Addr`
"""
loc = parser.get_aug_path(self.parser.loc["name"])
loc = parser.get_aug_path(self.parser.loc["name"])
if addr.get_port() == "443":
path = self.parser.add_dir_to_ifmodssl(
loc, "NameVirtualHost", [str(addr)])
@ -944,7 +947,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
######################################################################
def supported_enhancements(self): # pylint: disable=no-self-use
"""Returns currently supported enhancements."""
return ["redirect", "ensure-http-header"]
return ["redirect", "ensure-http-header", "staple-ocsp"]
def enhance(self, domain, enhancement, options=None):
"""Enhance configuration.
@ -971,6 +974,68 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logger.warn("Failed %s for %s", enhancement, domain)
raise
def _enable_ocsp_stapling(self, ssl_vhost, unused_options):
"""Enables OCSP Stapling
In OCSP, each client (e.g. browser) would have to query the
OCSP Responder to validate that the site certificate was not revoked.
Enabling OCSP Stapling, would allow the web-server to query the OCSP
Responder, and staple its response to the offered certificate during
TLS. i.e. clients would not have to query the OCSP responder.
OCSP Stapling enablement on Apache implicitly depends on
SSLCertificateChainFile being set by other code.
.. note:: This function saves the configuration
:param ssl_vhost: Destination of traffic, an ssl enabled vhost
:type ssl_vhost: :class:`~letsencrypt_apache.obj.VirtualHost`
:param unused_options: Not currently used
:type unused_options: Not Available
:returns: Success, general_vhost (HTTP vhost)
:rtype: (bool, :class:`~letsencrypt_apache.obj.VirtualHost`)
"""
min_apache_ver = (2, 3, 3)
if self.get_version() < min_apache_ver:
raise errors.PluginError(
"Unable to set OCSP directives.\n"
"Apache version is below 2.3.3.")
if "socache_shmcb_module" not in self.parser.modules:
self.enable_mod("socache_shmcb")
# Check if there's an existing SSLUseStapling directive on.
use_stapling_aug_path = self.parser.find_dir("SSLUseStapling",
"on", start=ssl_vhost.path)
if not use_stapling_aug_path:
self.parser.add_dir(ssl_vhost.path, "SSLUseStapling", "on")
ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep)
# Check if there's an existing SSLStaplingCache directive.
stapling_cache_aug_path = self.parser.find_dir('SSLStaplingCache',
None, ssl_vhost_aug_path)
# We'll simply delete the directive, so that we'll have a
# consistent OCSP cache path.
if stapling_cache_aug_path:
self.aug.remove(
re.sub(r"/\w*$", "", stapling_cache_aug_path[0]))
self.parser.add_dir_to_ifmodssl(ssl_vhost_aug_path,
"SSLStaplingCache",
["shmcb:/var/run/apache2/stapling_cache(128000)"])
msg = "OCSP Stapling was enabled on SSL Vhost: %s.\n"%(
ssl_vhost.filep)
self.save_notes += msg
self.save()
logger.info(msg)
def _set_http_header(self, ssl_vhost, header_substring):
"""Enables header that is identified by header_substring on ssl_vhost.
@ -1058,9 +1123,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param unused_options: Not currently used
:type unused_options: Not Available
:returns: Success, general_vhost (HTTP vhost)
:rtype: (bool, :class:`~certbot_apache.obj.VirtualHost`)
:raises .errors.PluginError: If no viable HTTP host can be created or
used for the redirect.
@ -1083,6 +1145,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"redirection")
self._create_redirect_vhost(ssl_vhost)
else:
if general_vh in self._enhanced_vhosts["redirect"]:
logger.debug("Already enabled redirect for this vhost")
return
# Check if Certbot redirection already exists
self._verify_no_certbot_redirect(general_vh)
@ -1118,6 +1184,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
(general_vh.filep, ssl_vhost.filep))
self.save()
self._enhanced_vhosts["redirect"].add(general_vh)
logger.info("Redirecting vhost in %s to ssl vhost in %s",
general_vh.filep, ssl_vhost.filep)
@ -1177,10 +1244,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
"""
rewrite_engine_path = self.parser.find_dir("RewriteEngine", "on",
rewrite_engine_path_list = self.parser.find_dir("RewriteEngine", "on",
start=vhost.path)
if rewrite_engine_path:
return self.parser.get_arg(rewrite_engine_path[0])
if rewrite_engine_path_list:
for re_path in rewrite_engine_path_list:
# A RewriteEngine directive may also be included in per
# directory .htaccess files. We only care about the VirtualHost.
if 'VirtualHost' in re_path:
return self.parser.get_arg(re_path)
return False
def _create_redirect_vhost(self, ssl_vhost):
@ -1202,6 +1273,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Make a new vhost data structure and add it to the lists
new_vhost = self._create_vhost(parser.get_aug_path(redirect_filepath))
self.vhosts.append(new_vhost)
self._enhanced_vhosts["redirect"].add(new_vhost)
# Finally create documentation for the change
self.save_notes += ("Created a port 80 vhost, %s, for redirection to "

View file

@ -0,0 +1,2 @@
RewriteCond %{HTTP:Content-Disposition} \.php [NC]
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /.+/trackback/?\ HTTP/ [NC]

View file

@ -0,0 +1,247 @@
#ATTENTION!
#
#DO NOT MODIFY THIS FILE BECAUSE IT WAS GENERATED AUTOMATICALLY,
#SO ALL YOUR CHANGES WILL BE LOST THE NEXT TIME THE FILE IS GENERATED.
NameVirtualHost 192.168.100.218:80
NameVirtualHost 10.128.178.192:80
NameVirtualHost 192.168.100.218:443
NameVirtualHost 10.128.178.192:443
ServerName "254020-web1.example.com"
ServerAdmin "name@example.com"
DocumentRoot "/tmp"
<IfModule mod_logio.c>
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" plesklog
</IfModule>
<IfModule !mod_logio.c>
LogFormat "%h %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_jk.c>
JkWorkersFile "/etc/httpd/conf/workers.properties"
JkLogFile /var/log/httpd/mod_jk.log
JkLogLevel info
</IfModule>
#Include "/etc/httpd/conf/plesk.conf.d/ip_default/*.conf"
<VirtualHost \
192.168.100.218:80 \
10.128.178.192:80 \
\
>
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 \
192.168.100.218:443 \
\
>
ServerName "default-192_168_100_218"
UseCanonicalName Off
DocumentRoot "/tmp"
ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin"
SSLEngine on
SSLVerifyClient none
#SSLCertificateFile "/usr/local/psa/var/certificates/cert-9MgutN"
#SSLCACertificateFile "/usr/local/psa/var/certificates/cert-s6Wx3P"
<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 \
10.128.178.192:443 \
\
>
ServerName "default-10_128_178_192"
UseCanonicalName Off
DocumentRoot "/tmp"
ScriptAlias /cgi-bin/ "/var/www/vhosts/default/cgi-bin"
SSLEngine on
SSLVerifyClient none
#SSLCertificateFile "/usr/local/psa/var/certificates/certxfb6025"
<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 \
192.168.100.218:80 \
10.128.178.192:80 \
\
>
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 \
192.168.100.218:443 \
10.128.178.192:443 \
\
>
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 "/usr/local/psa/var/certificates/certxfb6025"
<Directory /var/lib/mailman/archives/>
Options FollowSymLinks
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
</IfModule>
<IfModule mod_rpaf.c>
RPAFproxy_ips 192.168.100.218 10.128.178.192
</IfModule>
<IfModule mod_rpaf-2.0.c>
RPAFproxy_ips 192.168.100.218 10.128.178.192
</IfModule>

View file

@ -15,6 +15,7 @@ from certbot import errors
from certbot.tests import acme_util
from certbot_apache import configurator
from certbot_apache import parser
from certbot_apache import obj
from certbot_apache.tests import util
@ -85,7 +86,8 @@ class MultipleVhostsTest(util.ApacheTest):
mock_getutility.notification = mock.MagicMock(return_value=True)
names = self.config.get_all_names()
self.assertEqual(names, set(
["certbot.demo", "encryption-example.demo", "ip-172-30-0-17", "*.blue.purple.com"]))
["certbot.demo", "ocspvhost.com", "encryption-example.demo",
"ip-172-30-0-17", "*.blue.purple.com"]))
@mock.patch("zope.component.getUtility")
@mock.patch("certbot_apache.configurator.socket.gethostbyaddr")
@ -100,14 +102,24 @@ class MultipleVhostsTest(util.ApacheTest):
obj.Addr(("zombo.com",)),
obj.Addr(("192.168.1.2"))]),
True, False)
self.config.vhosts.append(vhost)
names = self.config.get_all_names()
self.assertEqual(len(names), 6)
self.assertEqual(len(names), 7)
self.assertTrue("zombo.com" in names)
self.assertTrue("google.com" in names)
self.assertTrue("certbot.demo" in names)
def test_bad_servername_alias(self):
ssl_vh1 = obj.VirtualHost(
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
True, False)
# pylint: disable=protected-access
self.config._add_servernames(ssl_vh1)
self.assertTrue(
self.config._add_servername_alias("oy_vey", ssl_vh1) is None)
def test_add_servernames_alias(self):
self.config.parser.add_dir(
self.vh_truth[2].path, "ServerAlias", ["*.le.co"])
@ -124,7 +136,7 @@ class MultipleVhostsTest(util.ApacheTest):
"""
vhs = self.config.get_virtual_hosts()
self.assertEqual(len(vhs), 7)
self.assertEqual(len(vhs), 8)
found = 0
for vhost in vhs:
@ -135,7 +147,7 @@ class MultipleVhostsTest(util.ApacheTest):
else:
raise Exception("Missed: %s" % vhost) # pragma: no cover
self.assertEqual(found, 7)
self.assertEqual(found, 8)
# Handle case of non-debian layout get_virtual_hosts
with mock.patch(
@ -143,7 +155,7 @@ class MultipleVhostsTest(util.ApacheTest):
) as mock_conf:
mock_conf.return_value = False
vhs = self.config.get_virtual_hosts()
self.assertEqual(len(vhs), 7)
self.assertEqual(len(vhs), 8)
@mock.patch("certbot_apache.display_ops.select_vhost")
def test_choose_vhost_none_avail(self, mock_select):
@ -224,16 +236,18 @@ class MultipleVhostsTest(util.ApacheTest):
# Assume only the two default vhosts.
self.config.vhosts = [
vh for vh in self.config.vhosts
if vh.name not in ["certbot.demo", "encryption-example.demo"]
if vh.name not in ["certbot.demo",
"encryption-example.demo",
"ocspvhost.com"]
and "*.blue.purple.com" not in vh.aliases
]
self.assertEqual(
self.config._find_best_vhost("example.demo"), self.vh_truth[2])
self.config._find_best_vhost("encryption-example.demo"),
self.vh_truth[2])
def test_non_default_vhosts(self):
# pylint: disable=protected-access
self.assertEqual(len(self.config._non_default_vhosts()), 5)
self.assertEqual(len(self.config._non_default_vhosts()), 6)
def test_is_site_enabled(self):
"""Test if site is enabled.
@ -539,7 +553,7 @@ class MultipleVhostsTest(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), 8)
self.assertEqual(len(self.config.vhosts), 9)
def test_clean_vhost_ssl(self):
# pylint: disable=protected-access
@ -726,16 +740,15 @@ class MultipleVhostsTest(util.ApacheTest):
def test_get_all_certs_keys(self):
c_k = self.config.get_all_certs_keys()
self.assertEqual(len(c_k), 2)
self.assertEqual(len(c_k), 3)
cert, key, path = next(iter(c_k))
self.assertTrue("cert" in cert)
self.assertTrue("key" in key)
self.assertTrue("default-ssl" in path)
self.assertTrue("default-ssl" in path or "ocsp-ssl" in path)
def test_get_all_certs_keys_malformed_conf(self):
self.config.parser.find_dir = mock.Mock(
side_effect=[["path"], [], ["path"], []])
side_effect=[["path"], [], ["path"], [], ["path"], []])
c_k = self.config.get_all_certs_keys()
self.assertFalse(c_k)
@ -756,15 +769,20 @@ class MultipleVhostsTest(util.ApacheTest):
def test_supported_enhancements(self):
self.assertTrue(isinstance(self.config.supported_enhancements(), list))
@mock.patch("certbot_apache.configurator.ApacheConfigurator._get_http_vhost")
@mock.patch("certbot_apache.display_ops.select_vhost")
@mock.patch("certbot.le_util.exe_exists")
def test_enhance_unknown_vhost(self, mock_exe):
def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get):
self.config.parser.modules.add("rewrite_module")
mock_exe.return_value = True
ssl_vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr(("*", "443"))]),
ssl_vh1 = obj.VirtualHost(
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
True, False)
ssl_vh.name = "satoshi.com"
self.config.vhosts.append(ssl_vh)
ssl_vh1.name = "satoshi.com"
self.config.vhosts.append(ssl_vh1)
mock_sel_vhost.return_value = None
mock_get.return_value = None
self.assertRaises(
errors.PluginError,
self.config.enhance, "satoshi.com", "redirect")
@ -774,6 +792,85 @@ class MultipleVhostsTest(util.ApacheTest):
errors.PluginError,
self.config.enhance, "certbot.demo", "unknown_enhancement")
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
def test_ocsp_stapling(self, mock_exe, mock_run_script):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.enhance("certbot.demo", "staple-ocsp")
self.assertTrue("socache_shmcb_module" in self.config.parser.modules)
self.assertTrue(mock_run_script.called)
# Get the ssl vhost for certbot.demo
ssl_vhost = self.config.assoc["certbot.demo"]
ssl_use_stapling_aug_path = self.config.parser.find_dir(
"SSLUseStapling", "on", ssl_vhost.path)
self.assertEqual(len(ssl_use_stapling_aug_path), 1)
ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep)
stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache',
"shmcb:/var/run/apache2/stapling_cache(128000)",
ssl_vhost_aug_path)
self.assertEqual(len(stapling_cache_aug_path), 1)
@mock.patch("certbot.le_util.exe_exists")
def test_ocsp_stapling_twice(self, mock_exe):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
# Checking the case with already enabled ocsp stapling configuration
self.config.enhance("ocspvhost.com", "staple-ocsp")
# Get the ssl vhost for letsencrypt.demo
ssl_vhost = self.config.assoc["ocspvhost.com"]
ssl_use_stapling_aug_path = self.config.parser.find_dir(
"SSLUseStapling", "on", ssl_vhost.path)
self.assertEqual(len(ssl_use_stapling_aug_path), 1)
ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep)
stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache',
"shmcb:/var/run/apache2/stapling_cache(128000)",
ssl_vhost_aug_path)
self.assertEqual(len(stapling_cache_aug_path), 1)
@mock.patch("certbot.le_util.exe_exists")
def test_ocsp_unsupported_apache_version(self, mock_exe):
mock_exe.return_value = True
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
self.assertRaises(errors.PluginError,
self.config.enhance, "certbot.demo", "staple-ocsp")
def test_get_http_vhost_third_filter(self):
ssl_vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr(("*", "443"))]),
True, False)
ssl_vh.name = "satoshi.com"
self.config.vhosts.append(ssl_vh)
# pylint: disable=protected-access
http_vh = self.config._get_http_vhost(ssl_vh)
self.assertTrue(http_vh.ssl == False)
@mock.patch("certbot.le_util.run_script")
@mock.patch("certbot.le_util.exe_exists")
def test_http_header_hsts(self, mock_exe, _):
@ -899,7 +996,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_redirect_with_existing_rewrite(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2))
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
# Create a preexisting rewrite rule
self.config.parser.add_dir(
@ -938,15 +1035,31 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertRaises(
errors.PluginError, self.config._enable_redirect, ssl_vh, "")
def test_redirect_twice(self):
def test_redirect_two_domains_one_vhost(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
self.config.enhance("encryption-example.demo", "redirect")
self.config.enhance("red.blue.purple.com", "redirect")
verify_no_redirect = ("certbot_apache.configurator."
"ApacheConfigurator._verify_no_certbot_redirect")
with mock.patch(verify_no_redirect) as mock_verify:
self.config.enhance("green.blue.purple.com", "redirect")
self.assertFalse(mock_verify.called)
def test_redirect_from_previous_run(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
self.config.enhance("red.blue.purple.com", "redirect")
# Clear state about enabling redirect on this run
# pylint: disable=protected-access
self.config._enhanced_vhosts["redirect"].clear()
self.assertRaises(
errors.PluginEnhancementAlreadyPresent,
self.config.enhance, "encryption-example.demo", "redirect")
self.config.enhance, "green.blue.purple.com", "redirect")
def test_create_own_redirect(self):
self.config.parser.modules.add("rewrite_module")
@ -957,7 +1070,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.config._enable_redirect(self.vh_truth[1], "")
self.assertEqual(len(self.config.vhosts), 8)
self.assertEqual(len(self.config.vhosts), 9)
def test_create_own_redirect_for_old_apache_version(self):
self.config.parser.modules.add("rewrite_module")
@ -968,7 +1081,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.config._enable_redirect(self.vh_truth[1], "")
self.assertEqual(len(self.config.vhosts), 8)
self.assertEqual(len(self.config.vhosts), 9)
def test_sift_line(self):
# pylint: disable=protected-access

View file

@ -0,0 +1,36 @@
<IfModule mod_ssl.c>
SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
<VirtualHost 10.2.3.4:443>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName
# specifies what hostname must appear in the request's Host: header to
# match this virtual host. For the default virtual host (this file) this
# value is not decisive as it is used as a last resort host regardless.
# However, you must set it for any further virtual host explicitly.
ServerName ocspvhost.com
ServerAdmin webmaster@dumpbits.com
DocumentRoot /var/www/html
# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
# error, crit, alert, emerg.
# It is also possible to configure the loglevel for particular
# modules, e.g.
#LogLevel info ssl:warn
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
# For most configuration files from conf-available/, which are
# enabled or disabled at a global level, it is possible to
# include a line for only one particular virtual host. For example the
# following line enables the CGI configuration for this host only
# after it has been globally disabled with "a2disconf".
#Include conf-available/serve-cgi-bin.conf
SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem
SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem
SSLUseStapling on
</VirtualHost>
# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
</IfModule>

View file

@ -0,0 +1 @@
../sites-available/ocsp-ssl.conf

View file

@ -1,2 +1,3 @@
sites-available/certbot.conf, certbot.demo
sites-available/encryption-example.conf, encryption-example.demo
sites-available/ocsp-ssl.conf, ocspvhost.com

View file

@ -4,6 +4,7 @@ import shutil
import mock
from certbot import errors
from certbot.plugins import common_test
from certbot_apache import obj
@ -137,6 +138,16 @@ class TlsSniPerformTest(util.ApacheTest):
set([obj.Addr.fromstring("*:443")]),
self.sni._get_addrs(self.achalls[0]))
def test_get_addrs_no_vhost_found(self):
self.sni.configurator.choose_vhost = mock.Mock(
side_effect=errors.MissingCommandlineFlag(
"Failed to run Apache plugin non-interactively"))
# pylint: disable=protected-access
self.assertEqual(
set([obj.Addr.fromstring("*:443")]),
self.sni._get_addrs(self.achalls[0]))
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -156,8 +156,12 @@ def get_vh_truth(temp_dir, config_name):
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"])
]
"ip-172-30-0-17", aliases=["*.blue.purple.com"]),
obj.VirtualHost(
os.path.join(prefix, "ocsp-ssl.conf"),
os.path.join(aug_pre, "ocsp-ssl.conf/IfModule/VirtualHost"),
set([obj.Addr.fromstring("10.2.3.4:443")]), True, True,
"ocspvhost.com")]
return vh_truth
return None # pragma: no cover

View file

@ -4,6 +4,7 @@ import os
import logging
from certbot.plugins import common
from certbot.errors import PluginError, MissingCommandlineFlag
from certbot_apache import obj
from certbot_apache import parser
@ -116,12 +117,21 @@ class ApacheTlsSni01(common.TLSSNI01):
def _get_addrs(self, achall):
"""Return the Apache addresses needed for TLS-SNI-01."""
vhost = self.configurator.choose_vhost(achall.domain, temp=True)
# TODO: Checkout _default_ rules.
addrs = set()
default_addr = obj.Addr(("*", str(
self.configurator.config.tls_sni_01_port)))
try:
vhost = self.configurator.choose_vhost(achall.domain, temp=True)
except (PluginError, MissingCommandlineFlag):
# We couldn't find the virtualhost for this domain, possibly
# because it's a new vhost that's not configured yet (GH #677),
# or perhaps because there were multiple <VirtualHost> sections
# in the config file (GH #1042). See also GH #2600.
addrs.add(default_addr)
return addrs
for addr in vhost.addrs:
if "_default_" == addr.get_addr():
addrs.add(default_addr)

View file

@ -314,5 +314,5 @@ texinfo_documents = [
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'certbot': ('https://letsencrypt.readthedocs.org/en/latest/', None),
'certbot': ('https://certbot.eff.org/docs/', None),
}

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.6.0.dev0'
version = '0.7.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

1088
certbot-auto Executable file

File diff suppressed because it is too large Load diff

View file

@ -311,7 +311,7 @@ texinfo_documents = [
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'certbot': ('https://letsencrypt.readthedocs.org/en/latest/', None),
'certbot': ('https://certbot.eff.org/docs/', None),
'certbot-apache': (
'https://letsencrypt-apache.readthedocs.org/en/latest/', None),
'certbot-nginx': (

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.6.0.dev0'
version = '0.7.0.dev0'
install_requires = [
'certbot=={0}'.format(version),

View file

@ -17,7 +17,7 @@ class RawNginxParser(object):
right_bracket = Literal("}").suppress()
semicolon = Literal(";").suppress()
space = White().suppress()
key = Word(alphanums + "_/")
key = Word(alphanums + "_/+-.")
# Matches anything that is not a special character AND any chars in single
# or double quotes
value = Regex(r"((\".*\")?(\'.*\')?[^\{\};,]?)+")
@ -30,10 +30,11 @@ class RawNginxParser(object):
assignment = (key + Optional(space + value, default=None) + semicolon)
location_statement = Optional(space + modifier) + Optional(space + location)
if_statement = Literal("if") + space + Regex(r"\(.+\)") + space
map_statement = Literal("map") + space + Regex(r"\S+") + space + Regex(r"\$\S+") + space
block = Forward()
block << Group(
(Group(key + location_statement) ^ Group(if_statement)) +
(Group(key + location_statement) ^ Group(if_statement) ^ Group(map_statement)) +
left_bracket +
Group(ZeroOrMore(Group(comment | assignment) | block)) +
right_bracket)

View file

@ -307,5 +307,5 @@ texinfo_documents = [
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'certbot': ('https://letsencrypt.readthedocs.org/en/latest/', None),
'certbot': ('https://certbot.eff.org/docs/', None),
}

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.6.0.dev0'
version = '0.7.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -1,4 +1,4 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
__version__ = '0.6.0.dev0'
__version__ = '0.7.0.dev0'

View file

@ -6,10 +6,8 @@ import logging
import logging.handlers
import os
import sys
import traceback
import configargparse
import OpenSSL
import six
import certbot
@ -37,8 +35,14 @@ helpful_parser = None
# should only be used for purposes where inability to detect letsencrypt-auto
# fails safely
fragment = os.path.join(".local", "share", "certbot")
cli_command = "letsencrypt-auto" if fragment in sys.argv[0] else "certbot"
LEAUTO = "letsencrypt-auto"
if "CERTBOT_AUTO" in os.environ:
# if we're here, this is probably going to be certbot-auto, unless the
# user saved the script under a different name
LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"])
fragment = os.path.join(".local", "share", "letsencrypt")
cli_command = LEAUTO if fragment in sys.argv[0] else "certbot"
# Argparse's help formatting has a lot of unhelpful peculiarities, so we want
# to replace as much of it as we can...
@ -141,6 +145,22 @@ def usage_strings(plugins):
return USAGE % (apache_doc, nginx_doc), SHORT_USAGE
def possible_deprecation_warning(config):
"A deprecation warning for users with the old, not-self-upgrading letsencrypt-auto."
if cli_command != LEAUTO:
return
if config.no_self_upgrade:
# users setting --no-self-upgrade might be hanging on a clent version like 0.3.0
# or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't
# need warnings
return
if "CERTBOT_AUTO" not in os.environ:
logger.warn("You are running with an old copy of letsencrypt-auto that does "
"not receive updates, and is less reliable than more recent versions. "
"We recommend upgrading to the latest certbot-auto script, or using native "
"OS packages.")
class _Default(object):
"""A class to use as a default to detect if a value is set by a user"""
@ -314,36 +334,41 @@ class HelpfulArgumentParser(object):
# Do any post-parsing homework here
if self.verb == "renew" and not parsed_args.dialog_mode:
parsed_args.noninteractive_mode = True
if parsed_args.staging or parsed_args.dry_run:
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
conflicts = ["--staging"] if parsed_args.staging else []
conflicts += ["--dry-run"] if parsed_args.dry_run else []
raise errors.Error("--server value conflicts with {0}".format(
" and ".join(conflicts)))
parsed_args.server = constants.STAGING_URI
if parsed_args.dry_run:
if self.verb not in ["certonly", "renew"]:
raise errors.Error("--dry-run currently only works with the "
"'certonly' or 'renew' subcommands (%r)" % self.verb)
parsed_args.break_my_certs = parsed_args.staging = True
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
# The user has a prod account, but might not have a staging
# one; we don't want to start trying to perform interactive registration
parsed_args.tos = True
parsed_args.register_unsafely_without_email = True
self.set_test_server(parsed_args)
if parsed_args.csr:
if parsed_args.allow_subset_of_names:
raise errors.Error("--allow-subset-of-names "
"cannot be used with --csr")
self.handle_csr(parsed_args)
hooks.validate_hooks(parsed_args)
return parsed_args
def set_test_server(self, parsed_args):
"""We have --staging/--dry-run; perform sanity check and set config.server"""
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
conflicts = ["--staging"] if parsed_args.staging else []
conflicts += ["--dry-run"] if parsed_args.dry_run else []
raise errors.Error("--server value conflicts with {0}".format(
" and ".join(conflicts)))
parsed_args.server = constants.STAGING_URI
if parsed_args.dry_run:
if self.verb not in ["certonly", "renew"]:
raise errors.Error("--dry-run currently only works with the "
"'certonly' or 'renew' subcommands (%r)" % self.verb)
parsed_args.break_my_certs = parsed_args.staging = True
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
# The user has a prod account, but might not have a staging
# one; we don't want to start trying to perform interactive registration
parsed_args.tos = True
parsed_args.register_unsafely_without_email = True
def handle_csr(self, parsed_args):
"""Process a --csr flag."""
if parsed_args.verb != "certonly":
@ -351,21 +376,11 @@ class HelpfulArgumentParser(object):
"when obtaining a new or replacement "
"via the certonly command. Please try the "
"certonly command instead.")
if parsed_args.allow_subset_of_names:
raise errors.Error("--allow-subset-of-names cannot be used with --csr")
try:
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
typ = OpenSSL.crypto.FILETYPE_ASN1
domains = crypto_util.get_sans_from_csr(csr.data, OpenSSL.crypto.FILETYPE_ASN1)
except OpenSSL.crypto.Error:
try:
e1 = traceback.format_exc()
typ = OpenSSL.crypto.FILETYPE_PEM
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="pem")
domains = crypto_util.get_sans_from_csr(csr.data, typ)
except OpenSSL.crypto.Error:
logger.debug("DER CSR parse error %s", e1)
logger.debug("PEM CSR parse error %s", traceback.format_exc())
raise errors.Error("Failed to parse CSR file: {0}".format(parsed_args.csr[0]))
csrfile, contents = parsed_args.csr[0:2]
typ, csr, domains = crypto_util.import_csr_file(csrfile, contents)
# This is not necessary for webroot to work, however,
# obtain_certificate_from_csr requires parsed_args.domains to be set
@ -688,6 +703,9 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
helpful.add(
"security", "--rsa-key-size", type=int, metavar="N",
default=flag_default("rsa_key_size"), help=config_help("rsa_key_size"))
helpful.add(
"security", "--must-staple", action="store_true",
help=config_help("must_staple"), dest="must_staple", default=False)
helpful.add(
"security", "--redirect", action="store_true",
help="Automatically redirect all HTTP traffic to HTTPS for the newly "
@ -712,9 +730,20 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
" https:// for every http:// resource.", dest="uir", default=None)
helpful.add(
"security", "--no-uir", action="store_false",
help=" Do not automatically set the \"Content-Security-Policy:"
help="Do not automatically set the \"Content-Security-Policy:"
" upgrade-insecure-requests\" header to every HTTP response.",
dest="uir", default=None)
helpful.add(
"security", "--staple-ocsp", action="store_true",
help="Enables OCSP Stapling. A valid OCSP response is stapled to"
" the certificate that the server offers during TLS.",
dest="staple", default=None)
helpful.add(
"security", "--no-staple-ocsp", action="store_false",
help="Do not automatically enable OCSP Stapling.",
dest="staple", default=None)
helpful.add(
"security", "--strict-permissions", action="store_true",
help="Require that all configuration files are owned by the current "
@ -729,7 +758,8 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
" certificate lineage. You can try it with `--dry-run` first. For"
" more fine-grained control, you can renew individual lineages with"
" the `certonly` subcommand. Hooks are available to run commands "
" before and after renewal; see XXX for more information on these.")
" before and after renewal; see"
" https://certbot.eff.org/docs/using.html#renewal for more information on these.")
helpful.add(
"renew", "--pre-hook",
@ -741,7 +771,8 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False):
"renew", "--post-hook",
help="Command to be run in a shell after attempting to obtain/renew "
" certificates. Can be used to deploy renewed certificates, or to restart"
" any servers that were stopped by --pre-hook.")
" any servers that were stopped by --pre-hook. This is only run if"
" an attempt was made to obtain/renew a certificate.")
helpful.add(
"renew", "--renew-hook",
help="Command to be run in a shell once for each successfully renewed certificate."

View file

@ -24,6 +24,7 @@ from certbot import interfaces
from certbot import le_util
from certbot import reverter
from certbot import storage
from certbot import cli
from certbot.display import ops as display_ops
from certbot.display import enhancements
@ -245,8 +246,9 @@ class Client(object):
domains,
self.config.allow_subset_of_names)
domains = [a.body.identifier.value.encode('ascii')
for a in authzr]
auth_domains = set(a.body.identifier.value.encode('ascii')
for a in authzr)
domains = [d for d in domains if d in auth_domains]
# Create CSR from names
key = crypto_util.init_save_key(
@ -316,23 +318,30 @@ class Client(object):
cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
cert_file, act_cert_path = le_util.unique_file(cert_path, 0o644)
cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)
try:
cert_file.write(cert_pem)
finally:
cert_file.close()
logger.info("Server issued certificate; certificate written to %s",
act_cert_path)
abs_cert_path)
cert_chain_abspath = None
fullchain_abspath = None
if chain_cert:
if not chain_cert:
return abs_cert_path, None, None
else:
chain_pem = crypto_util.dump_pyopenssl_chain(chain_cert)
cert_chain_abspath = _save_chain(chain_pem, chain_path)
fullchain_abspath = _save_chain(cert_pem + chain_pem,
fullchain_path)
return os.path.abspath(act_cert_path), cert_chain_abspath, fullchain_abspath
chain_file, abs_chain_path =\
_open_pem_file('chain_path', chain_path)
fullchain_file, abs_fullchain_path =\
_open_pem_file('fullchain_path', fullchain_path)
_save_chain(chain_pem, chain_file)
_save_chain(cert_pem + chain_pem, fullchain_file)
return abs_cert_path, abs_chain_path, abs_fullchain_path
def deploy_certificate(self, domains, privkey_path,
cert_path, chain_path, fullchain_path):
@ -395,7 +404,8 @@ class Client(object):
supported = self.installer.supported_enhancements()
redirect = config.redirect if "redirect" in supported else False
hsts = config.hsts if "ensure-http-header" in supported else False
uir = config.uir if "ensure-http-header" in supported else False
uir = config.uir if "ensure-http-header" in supported else False
staple = config.staple if "staple-ocsp" in supported else False
if redirect is None:
redirect = enhancements.ask("redirect")
@ -409,9 +419,11 @@ class Client(object):
if uir:
self.apply_enhancement(domains, "ensure-http-header",
"Upgrade-Insecure-Requests")
if staple:
self.apply_enhancement(domains, "staple-ocsp")
msg = ("We were unable to restart web server")
if redirect or hsts or uir:
if redirect or hsts or uir or staple:
with error_handler.ErrorHandler(self._rollback_and_restart, msg):
self.installer.restart()
@ -561,24 +573,35 @@ def view_config_changes(config, num=None):
rev.recovery_routine()
rev.view_config_changes(num)
def _open_pem_file(cli_arg_path, pem_path):
"""Open a pem file.
def _save_chain(chain_pem, chain_path):
If cli_arg_path was set by the client, open that.
Otherwise, uniquify the file path.
:param str cli_arg_path: the cli arg name, e.g. cert_path
:param str pem_path: the pem file path to open
:returns: a tuple of file object and its absolute file path
"""
if cli.set_by_cli(cli_arg_path):
return le_util.safe_open(pem_path, chmod=0o644),\
os.path.abspath(pem_path)
else:
uniq = le_util.unique_file(pem_path, 0o644)
return uniq[0], os.path.abspath(uniq[1])
def _save_chain(chain_pem, chain_file):
"""Saves chain_pem at a unique path based on chain_path.
:param str chain_pem: certificate chain in PEM format
:param str chain_path: candidate path for the cert chain
:returns: absolute path to saved cert chain
:rtype: str
:param str chain_file: chain file object
"""
chain_file, act_chain_path = le_util.unique_file(chain_path, 0o644)
try:
chain_file.write(chain_pem)
finally:
chain_file.close()
logger.info("Cert chain written to %s", act_chain_path)
# This expects a valid chain file
return os.path.abspath(act_chain_path)
logger.info("Cert chain written to %s", chain_file.name)

View file

@ -6,6 +6,7 @@
"""
import logging
import os
import traceback
import OpenSSL
import pyrfc3339
@ -75,9 +76,11 @@ def init_save_csr(privkey, names, path, csrname="csr-certbot.pem"):
:rtype: :class:`certbot.le_util.CSR`
"""
csr_pem, csr_der = make_csr(privkey.pem, names)
config = zope.component.getUtility(interfaces.IConfig)
csr_pem, csr_der = make_csr(privkey.pem, names,
must_staple=config.must_staple)
# Save CSR
le_util.make_or_verify_dir(path, 0o755, os.geteuid(),
config.strict_permissions)
@ -92,7 +95,7 @@ def init_save_csr(privkey, names, path, csrname="csr-certbot.pem"):
# Lower level functions
def make_csr(key_str, domains):
def make_csr(key_str, domains, must_staple=False):
"""Generate a CSR.
:param str key_str: PEM-encoded RSA key.
@ -111,13 +114,19 @@ def make_csr(key_str, domains):
req.get_subject().CN = domains[0]
# TODO: what to put into req.get_subject()?
# TODO: put SAN if len(domains) > 1
req.add_extensions([
extensions = [
OpenSSL.crypto.X509Extension(
"subjectAltName",
critical=False,
value=", ".join("DNS:%s" % d for d in domains)
),
])
)
]
if must_staple:
extensions.append(OpenSSL.crypto.X509Extension(
"1.3.6.1.5.5.7.1.24",
critical=False,
value="DER:30:03:02:01:05"))
req.add_extensions(extensions)
req.set_version(2)
req.set_pubkey(pkey)
req.sign(pkey, "sha256")
@ -171,6 +180,30 @@ def csr_matches_pubkey(csr, privkey):
return False
def import_csr_file(csrfile, data):
"""Import a CSR file, which can be either PEM or DER.
:param str csrfile: CSR filename
:param str data: contents of the CSR file
:returns: (`OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`,
le_util.CSR object representing the CSR,
list of domains requested in the CSR)
:rtype: tuple
"""
for form, typ in (("der", OpenSSL.crypto.FILETYPE_ASN1,),
("pem", OpenSSL.crypto.FILETYPE_PEM,),):
try:
domains = get_names_from_csr(data, typ)
except OpenSSL.crypto.Error:
logger.debug("CSR parse error (form=%s, typ=%s):", form, typ)
logger.debug(traceback.format_exc())
continue
return typ, le_util.CSR(file=csrfile, data=data, form=form), domains
raise errors.Error("Failed to parse CSR file: {0}".format(csrfile))
def make_key(bits):
"""Generate PEM encoded RSA key.
@ -220,15 +253,20 @@ def pyopenssl_load_certificate(data):
str(error) for error in openssl_errors)))
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
def _load_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
try:
cert_or_req = load_func(typ, cert_or_req_str)
return load_func(typ, cert_or_req_str)
except OpenSSL.crypto.Error as error:
logger.exception(error)
raise
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
# pylint: disable=protected-access
return acme_crypto_util._pyopenssl_cert_or_req_san(cert_or_req)
return acme_crypto_util._pyopenssl_cert_or_req_san(_load_cert_or_req(
cert_or_req_str, load_func, typ))
def get_sans_from_cert(cert, typ=OpenSSL.crypto.FILETYPE_PEM):
@ -259,6 +297,25 @@ def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
csr, OpenSSL.crypto.load_certificate_request, typ)
def get_names_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
"""Get a list of domains from a CSR, including the CN if it is set.
:param str csr: CSR (encoded).
:param typ: `OpenSSL.crypto.FILETYPE_PEM` or `OpenSSL.crypto.FILETYPE_ASN1`
:returns: A list of domain names.
:rtype: list
"""
loaded_csr = _load_cert_or_req(
csr, OpenSSL.crypto.load_certificate_request, typ)
# Use a set to avoid duplication with CN and Subject Alt Names
domains = set(d for d in (loaded_csr.get_subject().CN,) if d is not None)
# pylint: disable=protected-access
domains.update(acme_crypto_util._pyopenssl_cert_or_req_san(loaded_csr))
return list(domains)
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle.

View file

@ -27,7 +27,7 @@ def _validate_hook(shell_cmd, hook_name):
:raises .errors.HookCommandNotFound: if the command is not found
"""
if shell_cmd:
cmd = shell_cmd.partition(" ")[0]
cmd = shell_cmd.split(None, 1)[0]
if not _prog(cmd):
path = os.environ["PATH"]
msg = "Unable to find {2}-hook command {0} in the PATH.\n(PATH is {1})".format(
@ -39,7 +39,7 @@ def pre_hook(config):
if config.pre_hook and not pre_hook.already:
logger.info("Running pre-hook command: %s", config.pre_hook)
_run_hook(config.pre_hook)
pre_hook.already = True
pre_hook.already = True
pre_hook.already = False
@ -50,6 +50,11 @@ def post_hook(config, final=False):
we're called with final=True before actually doing anything.
"""
if config.post_hook:
if not pre_hook.already:
logger.info("No renewals attempted, so not running post-hook")
if config.verb != "renew":
logger.warn("Sanity failure in renewal hooks")
return
if final or config.verb != "renew":
logger.info("Running post-hook command: %s", config.post_hook)
_run_hook(config.post_hook)

View file

@ -200,6 +200,10 @@ class IConfig(zope.interface.Interface):
email = zope.interface.Attribute(
"Email used for registration and recovery contact.")
rsa_key_size = zope.interface.Attribute("Size of the RSA key.")
must_staple = zope.interface.Attribute(
"Whether to request the OCSP Must Staple certificate extension. "
"Additional setup may be required after issuance. This does not "
"currently autoconfigure web servers for OCSP stapling. ")
config_dir = zope.interface.Attribute("Configuration directory.")
work_dir = zope.interface.Attribute("Working directory.")

View file

@ -1,6 +1,9 @@
"""Utilities for all Certbot."""
import argparse
import collections
# distutils.version under virtualenv confuses pylint
# For more info, see: https://github.com/PyCQA/pylint/issues/73
import distutils.version # pylint: disable=import-error,no-name-in-module
import errno
import logging
import os
@ -151,7 +154,8 @@ def _unique_file(path, filename_pat, count, mode):
while True:
current_path = os.path.join(path, filename_pat(count))
try:
return safe_open(current_path, chmod=mode), current_path
return safe_open(current_path, chmod=mode),\
os.path.abspath(current_path)
except OSError as err:
# "File exists," is okay, try a different name.
if err.errno != errno.EEXIST:
@ -342,3 +346,17 @@ def enforce_domain_sanity(domain):
if not fqdn.match(domain):
raise errors.ConfigurationError("Requested domain {0} is not a FQDN".format(domain))
return domain
def get_strict_version(normalized):
"""Converts a normalized version to a strict version.
:param str normalized: normalized version string
:returns: An equivalent strict version
:rtype: distutils.version.StrictVersion
"""
# strict version ending with "a" and a number designates a pre-release
# pylint: disable=no-member
return distutils.version.StrictVersion(normalized.replace(".dev", "a"))

View file

@ -94,10 +94,10 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
if lineage is False:
raise errors.Error("Certificate could not be obtained")
finally:
hooks.post_hook(config)
hooks.post_hook(config, final=False)
if not config.dry_run and not config.verb == "renew":
_report_new_cert(lineage.cert, lineage.fullchain)
_report_new_cert(config, lineage.cert, lineage.fullchain)
return lineage, action
@ -267,7 +267,7 @@ def _find_domains(config, installer):
return domains
def _report_new_cert(cert_path, fullchain_path):
def _report_new_cert(config, cert_path, fullchain_path):
"""Reports the creation of a new certificate to the user.
:param str cert_path: path to cert
@ -285,12 +285,15 @@ def _report_new_cert(cert_path, fullchain_path):
# Unless we're in .csr mode and there really isn't one
and_chain = "has "
path = cert_path
verbswitch = ' with the "certonly" option' if config.verb == "run" else ""
# XXX Perhaps one day we could detect the presence of known old webservers
# and say something more informative here.
msg = ("Congratulations! Your certificate {0} been saved at {1}."
" Your cert will expire on {2}. To obtain a new version of the "
"certificate in the future, simply run Certbot again."
.format(and_chain, path, expiry))
msg = ('Congratulations! Your certificate {0} been saved at {1}.'
' Your cert will expire on {2}. To obtain a new or tweaked version of this '
'certificate in the future, simply run {3} again{4}. '
'To non-interactively renew *all* of your ceriticates, run "{3} renew"'
.format(and_chain, path, expiry, cli.cli_command, verbswitch))
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
@ -485,7 +488,7 @@ def _csr_obtain_cert(config, le_client):
else:
cert_path, _, cert_fullchain = le_client.save_certificate(
certr, chain, config.cert_path, config.chain_path, config.fullchain_path)
_report_new_cert(cert_path, cert_fullchain)
_report_new_cert(config, cert_path, cert_fullchain)
def obtain_cert(config, plugins, lineage=None):
@ -661,6 +664,7 @@ def main(cli_args=sys.argv[1:]):
le_util.make_or_verify_dir(
config.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args)
setup_logging(config, _cli_log_handler, logfile='letsencrypt.log')
cli.possible_deprecation_warning(config)
logger.debug("certbot version: %s", certbot.__version__)
# do not log `config`, as it contains sensitive data (e.g. revoke --key)!
@ -687,11 +691,6 @@ def main(cli_args=sys.argv[1:]):
displayer = display_util.NoninteractiveDisplay(sys.stdout)
elif config.text_mode:
displayer = display_util.FileDisplay(sys.stdout)
elif config.dialog_mode:
displayer = display_util.NcursesDisplay()
elif config.verb == "renew":
config.noninteractive_mode = True
displayer = display_util.NoninteractiveDisplay(sys.stdout)
else:
displayer = display_util.NcursesDisplay()
zope.component.provideUtility(displayer)

View file

@ -120,6 +120,14 @@ def supported_challenges_validator(data):
"""
challs = data.split(",")
# tls-sni-01 was dvsni during private beta
if "dvsni" in challs:
logger.info("Updating legacy standalone_supported_challenges value")
challs = [challenges.TLSSNI01.typ if chall == "dvsni" else chall
for chall in challs]
data = ",".join(challs)
unrecognized = [name for name in challs
if name not in challenges.Challenge.TYPES]
if unrecognized:

View file

@ -85,6 +85,11 @@ class SupportedChallengesValidatorTest(unittest.TestCase):
def test_not_subset(self):
self.assertRaises(argparse.ArgumentTypeError, self._call, "dns")
def test_dvsni(self):
self.assertEqual("tls-sni-01", self._call("dvsni"))
self.assertEqual("http-01,tls-sni-01", self._call("http-01,dvsni"))
self.assertEqual("tls-sni-01,http-01", self._call("dvsni,http-01"))
class AuthenticatorTest(unittest.TestCase):
"""Tests for certbot.plugins.standalone.Authenticator."""

View file

@ -181,12 +181,8 @@ to serve all files under specified web root ({0})."""
os.chown(self.full_roots[name], stat_path.st_uid,
stat_path.st_gid)
except OSError as exception:
if exception.errno == errno.EACCES:
logger.debug("Insufficient permissions to change owner and uid - ignoring")
else:
raise errors.PluginError(
"Couldn't create root for {0} http-01 "
"challenge responses: {1}", name, exception)
logger.info("Unable to change owner and uid of webroot directory")
logger.debug("Error was: %s", exception)
except OSError as exception:
if exception.errno != errno.EEXIST:
@ -235,14 +231,9 @@ to serve all files under specified web root ({0})."""
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)
elif exc.errno == errno.EACCES:
logger.debug("Challenges cleaned up but no permissions for %s",
root_path)
else:
raise
logger.info(
"Unable to clean up challenge directory %s", root_path)
logger.debug("Error was: %s", exc)
class _WebrootMapAction(argparse.Action):

View file

@ -138,15 +138,10 @@ class AuthenticatorTest(unittest.TestCase):
os.chmod(self.path, 0o700)
@mock.patch("certbot.plugins.webroot.os.chown")
def test_failed_chown_eacces(self, mock_chown):
def test_failed_chown(self, mock_chown):
mock_chown.side_effect = OSError(errno.EACCES, "msg")
self.auth.perform([self.achall]) # exception caught and logged
@mock.patch("certbot.plugins.webroot.os.chown")
def test_failed_chown_not_eacces(self, mock_chown):
mock_chown.side_effect = OSError()
self.assertRaises(errors.PluginError, self.auth.perform, [])
def test_perform_permissions(self):
self.auth.prepare()
@ -200,7 +195,7 @@ class AuthenticatorTest(unittest.TestCase):
os.rmdir(leftover_path)
@mock.patch('os.rmdir')
def test_cleanup_permission_denied(self, mock_rmdir):
def test_cleanup_failure(self, mock_rmdir):
self.auth.prepare()
self.auth.perform([self.achall])
@ -212,19 +207,6 @@ class AuthenticatorTest(unittest.TestCase):
self.assertFalse(os.path.exists(self.validation_path))
self.assertTrue(os.path.exists(self.root_challenge_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.ENOENT
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))
class WebrootActionTest(unittest.TestCase):
"""Tests for webroot argparse actions."""

View file

@ -301,7 +301,10 @@ def _renew_describe_results(config, renew_successes, renew_failures,
def renew_all_lineages(config):
"""Examine each lineage; renew if due and report results"""
if config.domains != []:
# This is trivially False if config.domains is empty
if any(domain not in config.webroot_map for domain in config.domains):
# If more plugins start using cli.add_domains,
# we may want to only log a warning here
raise errors.Error("Currently, the renew verb is only capable of "
"renewing all installed certificates that are due "
"to be renewed; individual domains cannot be "

View file

@ -7,6 +7,7 @@ import shutil
import time
import traceback
import zope.component
from certbot import constants
@ -489,7 +490,7 @@ class Reverter(object):
if not os.path.exists(changes_since_path):
logger.info("Rollback checkpoint is empty (no changes made?)")
with open(self.config.changes_since_path) as f:
with open(changes_since_path, 'w') as f:
f.write("No changes\n")
# Add title to self.config.in_progress_dir CHANGES_SINCE

View file

@ -8,6 +8,7 @@ import configobj
import parsedatetime
import pytz
import certbot
from certbot import constants
from certbot import crypto_util
from certbot import errors
@ -17,6 +18,7 @@ from certbot import le_util
logger = logging.getLogger(__name__)
ALL_FOUR = ("cert", "privkey", "chain", "fullchain")
CURRENT_VERSION = le_util.get_strict_version(certbot.__version__)
def config_with_defaults(config=None):
@ -63,6 +65,7 @@ def write_renewal_config(o_filename, n_filename, target, relevant_data):
"""
config = configobj.ConfigObj(o_filename)
config["version"] = certbot.__version__
for kind in ALL_FOUR:
config[kind] = target[kind]
@ -78,6 +81,10 @@ def write_renewal_config(o_filename, n_filename, target, relevant_data):
if k not in relevant_data:
del config["renewalparams"][k]
if "renew_before_expiry" not in config:
default_interval = constants.RENEWER_DEFAULTS["renew_before_expiry"]
config.initial_comment = ["renew_before_expiry = " + default_interval]
# TODO: add human-readable comments explaining other available
# parameters
logger.debug("Writing new config %s.", n_filename)
@ -255,6 +262,14 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
"renewal config file {0} is missing a required "
"file reference".format(self.configfile))
conf_version = self.configuration.get("version")
if (conf_version is not None and
le_util.get_strict_version(conf_version) > CURRENT_VERSION):
logger.warning(
"Attempting to parse the version %s renewal configuration "
"file found at %s with version %s of Certbot. This might not "
"work.", conf_version, config_filename, certbot.__version__)
self.cert = self.configuration["cert"]
self.privkey = self.configuration["privkey"]
self.chain = self.configuration["chain"]

View file

@ -357,8 +357,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
['-d', '204.11.231.35'])
def test_csr_with_besteffort(self):
args = ["--csr", CSR, "--allow-subset-of-names"]
self.assertRaises(errors.Error, self._call, args)
self.assertRaises(
errors.Error, self._call,
'certonly --csr {0} --allow-subset-of-names'.format(CSR).split())
def test_run_with_csr(self):
# This is an error because you can only use --csr with certonly
@ -369,6 +370,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
return
assert False, "Expected supplying --csr to fail with default verb"
def test_csr_with_no_domains(self):
self.assertRaises(
errors.Error, self._call,
'certonly --csr {0}'.format(
test_util.vector_path('csr-nonames.pem')).split())
def test_csr_with_inconsistent_domains(self):
self.assertRaises(
errors.Error, self._call,
'certonly -d example.org --csr {0}'.format(CSR).split())
def _get_argument_parser(self):
plugins = disco.PluginsRegistry.find_all()
return functools.partial(cli.prepare_and_parse_args, plugins)
@ -720,6 +732,12 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._test_renew_common(renewalparams=renewalparams,
assert_oc_called=True)
def test_renew_with_webroot_map(self):
renewalparams = {'authenticator': 'webroot'}
self._test_renew_common(
renewalparams=renewalparams, assert_oc_called=True,
args=['renew', '--webroot-map', '{"example.com": "/tmp"}'])
def test_renew_reconstitute_error(self):
# pylint: disable=protected-access
with mock.patch('certbot.main.renewal._reconstitute') as mock_reconstitute:

View file

@ -134,57 +134,39 @@ 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("certbot.client.logger")
def test_obtain_certificate_from_csr(self, mock_logger):
self._mock_obtain_certificate()
from certbot 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("certbot.client.le_util.CSR") as mock_CSR:
mock_CSR.return_value = test_csr
mock_parsed_args.domains = self.eg_domains[:]
mock_parser = mock.MagicMock(cli.HelpfulArgumentParser)
cli.HelpfulArgumentParser.handle_csr(mock_parser, mock_parsed_args)
auth_handler = self.client.auth_handler
# Now provoke an inconsistent domains error...
mock_parsed_args.domains.append("hippopotamus.io")
self.assertRaises(errors.ConfigurationError,
cli.HelpfulArgumentParser.handle_csr, mock_parser, mock_parsed_args)
authzr = self.client.auth_handler.get_authorizations(self.eg_domains, False)
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr,
authzr=authzr))
# and that the cert was obtained correctly
self._check_obtain_certificate()
# Test for authzr=None
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr,
authzr=None))
self.client.auth_handler.get_authorizations.assert_called_with(
self.eg_domains)
# Test for no auth_handler
self.client.auth_handler = None
self.assertRaises(
errors.Error,
self.client.obtain_certificate_from_csr,
authzr = auth_handler.get_authorizations(self.eg_domains, False)
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr)
mock_logger.warning.assert_called_once_with(mock.ANY)
test_csr,
authzr=authzr))
# and that the cert was obtained correctly
self._check_obtain_certificate()
# Test for authzr=None
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(
self.eg_domains,
test_csr,
authzr=None))
auth_handler.get_authorizations.assert_called_with(self.eg_domains)
# 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("certbot.client.crypto_util")
def test_obtain_certificate(self, mock_crypto_util):
@ -201,7 +183,8 @@ class ClientTest(unittest.TestCase):
authzr = []
for domain in domains:
# domain ordering should not be affected by authorization order
for domain in reversed(domains):
authzr.append(
mock.MagicMock(
body=mock.MagicMock(
@ -220,7 +203,9 @@ class ClientTest(unittest.TestCase):
mock.sentinel.key, domains, self.config.csr_dir)
self._check_obtain_certificate()
def test_save_certificate(self):
@mock.patch("certbot.cli.helpful_parser")
def test_save_certificate(self, mock_parser):
# pylint: disable=too-many-locals
certs = ["matching_cert.pem", "cert.pem", "cert-san.pem"]
tmp_path = tempfile.mkdtemp()
os.chmod(tmp_path, 0o755) # TODO: really??
@ -231,6 +216,10 @@ class ClientTest(unittest.TestCase):
candidate_cert_path = os.path.join(tmp_path, "certs", "cert.pem")
candidate_chain_path = os.path.join(tmp_path, "chains", "chain.pem")
candidate_fullchain_path = os.path.join(tmp_path, "chains", "fullchain.pem")
mock_parser.verb = "certonly"
mock_parser.args = ["--cert-path", candidate_cert_path,
"--chain-path", candidate_chain_path,
"--fullchain-path", candidate_fullchain_path]
cert_path, chain_path, fullchain_path = self.client.save_certificate(
certr, chain_cert, candidate_cert_path, candidate_chain_path,

View file

@ -10,6 +10,7 @@ import zope.component
from certbot import errors
from certbot import interfaces
from certbot import le_util
from certbot.tests import test_util
@ -95,6 +96,25 @@ class MakeCSRTest(unittest.TestCase):
['example.com', 'www.example.com'], get_sans_from_csr(
csr_der, OpenSSL.crypto.FILETYPE_ASN1))
def test_must_staple(self):
# TODO: Fails for RSA256_KEY
csr_pem, _ = self._call(
RSA512_KEY, ['example.com', 'www.example.com'], must_staple=True)
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# In pyopenssl 0.13 (used with TOXENV=py26-oldest and py27-oldest), csr
# objects don't have a get_extensions() method, so we skip this test if
# the method isn't available.
if hasattr(csr, 'get_extensions'):
# NOTE: Ideally we would filter by the TLS Feature OID, but
# OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID,
# and the shortname field is just "UNDEF"
must_staple_exts = [e for e in csr.get_extensions()
if e.get_data() == "0\x03\x02\x01\x05"]
self.assertEqual(len(must_staple_exts), 1,
"Expected exactly one Must Staple extension")
class ValidCSRTest(unittest.TestCase):
"""Tests for certbot.crypto_util.valid_csr."""
@ -140,6 +160,44 @@ class CSRMatchesPubkeyTest(unittest.TestCase):
test_util.load_vector('csr.pem'), RSA256_KEY))
class ImportCSRFileTest(unittest.TestCase):
"""Tests for certbot.certbot_util.import_csr_file."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.crypto_util import import_csr_file
return import_csr_file(*args, **kwargs)
def test_der_csr(self):
csrfile = test_util.vector_path('csr.der')
data = test_util.load_vector('csr.der')
self.assertEqual(
(OpenSSL.crypto.FILETYPE_ASN1,
le_util.CSR(file=csrfile,
data=data,
form="der"),
["example.com"],),
self._call(csrfile, data))
def test_pem_csr(self):
csrfile = test_util.vector_path('csr.pem')
data = test_util.load_vector('csr.pem')
self.assertEqual(
(OpenSSL.crypto.FILETYPE_PEM,
le_util.CSR(file=csrfile,
data=data,
form="pem"),
["example.com"],),
self._call(csrfile, data))
def test_bad_csr(self):
self.assertRaises(errors.Error, self._call,
test_util.vector_path('cert.pem'),
test_util.load_vector('cert.pem'))
class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods
"""Tests for certbot.crypto_util.make_key."""
@ -215,6 +273,36 @@ class GetSANsFromCSRTest(unittest.TestCase):
[], self._call(test_util.load_vector('csr-nosans.pem')))
class GetNamesFromCSRTest(unittest.TestCase):
"""Tests for certbot.crypto_util.get_names_from_csr."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.crypto_util import get_names_from_csr
return get_names_from_csr(*args, **kwargs)
def test_extract_one_san(self):
self.assertEqual(['example.com'], self._call(
test_util.load_vector('csr.pem')))
def test_extract_two_sans(self):
self.assertEqual(set(('example.com', 'www.example.com',)), set(
self._call(test_util.load_vector('csr-san.pem'))))
def test_extract_six_sans(self):
self.assertEqual(
set(self._call(test_util.load_vector('csr-6sans.pem'))),
set(("example.com", "example.org", "example.net",
"example.info", "subdomain.example.com",
"other.subdomain.example.com",)))
def test_parse_non_csr(self):
self.assertRaises(OpenSSL.crypto.Error, self._call, "hello there")
def test_parse_no_sans(self):
self.assertEqual(["example.org"],
self._call(test_util.load_vector('csr-nosans.pem')))
class CertLoaderTest(unittest.TestCase):
"""Tests for certbot.crypto_util.pyopenssl_load_certificate"""

View file

@ -56,14 +56,22 @@ class HookTest(unittest.TestCase):
return mock_logger.warning
def test_pre_hook(self):
hooks.pre_hook.already = False
config = mock.MagicMock(pre_hook="true")
self._test_a_hook(config, hooks.pre_hook, 1)
config = mock.MagicMock(pre_hook="")
self._test_a_hook(config, hooks.pre_hook, 0)
def test_post_hook(self):
hooks.pre_hook.already = False
# if pre-hook isn't called, post-hook shouldn't be
config = mock.MagicMock(post_hook="true", verb="splonk")
self._test_a_hook(config, hooks.post_hook, 0)
config = mock.MagicMock(post_hook="true", verb="splonk")
self._test_a_hook(config, hooks.pre_hook, 1)
self._test_a_hook(config, hooks.post_hook, 2)
config = mock.MagicMock(post_hook="true", verb="renew")
self._test_a_hook(config, hooks.post_hook, 0)

View file

@ -10,6 +10,7 @@ import unittest
import mock
import six
import certbot
from certbot import errors
@ -339,5 +340,32 @@ class EnforceDomainSanityTest(unittest.TestCase):
u"eichh\u00f6rnchen.example.com")
class GetStrictVersionTest(unittest.TestCase):
"""Tests for certbot.le_util.get_strict_version."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.le_util import get_strict_version
return get_strict_version(*args, **kwargs)
def test_two_dev_versions(self):
self.assertTrue(
self._call("0.0.0.dev20151006") < self._call("0.0.0.dev20151008"))
def test_one_dev_one_release_version(self):
self.assertTrue(self._call("1.0.0.dev0") < self._call("1.0.0"))
self.assertTrue(self._call("1.0.0") < self._call("1.0.1.dev0"))
def test_two_release_versions(self):
self.assertTrue(self._call("0.0.0") < self._call("0.0.1"))
self.assertTrue(self._call("0.0.0") < self._call("0.1.0"))
self.assertTrue(self._call("0.0.0") < self._call("1.0.0"))
def test_current_version(self):
current_version = self._call(certbot.__version__)
self.assertTrue(self._call("0.6.0") < current_version)
self.assertTrue(current_version < self._call("99.99.99"))
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -34,6 +34,20 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
logging.disable(logging.NOTSET)
@mock.patch("certbot.reverter.Reverter._read_and_append")
def test_no_change(self, mock_read):
mock_read.side_effect = OSError("cannot even")
try:
self.reverter.add_to_checkpoint(self.sets[0], "save1")
except OSError:
pass
self.reverter.finalize_checkpoint("blah")
path = os.listdir(self.reverter.config.backup_dir)[0]
no_change = os.path.join(self.reverter.config.backup_dir, path, "CHANGES_SINCE")
with open(no_change, "r") as f:
x = f.read()
self.assertTrue("No changes" in x)
def test_basic_add_to_temp_checkpoint(self):
# These shouldn't conflict even though they are both named config.txt
self.reverter.add_to_temp_checkpoint(self.sets[0], "save1")

View file

@ -10,6 +10,7 @@ import configobj
import mock
import pytz
import certbot
from certbot import configuration
from certbot import errors
from certbot.storage import ALL_FOUR
@ -137,6 +138,28 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertRaises(errors.CertStorageError, storage.RenewableCert,
config.filename, self.cli_config)
def test_no_renewal_version(self):
from certbot import storage
self._write_out_ex_kinds()
self.assertTrue("version" not in self.config)
with mock.patch("certbot.storage.logger") as mock_logger:
storage.RenewableCert(self.config.filename, self.cli_config)
self.assertFalse(mock_logger.warning.called)
def test_renewal_newer_version(self):
from certbot import storage
self._write_out_ex_kinds()
self.config["version"] = "99.99.99"
self.config.write()
with mock.patch("certbot.storage.logger") as mock_logger:
storage.RenewableCert(self.config.filename, self.cli_config)
self.assertTrue(mock_logger.warning.called)
self.assertTrue("version" in mock_logger.warning.call_args[0][0])
def test_consistent(self):
# pylint: disable=too-many-statements,protected-access
oldcert = self.test_rc.cert
@ -760,11 +783,14 @@ class RenewableCertTests(BaseRenewableCertTest):
with open(temp2, "r") as f:
content = f.read()
# useful value was updated
assert "useful = new_value" in content
self.assertTrue("useful = new_value" in content)
# associated comment was preserved
assert "A useful value" in content
self.assertTrue("A useful value" in content)
# useless value was deleted
assert "useless" not in content
self.assertTrue("useless" not in content)
# check version was stored
self.assertTrue("version = {0}".format(certbot.__version__) in content)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -0,0 +1,8 @@
-----BEGIN CERTIFICATE REQUEST-----
MIH/MIGqAgEAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwXDANBgkqhkiG9w0BAQEF
AANLADBIAkEArHVztFHtH92ucFJD/N/HW9AsdRsUuHUBBBDlHwNlRd3fp580rv2+
6QWE30cWgdmJS86ObRz6lUTor4R0T+3C5QIDAQABoAAwDQYJKoZIhvcNAQELBQAD
QQBt9XLSZ9DGfWcGGaBUTCiSY7lWBegpNlCeo8pK3ydWmKpjcza+j7lF5paph2LH
lKWVQ8+xwYMscGWK0NApHGco
-----END CERTIFICATE REQUEST-----

View file

@ -17,15 +17,13 @@ Autoupdates
Within certain limits, TLS server software can choose what kind of
cryptography to use when a client connects. These choices can affect
security, compatibility, and performance in complex ways. Most of
these options are independent of a particular certificate. The Let's
Encrypt client tries to provide defaults that we think are most useful
to our users.
these options are independent of a particular certificate. Certbot
tries to provide defaults that we think are most useful to our users.
As described below, the Let's Encrypt client will default to modifying
As described below, Certbot will default to modifying
server software's cryptographic settings to keep these up-to-date with
what we think are appropriate defaults when new versions of the Let's
Encrypt client are installed (for example, by an operating system package
manager).
what we think are appropriate defaults when new versions of the Certbot
are installed (for example, by an operating system package manager).
When this feature is implemented, this document will be updated
to describe how to disable these automatic changes.
@ -54,7 +52,7 @@ improve, others' security. But important information that improves our
understanding of the state of the art is published regularly.
When enabling TLS support in a compatible web server (which is a separate
step from obtaining a certificate), Let's Encrypt has the ability to
step from obtaining a certificate), Certbot has the ability to
update that web server's TLS configuration. Again, this is *different
from the cryptographic particulars of the certificate itself*; the
certificate as of the initial release will be RSA-signed using one of
@ -80,30 +78,29 @@ art. However, the Let's Encrypt certificate authority does *not*
dictate end-users' security policy, and any site is welcome to change
its preferences in accordance with its own policy or its administrators'
preferences, and use different cryptographic mechanisms or parameters,
or a different priority order, than the defaults provided by the Let's
Encrypt client.
or a different priority order, than the defaults provided by Certbot.
If you don't use the Let's Encrypt client to configure your server
directly, because the client doesn't integrate with your server software
or because you chose not to use this integration, then the cryptographic
defaults haven't been modified, and the cryptography chosen by the server
will still be whatever the default for your software was. For example,
if you obtain a certificate using *standalone* mode and then manually
install it in an IMAP or LDAP server, your cryptographic settings will
not be modified by the client in any way.
If you don't use Certbot to configure your server directly, because the
client doesn't integrate with your server software or because you chose
not to use this integration, then the cryptographic defaults haven't been
modified, and the cryptography chosen by the server will still be whatever
the default for your software was. For example, if you obtain a
certificate using *standalone* mode and then manually install it in an IMAP
or LDAP server, your cryptographic settings will not be modified by the
client in any way.
Sources of defaults
-------------------
Initially, the Let's Encrypt client will configure users' servers to
use the cryptographic defaults recommended by the Mozilla project.
These settings are well-reasoned recommendations that carefully
consider client software compatibility. They are described at
Initially, Certbot will configure users' servers to use the cryptographic
defaults recommended by the Mozilla project. These settings are well-reasoned
recommendations that carefully consider client software compatibility. They
are described at
https://wiki.mozilla.org/Security/Server_Side_TLS
and the version implemented by the Let's Encrypt client will be the
and the version implemented by Certbot will be the
version that was most current as of the release date of each client
version. Mozilla offers three separate sets of cryptographic options,
which trade off security and compatibility differently. These are
@ -113,12 +110,12 @@ to most-backwards compatible). The client will follow the Mozilla defaults
for the *Intermediate* configuration by default, at least with regards to
ciphersuites and TLS versions. Mozilla's web site describes which client
software will be compatible with each configuration. You can also use
the Qualys SSL Labs site, which the Let's Encrypt software will suggest
the Qualys SSL Labs site, which Certbot will suggest
when installing a certificate, to test your server and see whether it
will be compatible with particular software versions.
It will be possible to ask the Let's Encrypt client to instead apply
(and track) Modern or Old configurations.
It will be possible to ask Certbot to instead apply (and track) Modern
or Old configurations.
The Let's Encrypt project expects to follow the Mozilla recommendations
in the future as those recommendations are updated. (For example, some
@ -127,15 +124,15 @@ which uses the ChaCha and Poly1305 algorithms, and which is already
implemented by the Chrome browser. Mozilla has delayed recommending
``0xcc13`` over compatibility and standardization concerns, but is likely
to recommend it in the future once these concerns have been addressed. At
that point, the Let's Encrypt client would likely follow the Mozilla
recommendations and favor the use of this ciphersuite as well.)
that point, Certbot would likely follow the Mozilla recommendations and favor
the use of this ciphersuite as well.)
The Let's Encrypt project may deviate from the Mozilla recommendations
in the future if good cause is shown and we believe our users'
priorities would be well-served by doing so. In general, please address
relevant proposals for changing priorities to the Mozilla security
team first, before asking the Let's Encrypt project to change the
client's priorities. The Mozilla security team is likely to have more
team first, before asking the Certbot developers to change
Certbot'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.
@ -144,8 +141,8 @@ 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
an existing configuration. For example, if many sysadmins want their
servers configured to track a different expert recommendation, Let's
Encrypt could add an option to do so.
servers configured to track a different expert recommendation, Certbot
could add an option to do so.
Resources for recommendations
@ -156,9 +153,9 @@ recommendations with sources of expert guidance on ciphersuites and other
cryptographic parameters. We're grateful to everyone who contributed
suggestions. The recommendations we received are available at
https://github.com/letsencrypt/letsencrypt/wiki/Ciphersuite-guidance
https://github.com/certbot/certbot/wiki/Ciphersuite-guidance
Let's Encrypt client users are welcome to review these authorities to
Certbot users are welcome to review these authorities to
better inform their own cryptographic parameter choices. We also
welcome suggestions of other resources to add to this list. Please keep
in mind that different recommendations may reflect different priorities
@ -172,26 +169,25 @@ This will probably look something like
.. code-block:: shell
letsencrypt --cipher-recommendations mozilla-secure
letsencrypt --cipher-recommendations mozilla-intermediate
letsencrypt --cipher-recommendations mozilla-old
certbot --cipher-recommendations mozilla-secure
certbot --cipher-recommendations mozilla-intermediate
certbot --cipher-recommendations mozilla-old
to track Mozilla's *Secure*, *Intermediate*, or *Old* recommendations,
and
.. code-block:: shell
letsencrypt --update-ciphers on
certbot --update-ciphers on
to enable updating ciphers with each new Let's Encrypt client release,
or
to enable updating ciphers with each new Certbot release, or
.. code-block:: shell
letsencrypt --update-ciphers off
certbot --update-ciphers off
to disable automatic configuration updates. These features have not yet
been implemented and this syntax may change then they are implemented.
been implemented and this syntax may change when they are implemented.
TODO
@ -200,7 +196,7 @@ TODO
The status of this feature is tracked as part of issue #1123 in our
bug tracker.
https://github.com/letsencrypt/letsencrypt/issues/1123
https://github.com/certbot/certbot/issues/1123
Prior to implementation of #1123, the client does not actually modify
ciphersuites (this is intended to be implemented as a "configuration

340
docs/cli-help.txt Normal file
View file

@ -0,0 +1,340 @@
usage:
certbot [SUBCOMMAND] [options] [-d domain] [-d domain] ...
Certbot can obtain and install HTTPS/TLS/SSL certificates. By default,
it will attempt to use a webserver both for obtaining and installing the
cert. Major SUBCOMMANDS are:
(default) run Obtain & install a cert in your current webserver
certonly Obtain cert, but do not install it (aka "auth")
install Install a previously obtained cert in a server
renew Renew previously obtained certs that are near expiry
revoke Revoke a previously obtained certificate
rollback Rollback server configuration changes made during install
config_changes Show changes made to server config during installation
plugins Display information about installed plugins
optional arguments:
-h, --help show this help message and exit
-c CONFIG_FILE, --config CONFIG_FILE
config file path (default: None)
-v, --verbose This flag can be used multiple times to incrementally
increase the verbosity of output, e.g. -vvv. (default:
-3)
-t, --text Use the text output instead of the curses UI.
(default: False)
-n, --non-interactive, --noninteractive
Run without ever asking for user input. This may
require additional command line flags; the client will
try to explain which ones are required if it finds one
missing (default: False)
--dry-run Perform a test run of the client, obtaining test
(invalid) certs but not saving them to disk. This can
currently only be used with the 'certonly' and 'renew'
subcommands. Note: Although --dry-run tries to avoid
making any persistent changes on a system, it is not
completely side-effect free: if used with webserver
authenticator plugins like apache and nginx, it makes
and then reverts temporary config changes in order to
obtain test certs, and reloads webservers to deploy
and then roll back those changes. It also calls --pre-
hook and --post-hook commands if they are defined
because they may be necessary to accurately simulate
renewal. --renew-hook commands are not called.
(default: False)
--register-unsafely-without-email
Specifying this flag enables registering an account
with no email address. This is strongly discouraged,
because in the event of key loss or account compromise
you will irrevocably lose access to your account. You
will also be unable to receive notice about impending
expiration or revocation of your certificates. Updates
to the Subscriber Agreement will still affect you, and
will be effective 14 days after posting an update to
the web site. (default: False)
-m EMAIL, --email EMAIL
Email used for registration and recovery contact.
(default: None)
-d DOMAIN, --domains DOMAIN, --domain DOMAIN
Domain names to apply. For multiple domains you can
use multiple -d flags or enter a comma separated list
of domains as a parameter. (default: [])
--user-agent USER_AGENT
Set a custom user agent string for the client. User
agent strings allow the CA to collect high level
statistics about success rates by OS and plugin. If
you wish to hide your server OS version from the Let's
Encrypt server, set this to "". (default: None)
automation:
Arguments for automating execution & other tweaks
--keep-until-expiring, --keep, --reinstall
If the requested cert matches an existing cert, always
keep the existing one until it is due for renewal (for
the 'run' subcommand this means reinstall the existing
cert) (default: False)
--expand If an existing cert covers some subset of the
requested names, always expand and replace it with the
additional names. (default: False)
--version show program's version number and exit
--force-renewal, --renew-by-default
If a certificate already exists for the requested
domains, renew it now, regardless of whether it is
near expiry. (Often --keep-until-expiring is more
appropriate). Also implies --expand. (default: False)
--allow-subset-of-names
When performing domain validation, do not consider it
a failure if authorizations can not be obtained for a
strict subset of the requested domains. This may be
useful for allowing renewals for multiple domains to
succeed even if some domains no longer point at this
system. This option cannot be used with --csr.
(default: False)
--agree-tos Agree to the ACME Subscriber Agreement (default:
False)
--account ACCOUNT_ID Account ID to use (default: None)
--duplicate Allow making a certificate lineage that duplicates an
existing one (both can be renewed in parallel)
(default: False)
--os-packages-only (letsencrypt-auto only) install OS package
dependencies and then stop (default: False)
--no-self-upgrade (letsencrypt-auto only) prevent the letsencrypt-auto
script from upgrading itself to newer released
versions (default: False)
-q, --quiet Silence all output except errors. Useful for
automation via cron. Implies --non-interactive.
(default: False)
testing:
The following flags are meant for testing purposes only! Do NOT change
them, unless you really know what you're doing!
--debug Show tracebacks in case of errors, and allow
letsencrypt-auto execution on experimental platforms
(default: False)
--no-verify-ssl Disable SSL certificate verification. (default: False)
--tls-sni-01-port TLS_SNI_01_PORT
Port number to perform tls-sni-01 challenge. Boulder
in testing mode defaults to 5001. (default: 443)
--http-01-port HTTP01_PORT
Port used in the SimpleHttp challenge. (default: 80)
--break-my-certs Be willing to replace or renew valid certs with
invalid (testing/staging) certs (default: False)
--test-cert, --staging
Use the staging server to obtain test (invalid) certs;
equivalent to --server https://acme-
staging.api.letsencrypt.org/directory (default: False)
security:
Security parameters & server settings
--rsa-key-size N Size of the RSA key. (default: 2048)
--redirect Automatically redirect all HTTP traffic to HTTPS for
the newly authenticated vhost. (default: None)
--no-redirect Do not automatically redirect all HTTP traffic to
HTTPS for the newly authenticated vhost. (default:
None)
--hsts Add the Strict-Transport-Security header to every HTTP
response. Forcing browser to use always use SSL for
the domain. Defends against SSL Stripping. (default:
False)
--no-hsts Do not automatically add the Strict-Transport-Security
header to every HTTP response. (default: False)
--uir Add the "Content-Security-Policy: upgrade-insecure-
requests" header to every HTTP response. Forcing the
browser to use https:// for every http:// resource.
(default: None)
--no-uir Do not automatically set the "Content-Security-Policy:
upgrade-insecure-requests" header to every HTTP
response. (default: None)
--strict-permissions Require that all configuration files are owned by the
current user; only needed if your config is somewhere
unsafe like /tmp/ (default: False)
renew:
The 'renew' subcommand will attempt to renew all certificates (or more
precisely, certificate lineages) you have previously obtained if they are
close to expiry, and print a summary of the results. By default, 'renew'
will reuse the options used to create obtain or most recently successfully
renew each certificate lineage. You can try it with `--dry-run` first. For
more fine-grained control, you can renew individual lineages with the
`certonly` subcommand. Hooks are available to run commands before and
after renewal; see https://certbot.eff.org/docs/using.html#renewal for
more information on these.
--pre-hook PRE_HOOK Command to be run in a shell before obtaining any
certificates. Intended primarily for renewal, where it
can be used to temporarily shut down a webserver that
might conflict with the standalone plugin. This will
only be called if a certificate is actually to be
obtained/renewed. (default: None)
--post-hook POST_HOOK
Command to be run in a shell after attempting to
obtain/renew certificates. Can be used to deploy
renewed certificates, or to restart any servers that
were stopped by --pre-hook. (default: None)
--renew-hook RENEW_HOOK
Command to be run in a shell once for each
successfully renewed certificate.For this command, the
shell variable $RENEWED_LINEAGE will point to
theconfig live subdirectory containing the new certs
and keys; the shell variable $RENEWED_DOMAINS will
contain a space-delimited list of renewed cert domains
(default: None)
certonly:
Options for modifying how a cert is obtained
--csr CSR Path to a Certificate Signing Request (CSR) in DER
format; note that the .csr file *must* contain a
Subject Alternative Name field for each domain you
want certified. Currently --csr only works with the
'certonly' subcommand' (default: None)
install:
Options for modifying how a cert is deployed
revoke:
Options for revocation of certs
rollback:
Options for reverting config changes
--checkpoints N Revert configuration N number of checkpoints.
(default: 1)
plugins:
Plugin options
--init Initialize plugins. (default: False)
--prepare Initialize and prepare plugins. (default: False)
--authenticators Limit to authenticator plugins only. (default: None)
--installers Limit to installer plugins only. (default: None)
config_changes:
Options for showing a history of config changes
--num NUM How many past revisions you want to be displayed
(default: None)
paths:
Arguments changing execution paths & servers
--cert-path CERT_PATH
Path to where cert is saved (with auth --csr),
installed from or revoked. (default: None)
--key-path KEY_PATH Path to private key for cert installation or
revocation (if account key is missing) (default: None)
--fullchain-path FULLCHAIN_PATH
Accompanying path to a full certificate chain (cert
plus chain). (default: None)
--chain-path CHAIN_PATH
Accompanying path to a certificate chain. (default:
None)
--config-dir CONFIG_DIR
Configuration directory. (default: /etc/letsencrypt)
--work-dir WORK_DIR Working directory. (default: /var/lib/letsencrypt)
--logs-dir LOGS_DIR Logs directory. (default: /var/log/letsencrypt)
--server SERVER ACME Directory Resource URI. (default:
https://acme-v01.api.letsencrypt.org/directory)
plugins:
Certbot client supports an extensible plugins architecture. See 'certbot
plugins' for a list of all installed plugins and their names. You can
force a particular plugin by setting options provided below. Running
--help <plugin_name> will list flags specific to that plugin.
-a AUTHENTICATOR, --authenticator AUTHENTICATOR
Authenticator plugin name. (default: None)
-i INSTALLER, --installer INSTALLER
Installer plugin name (also used to find domains).
(default: None)
--configurator CONFIGURATOR
Name of the plugin that is both an authenticator and
an installer. Should not be used together with
--authenticator or --installer. (default: None)
--apache Obtain and install certs using Apache (default: False)
--nginx Obtain and install certs using Nginx (default: False)
--standalone Obtain certs using a "standalone" webserver. (default:
False)
--manual Provide laborious manual instructions for obtaining a
cert (default: False)
--webroot Obtain certs by placing files in a webroot directory.
(default: False)
nginx:
Nginx Web Server - currently doesn't work
--nginx-server-root NGINX_SERVER_ROOT
Nginx server root directory. (default: /etc/nginx)
--nginx-ctl NGINX_CTL
Path to the 'nginx' binary, used for 'configtest' and
retrieving nginx version number. (default: nginx)
standalone:
Automatically use a temporary webserver
--standalone-supported-challenges STANDALONE_SUPPORTED_CHALLENGES
Supported challenges. Preferred in the order they are
listed. (default: tls-sni-01,http-01)
manual:
Manually configure an HTTP server
--manual-test-mode Test mode. Executes the manual command in subprocess.
(default: False)
--manual-public-ip-logging-ok
Automatically allows public IP logging. (default:
False)
webroot:
Place files in webroot directory
--webroot-path WEBROOT_PATH, -w WEBROOT_PATH
public_html / webroot path. This can be specified
multiple times to handle different domains; each
domain will have the webroot path that preceded it.
For instance: `-w /var/www/example -d example.com -d
www.example.com -w /var/www/thing -d thing.net -d
m.thing.net` (default: [])
--webroot-map WEBROOT_MAP
JSON dictionary mapping domains to webroot paths; this
implies -d for each entry. You may need to escape this
from your shell. E.g.: --webroot-map
'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}'
This option is merged with, but takes precedence over,
-w / -d entries. At present, if you put webroot-map in
a config file, it needs to be on a single line, like:
webroot-map = {"example.com":"/var/www"}. (default:
{})
apache:
Apache Web Server - Alpha
--apache-enmod APACHE_ENMOD
Path to the Apache 'a2enmod' binary. (default:
a2enmod)
--apache-dismod APACHE_DISMOD
Path to the Apache 'a2dismod' binary. (default:
a2dismod)
--apache-le-vhost-ext APACHE_LE_VHOST_EXT
SSL vhost configuration extension. (default: -le-
ssl.conf)
--apache-server-root APACHE_SERVER_ROOT
Apache server root directory. (default: /etc/apache2)
--apache-vhost-root APACHE_VHOST_ROOT
Apache server VirtualHost configuration root (default:
/etc/apache2/sites-available)
--apache-challenge-location APACHE_CHALLENGE_LOCATION
Directory path for challenge configuration. (default:
/etc/apache2)
--apache-handle-modules APACHE_HANDLE_MODULES
Let installer handle enabling required modules for
you.(Only Ubuntu/Debian currently) (default: True)
--apache-handle-sites APACHE_HANDLE_SITES
Let installer handle enabling sites for you.(Only
Ubuntu/Debian currently) (default: True)
null:
Null Installer

View file

@ -64,8 +64,8 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'Let\'s Encrypt'
copyright = u'2014-2015, Let\'s Encrypt Project'
project = u'Certbot'
copyright = u'2014-2016 - The Certbot software and documentation are licensed under the Apache 2.0 license as described at https://eff.org/cb-license '
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
@ -225,7 +225,7 @@ html_static_path = ['_static']
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'LetsEncryptdoc'
htmlhelp_basename = 'Certbotdoc'
# -- Options for LaTeX output ---------------------------------------------
@ -247,8 +247,8 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
('index', 'LetsEncrypt.tex', u'Let\'s Encrypt Documentation',
u'Let\'s Encrypt Project', 'manual'),
('index', 'Certbot.tex', u'Certbot Documentation',
u'Certbot Project', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@ -277,7 +277,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'certbot', u'Let\'s Encrypt Documentation',
('index', 'certbot', u'Certbot Documentation',
[project], 7),
('man/certbot', 'certbot', u'certbot script documentation',
[project], 1),
@ -293,8 +293,8 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'LetsEncrypt', u'Let\'s Encrypt Documentation',
u'Let\'s Encrypt Project', 'LetsEncrypt', 'One line description of project.',
('index', 'Certbot', u'Certbot Documentation',
u'Certbot Project', 'Certbot', 'One line description of project.',
'Miscellaneous'),
]

View file

@ -20,8 +20,8 @@ once:
.. code-block:: shell
git clone https://github.com/letsencrypt/letsencrypt
cd letsencrypt
git clone https://github.com/certbot/certbot
cd certbot
./letsencrypt-auto-source/letsencrypt-auto --os-packages-only
./tools/venv.sh
@ -57,8 +57,8 @@ your pull request must have thorough unit test coverage, pass our
`integration`_ tests, and be compliant with the :ref:`coding style
<coding-style>`.
.. _github issue tracker: https://github.com/letsencrypt/letsencrypt/issues
.. _Good Volunteer Task: https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+Volunteer+Task%22
.. _github issue tracker: https://github.com/certbot/certbot/issues
.. _Good Volunteer Task: https://github.com/certbot/certbot/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+Volunteer+Task%22
Testing
-------
@ -175,8 +175,8 @@ Configurators may implement just one of those).
There are also `~certbot.interfaces.IDisplay` plugins,
which implement bindings to alternative UI libraries.
.. _interfaces.py: https://github.com/letsencrypt/letsencrypt/blob/master/certbot/interfaces.py
.. _plugins/common.py: https://github.com/letsencrypt/letsencrypt/blob/master/certbot/plugins/common.py#L34
.. _interfaces.py: https://github.com/certbot/certbot/blob/master/certbot/interfaces.py
.. _plugins/common.py: https://github.com/certbot/certbot/blob/master/certbot/plugins/common.py#L34
Authenticators
@ -297,7 +297,7 @@ Please:
4. Remember to use ``pylint``.
.. _Google Python Style Guide:
https://google-styleguide.googlecode.com/svn/trunk/pyguide.html
https://google.github.io/styleguide/pyguide.html
.. _Sphinx-style: http://sphinx-doc.org/
.. _PEP 8 - Style Guide for Python Code:
https://www.python.org/dev/peps/pep-0008
@ -323,7 +323,7 @@ Steps:
See `Known Issues`_. If it's not a known issue, fix any errors.
.. _Known Issues:
https://github.com/letsencrypt/letsencrypt/wiki/Known-issues
https://github.com/certbot/certbot/wiki/Known-issues
Updating the documentation
==========================
@ -333,7 +333,7 @@ commands:
.. code-block:: shell
make -C docs clean html
make -C docs clean html man
This should generate documentation in the ``docs/_build/html``
directory.

View file

@ -1,4 +1,4 @@
Welcome to the Let's Encrypt client documentation!
Welcome to the Certbot documentation!
==================================================
.. toctree::

View file

@ -1,6 +1,6 @@
============
Introduction
============
=====================
README / Introduction
=====================
.. include:: ../README.rst
.. include:: ../CHANGES.rst

View file

@ -1 +1 @@
.. program-output:: certbot --help all
.. literalinclude:: cli-help.txt

View file

@ -3,4 +3,4 @@ Packaging Guide
===============
Documentation can be found at
https://github.com/letsencrypt/letsencrypt/wiki/Packaging.
https://github.com/certbot/certbot/wiki/Packaging.

View file

@ -5,66 +5,38 @@ User Guide
.. contents:: Table of Contents
:local:
.. _installation:
Getting Certbot
===============
Installation
============
To get specific instructions for installing Certbot on your OS, we recommend
visiting certbot.eff.org_. If you're offline, you can find some general
instructions `in the README / Introduction <intro.html#installation>`__
.. _letsencrypt-auto:
__ installation_
.. _certbot.eff.org: https://certbot.eff.org
letsencrypt-auto
----------------
.. _certbot-auto:
``letsencrypt-auto`` is a wrapper which installs some dependencies
from your OS standard package repositories (e.g. using `apt-get` or
`yum`), and for other dependencies it sets up a virtualized Python
environment with packages downloaded from PyPI [#venv]_. It also
provides automated updates.
The name of the certbot command
-------------------------------
To install and run the client, just type...
.. code-block:: shell
./letsencrypt-auto
.. hint:: During the beta phase, Let's Encrypt enforces strict rate limits on
the number of certificates issued for one domain. It is recommended to
initially use the test server via `--test-cert` until you get the desired
certificates.
Throughout the documentation, whenever you see references to
``letsencrypt`` script/binary, you can substitute in
``letsencrypt-auto``. For example, to get basic help you would type:
.. code-block:: shell
./letsencrypt-auto --help
or for full help, type:
.. code-block:: shell
./letsencrypt-auto --help all
``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, 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
the :doc:`contributing`. Some `other methods of installation`_ are discussed
below.
Many platforms now have native packages that give you a ``certbot`` or (for
older packages) ``letsencrypt`` command you can run. On others, the
``certbot-auto`` / ``letsencrypt-auto`` installer and wrapper script is a
stand-in. Throughout the documentation, whenever you see references to
``certbot`` script/binary, you should substitute in the name of the command
that certbot.eff.org_ told you to use on your system (``certbot``,
``letsencrypt``, or ``certbot-auto``).
Plugins
=======
The Let's Encrypt client supports a number of different "plugins" that can be
The Certbot client supports a number of different "plugins" that can be
used to obtain and/or install certificates. Plugins that can obtain a cert
are called "authenticators" and can be used with the "certonly" command.
Plugins that can install a cert are called "installers". Plugins that do both
can be used with the "letsencrypt run" command, which is the default.
can be used with the "certbot run" command, which is the default.
=========== ==== ==== ===============================================================
Plugin Auth Inst Notes
@ -79,27 +51,10 @@ standalone_ Y N Uses a "standalone" webserver to obtain a cert. Requires
webserver is not supported or not desired.
manual_ Y N Helps you obtain a cert by giving you instructions to perform
domain validation yourself.
nginx_ Y Y Very experimental and not included in letsencrypt-auto_.
nginx_ Y Y Very experimental and not included in certbot-auto_.
=========== ==== ==== ===============================================================
There are also a number of third-party plugins for the client, provided by other developers:
=========== ==== ==== ===============================================================
Plugin Auth Inst Notes
=========== ==== ==== ===============================================================
plesk_ Y Y Integration with the Plesk web hosting tool
haproxy_ Y Y Integration with the HAProxy load balancer
s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets
gandi_ Y Y Integration with Gandi's hosting products and API
=========== ==== ==== ===============================================================
.. _plesk: https://github.com/plesk/letsencrypt-plesk
.. _haproxy: https://code.greenhost.net/open/letsencrypt-haproxy
.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front
.. _gandi: https://github.com/Gandi/letsencrypt-gandi
Future plugins for IMAP servers, SMTP servers, IRC servers, etc, are likely to
be installers but not authenticators.
There are many third-party-plugins_ available.
Apache
------
@ -124,22 +79,22 @@ 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
potentially be a separate directory for each domain. When requesting 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
certbot 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
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 certbot. An example request
made to your web server would look like:
::
@ -173,7 +128,7 @@ specified port using each requested domain name.
Manual
------
If you'd like to obtain a cert running ``letsencrypt`` on a machine
If you'd like to obtain a cert running ``certbot`` on a machine
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
@ -184,18 +139,51 @@ be on a different computer.
Nginx
-----
In the future, if you're running Nginx you can use this plugin to
automatically obtain and install your certificate. The Nginx plugin
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``.
In the future, if you're running Nginx you will hopefully be able to use this
plugin to automatically obtain and install your certificate. The Nginx plugin is
still experimental, however, and is not installed with certbot-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
https://github.com/letsencrypt/letsencrypt/wiki/Plugins. If you're
interested, you can also :ref:`write your own plugin <dev-plugin>`.
There are also a number of third-party plugins for the client, provided by
other developers. Many are beta/experimental, but some are already in
widespread use:
=========== ==== ==== ===============================================================
Plugin Auth Inst Notes
=========== ==== ==== ===============================================================
plesk_ Y Y Integration with the Plesk web hosting tool
haproxy_ Y Y Integration with the HAProxy load balancer
s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets
gandi_ Y Y Integration with Gandi's hosting products and API
varnish_ Y N Obtain certs via a Varnish server
external_ Y N A plugin for convenient scripting (See also ticket 2782_)
icecast_ N Y Deploy certs to Icecast 2 streaming media servers
pritunl_ N Y Install certs in pritunl distributed OpenVPN servers
proxmox_ N Y Install certs in Proxmox Virtualization servers
postfix_ N Y STARTTLS Everywhere is becoming a Certbot Postfix/Exim plugin
=========== ==== ==== ===============================================================
.. _plesk: https://github.com/plesk/letsencrypt-plesk
.. _haproxy: https://code.greenhost.net/open/letsencrypt-haproxy
.. _s3front: https://github.com/dlapiduz/letsencrypt-s3front
.. _gandi: https://github.com/Gandi/letsencrypt-gandi
.. _icecast: https://github.com/e00E/lets-encrypt-icecast
.. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin
.. _2782: https://github.com/certbot/certbot/issues/2782
.. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl
.. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox
.. _external: https://github.com/marcan/letsencrypt-external
.. _postfix: https://github.com/EFForg/starttls-everywhere
If you're interested, you can also :ref:`write your own plugin <dev-plugin>`.
Renewal
=======
@ -204,23 +192,36 @@ Renewal
days). Make sure you renew the certificates at least once in 3
months.
The ``letsencrypt`` client now supports a ``renew`` action to check
The ``certbot`` 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``
``certbot 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.
You can also specify hooks to be run before or after a certificate is
renewed. For example, if you want to use the standalone_ plugin to renew
your certificates, you may want to use a command like
``certbot renew --standalone --pre-hook "service nginx stop" --post-hook "service nginx start"``
This will stop Nginx so standalone can bind to the necessary ports and
then restart Nginx after the plugin is finished. The hooks will only be
run if a certificate is due for renewal, so you can run this command
frequently without unnecessarily stopping your webserver. More
information about renewal hooks can be found by running
``certbot --help renew``.
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.
can run on a regular basis, like every week or every day). In that case,
you are likely to want to use the ``-q`` or ``--quiet`` quiet flag to
silence all output except errors.
The ``--force-renew`` flag may be helpful for automating renewal;
it causes the expiration time of the certificate(s) to be ignored when
@ -229,9 +230,9 @@ 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
Note that options provided to ``certbot renew`` will apply to
*every* certificate for which renewal is attempted; for example,
``letsencrypt renew --rsa-key-size 4096`` would try to replace every
``certbot 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
@ -240,10 +241,12 @@ 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
is ``certbot certonly`` with the complete set of subject domains of
a specific certificate specified via `-d` flags. You may also want to
include the ``-n`` or ``--noninteractive`` flag to prevent blocking on
user input (which is useful when running the command from cron).
``letsencrypt certonly -d example.com -d www.example.com``
``certbot certonly -n -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
@ -256,21 +259,25 @@ 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 improving the renewal process, and we
Certbot is working hard on improving the renewal process, and we
apologize for any inconveniences you encounter in integrating these
commands into your individual environment.
.. _command-line:
Command line options
====================
Certbot supports a lot of command line options. Here's the full list, from
``certbot --help all``:
.. literalinclude:: cli-help.txt
.. _where-certs:
Where are my certificates?
==========================
First of all, we encourage you to use Apache or nginx installers, both
which perform the certificate management automatically. If, however,
you prefer to manage everything by hand, this section provides
information on where to find necessary files.
All generated keys and issued certificates can be found in
``/etc/letsencrypt/live/$domain``. Rather than copying, please point
your (web) server configuration directly to those files (or create
@ -287,7 +294,7 @@ The following files are available:
Private key for the certificate.
.. warning:: This **must be kept secret at all times**! Never share
it with anyone, including Let's Encrypt developers. You cannot
it with anyone, including Certbot developers. You cannot
put it into a safe, however - your server still needs to access
this file in order for SSL/TLS to work.
@ -330,8 +337,8 @@ will cause nasty errors served through the browsers!
.. note:: All files are PEM-encoded (as the filename suffix
suggests). If you need other format, such as DER or PFX, then you
could convert using ``openssl``, but this means you will not
benefit from automatic renewal_!
could convert using ``openssl``. You can automate that with
``--renew-hook`` if you're using automatic renewal_.
.. _config-file:
@ -340,7 +347,7 @@ Configuration file
==================
It is possible to specify configuration file with
``letsencrypt-auto --config cli.ini`` (or shorter ``-c cli.ini``). An
``certbot-auto --config cli.ini`` (or shorter ``-c cli.ini``). An
example configuration file is shown below:
.. include:: ../examples/cli.ini
@ -359,13 +366,14 @@ By default, the following locations are searched:
Getting help
============
If you're having problems you can chat with us on `IRC (#letsencrypt @
Freenode) <https://webchat.freenode.net?channels=%23letsencrypt>`_ or
get support on our `forums <https://community.letsencrypt.org>`_.
If you're having problems you can chat with us on `IRC (#certbot @
OFTC) <https://webchat.oftc.net?channels=%23certbot>`_ or at
`IRC (#letsencrypt @ freenode) <https://webchat.freenode.net?channels=%23letsencrypt>`_
or get support on the Let's Encrypt `forums <https://community.letsencrypt.org>`_.
If you find a bug in the software, please do report it in our `issue
tracker
<https://github.com/letsencrypt/letsencrypt/issues>`_. Remember to
<https://github.com/certbot/certbot/issues>`_. Remember to
give us as much information as possible:
- copy and paste exact command line used and the output (though mind
@ -373,9 +381,9 @@ give us as much information as possible:
information, including your email and domains)
- copy and paste logs from ``/var/log/letsencrypt`` (though mind they
also might contain personally identifiable information)
- copy and paste ``letsencrypt --version`` output
- copy and paste ``certbot --version`` output
- your operating system, including specific version
- specify which installation_ method you've chosen
- specify which installation method you've chosen
Other methods of installation
=============================
@ -390,10 +398,10 @@ plugins cannot reach it from inside the Docker container.
You should definitely read the :ref:`where-certs` section, in order to
know how to manage the certs
manually. https://github.com/letsencrypt/letsencrypt/wiki/Ciphersuite-guidance
manually. https://github.com/certbot/certbot/wiki/Ciphersuite-guidance
provides some information about recommended ciphersuites. If none of
these make much sense to you, you should definitely use the
letsencrypt-auto_ method, which enables you to use installer plugins
certbot-auto_ method, which enables you to use installer plugins
that cover both of those hard topics.
If you're still not convinced and have decided to use this method,
@ -402,7 +410,7 @@ to, `install Docker`_, then issue the following command:
.. code-block:: shell
sudo docker run -it --rm -p 443:443 -p 80:80 --name letsencrypt \
sudo docker run -it --rm -p 443:443 -p 80:80 --name certbot \
-v "/etc/letsencrypt:/etc/letsencrypt" \
-v "/var/lib/letsencrypt:/var/lib/letsencrypt" \
quay.io/letsencrypt/letsencrypt:latest auth
@ -432,21 +440,27 @@ Operating System Packages
.. code-block:: shell
sudo pacman -S letsencrypt letsencrypt-apache
sudo pacman -S letsencrypt
**Debian**
If you run Debian Stretch or Debian Sid, you can install letsencrypt packages.
If you run Debian Stretch or Debian Sid, you can install certbot packages.
.. code-block:: shell
sudo apt-get update
sudo apt-get install letsencrypt python-letsencrypt-apache
sudo apt-get install certbot python-certbot-apache
If you don't want to use the Apache plugin, you can omit the
``python-letsencrypt-apache`` package.
``python-certbot-apache`` package.
Packages for Debian Jessie are coming in the next few weeks.
Packages exist for Debian Jessie via backports. First you'll have to follow the
instructions at http://backports.debian.org/Instructions/ to enable the Jessie backports
repo, if you have not already done so. Then run:
.. code-block:: shell
sudo apt-get install letsencrypt python-letsencrypt-apache -t jessie-backports
**Fedora**
@ -456,7 +470,7 @@ Packages for Debian Jessie are coming in the next few weeks.
**Gentoo**
The official Let's Encrypt client is available in Gentoo Portage. If you
The official Certbot client is available in Gentoo Portage. If you
want to use the Apache plugin, it has to be installed separately:
.. code-block:: shell
@ -465,8 +479,12 @@ want to use the Apache plugin, it has to be installed separately:
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:
Warning!
You can use Layman to add the mrueg overlay which does include a package for the
Certbot Nginx plugin, however, this plugin is known to be buggy and should only
be used with caution after creating a backup up your Nginx configuration.
We strongly recommend you use the app-crypt/letsencrypt package instead until
the Nginx plugin is ready.
.. code-block:: shell
@ -503,7 +521,7 @@ 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
Let's Encrypt client for your distribution of choice please have a
Certbot for your distribution of choice please have a
look at the :doc:`packaging`.
@ -519,19 +537,19 @@ whole process is described in the :doc:`contributing`.
environment, e.g. ``sudo python setup.py install``, ``sudo pip
install``, ``sudo ./venv/bin/...``. These modes of operation might
corrupt your operating system and are **not supported** by the
Let's Encrypt team!
Certbot team!
Comparison of different methods
-------------------------------
Unless you have a very specific requirements, we kindly ask you to use
the letsencrypt-auto_ method. It's the fastest, the most thoroughly
Unless you have a very specific requirements, we kindly suggest that you use
the certbot-auto_ method. It's the fastest, the most thoroughly
tested and the most reliable way of getting our software and the free
SSL certificates!
TLS/SSL certificates!
Beyond the methods discussed here, other methods may be possible, such as
installing Let's Encrypt directly with pip from PyPI or downloading a ZIP
installing Certbot directly with pip from PyPI or downloading a ZIP
archive from GitHub may be technically possible but are not presently
recommended or supported.

View file

@ -16,7 +16,7 @@ here = os.path.abspath(os.path.dirname(__file__))
readme = read_file(os.path.join(here, 'README.rst'))
version = '0.6.0.dev0'
version = '0.7.0.dev0'
# This package is a simple shim around certbot-apache

View file

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

View file

@ -0,0 +1,11 @@
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1
iQEcBAABAgAGBQJXM9ZDAAoJEE0XyZXNl3XyzGkH/2KeR0jYxXKlvwfCkxU6hSC0
eXcxZVQk59hCSvkNGE6Mj6rwQcyjSqmRp14MaJpq7NZADN6F+HWb6VB/Wq6moMQs
PJtthqwhF767Qg+Py9Hp6XmlKscjXB6AKCVxq5TBwEIOTtj0rhQRLF9/+GW6jFuf
kT6aUcDWNjOyWWUtp9vOVprDtegrltp0/2DNitlvPu263pKC+7I3GyLTq4fKP4EE
auZSAhFry9SNR3Usf2wD3kzhvLSrT3h9Yh5oA04oaX9H6e86EHwt6RJJRHpg8s6b
e0CBIIuaRJEmdiMUWlV/gAfH6M2PbG1wtJdxc0ThNEoWAjTsopr61BoHJ3cpCy4=
=+e7/
-----END PGP SIGNATURE-----

View file

@ -1,6 +1,6 @@
#!/bin/sh
#
# Download and run the latest release version of the Let's Encrypt client.
# Download and run the latest release version of the Certbot client.
#
# NOTE: THIS SCRIPT IS AUTO-GENERATED AND SELF-UPDATING
#
@ -19,11 +19,25 @@ 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.6.0.dev0"
LE_AUTO_VERSION="0.7.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
to both this script and certbot will be downloaded and installed. After
ensuring you have the latest versions installed, certbot will be invoked with
all arguments you have provided.
Help for certbot itself cannot be provided until it is installed.
--debug attempt experimental installation
-h, --help print this help
-n, --non-interactive, --noninteractive run without asking for user input
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
-v, --verbose provide more output
All arguments are accepted and forwarded to the Certbot client when run."
# This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
for arg in "$@" ; do
case "$arg" in
--debug)
@ -34,25 +48,43 @@ for arg in "$@" ; do
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1;;
--help)
HELP=1;;
--noninteractive|--non-interactive)
ASSUME_YES=1;;
--verbose)
VERBOSE=1;;
[!-]*|-*[!v]*|-)
# Anything that isn't -v, -vv, etc.: that is, anything that does not
# start with a -, contains anything that's not a v, or is just "-"
;;
*) # -v+ remains.
VERBOSE=1;;
-[!-]*)
while getopts ":hnv" short_arg $arg; do
case "$short_arg" in
h)
HELP=1;;
n)
ASSUME_YES=1;;
v)
VERBOSE=1;;
esac
done;;
esac
done
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
# letsencrypt itself needs root access for almost all modes of operation
if [ $BASENAME = "letsencrypt-auto" ]; then
# letsencrypt-auto does not respect --help or --yes for backwards compatibility
ASSUME_YES=1
HELP=0
fi
# certbot-auto needs root access to bootstrap OS dependencies, and
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
# this script *can* be run as root (not recommended), or fall back to using
# `su`
SUDO_ENV=""
export CERTBOT_AUTO="$0"
if test "`id -u`" -ne "0" ; then
if command -v sudo 1>/dev/null 2>&1; then
SUDO=sudo
SUDO_ENV="CERTBOT_AUTO=$0"
else
echo \"sudo\" is not available, will use \"su\" for installation steps...
# Because the parameters in `su -c` has to be a string,
@ -151,30 +183,45 @@ BootstrapDebCommon() {
augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
if [ "$ASSUME_YES" = 1 ]; then
YES_FLAG="-y"
fi
AddBackportRepo() {
# ARGS:
BACKPORT_NAME="$1"
BACKPORT_SOURCELINE="$2"
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
if echo $BACKPORT_NAME | grep -q wheezy ; then
/bin/echo '(Backports are only installed if explicitly requested via "apt-get install -t wheezy-backports")'
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
sleep 1s
add_backports=1
else
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
case $response in
[yY][eE][sS]|[yY]|"")
add_backports=1;;
*)
add_backports=0;;
esac
fi
if [ "$add_backports" = 1 ]; then
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
$SUDO apt-get update
fi
fi
$SUDO apt-get install -y --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
fi
}
@ -186,12 +233,12 @@ BootstrapDebCommon() {
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Let's Encrypt apache plugin..."
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
fi
$SUDO apt-get install -y --no-install-recommends \
$SUDO apt-get install $YES_FLAG --no-install-recommends \
python \
python-dev \
$virtualenv \
@ -212,9 +259,10 @@ BootstrapDebCommon() {
BootstrapRpmCommon() {
# Tested with:
# - Fedora 22, 23 (x64)
# - Fedora 20, 21, 22, 23 (x64)
# - Centos 7 (x64: on DigitalOcean droplet)
# - CentOS 7 Minimal install in a Hyper-V VM
# - CentOS 6 (EPEL must be installed manually)
if type dnf 2>/dev/null
then
@ -228,54 +276,62 @@ BootstrapRpmCommon() {
exit 1
fi
pkgs="
gcc
dialog
augeas-libs
openssl
openssl-devel
libffi-devel
redhat-rpm-config
ca-certificates
"
# Some distros and older versions of current distros use a "python27"
# instead of "python" naming convention. Try both conventions.
if ! $SUDO $tool install -y \
python \
python-devel \
python-virtualenv \
python-tools \
python-pip
then
if ! $SUDO $tool install -y \
python27 \
python27-devel \
python27-virtualenv \
python27-tools \
python27-pip
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
if $SUDO $tool list python >/dev/null 2>&1; then
pkgs="$pkgs
python
python-devel
python-virtualenv
python-tools
python-pip
"
else
pkgs="$pkgs
python27
python27-devel
python27-virtualenv
python27-tools
python27-pip
"
fi
if ! $SUDO $tool install -y \
gcc \
dialog \
augeas-libs \
openssl \
openssl-devel \
libffi-devel \
redhat-rpm-config \
ca-certificates
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
if $SUDO $tool list installed "httpd" >/dev/null 2>&1; then
if ! $SUDO $tool install -y mod_ssl
then
echo "Apache found, but mod_ssl could not be installed."
fi
pkgs="$pkgs
mod_ssl
"
fi
if [ "$ASSUME_YES" = 1 ]; then
yes_flag="-y"
fi
if ! $SUDO $tool install $yes_flag $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
fi
}
BootstrapSuseCommon() {
# SLE12 don't have python-virtualenv
$SUDO zypper -nq in -l \
if [ "$ASSUME_YES" = 1 ]; then
zypper_flags="-nq"
install_flags="-l"
fi
$SUDO zypper $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \
@ -310,8 +366,12 @@ BootstrapArchCommon() {
# pacman -T exits with 127 if there are missing dependencies
missing=$($SUDO pacman -T $deps) || true
if [ "$ASSUME_YES" = 1 ]; then
noconfirm="--noconfirm"
fi
if [ "$missing" ]; then
$SUDO pacman -S --needed $missing
$SUDO pacman -S --needed $missing $noconfirm
fi
}
@ -365,7 +425,8 @@ BootstrapMac() {
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" ]; then
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.
# python.org, MacPorts or HomeBrew Python installations should all be OK.
echo "Installing python..."
@ -375,7 +436,8 @@ BootstrapMac() {
# Workaround for _dlopen not finding augeas on OS X
if [ "$pkgman" = "port" ] && ! [ -e "/usr/local/lib/libaugeas.dylib" ] && [ -e "/opt/local/lib/libaugeas.dylib" ]; then
echo "Applying augeas workaround"
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib
$SUDO mkdir -p /usr/local/lib/
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib/
fi
if ! hash pip 2>/dev/null; then
@ -391,6 +453,11 @@ BootstrapMac() {
fi
}
BootstrapSmartOS() {
pkgin update
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
}
# Install required OS packages:
Bootstrap() {
@ -423,10 +490,12 @@ Bootstrap() {
ExperimentalBootstrap "FreeBSD" BootstrapFreeBsd
elif uname | grep -iq Darwin ; then
ExperimentalBootstrap "Mac OS X" BootstrapMac
elif grep -iq "Amazon Linux" /etc/issue ; then
elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then
ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
elif [ -f /etc/product ] && grep -q "Joyent Instance" /etc/product ; then
ExperimentalBootstrap "Joyent SmartOS Zone" BootstrapSmartOS
else
echo "Sorry, I don't know how to bootstrap Let's Encrypt on your operating system!"
echo "Sorry, I don't know how to bootstrap Certbot on your operating system!"
echo
echo "You will need to bootstrap, configure virtualenv, and run pip install manually."
echo "Please see https://letsencrypt.readthedocs.org/en/latest/contributing.html#prerequisites"
@ -446,7 +515,8 @@ if [ "$1" = "--le-auto-phase2" ]; then
shift 1 # the --le-auto-phase2 arg
if [ -f "$VENV_BIN/letsencrypt" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep ^letsencrypt | cut -d " " -f 2)
# grep for both certbot and letsencrypt until certbot and shim packages have been released
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
else
INSTALLED_VERSION="none"
fi
@ -462,11 +532,12 @@ if [ "$1" = "--le-auto-phase2" ]; then
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
# This is the flattened list of packages letsencrypt-auto installs. To generate
# this, do `pip install --no-cache-dir -e acme -e . -e letsencrypt-apache`, and
# This is the flattened list of packages certbot-auto installs. To generate
# this, do `pip install --no-cache-dir -e acme -e . -e certbot-apache`, and
# then use `hashin` or a more secure method to gather the hashes.
argparse==1.4.0 \
@ -645,15 +716,21 @@ mock==1.0.1 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.5.0 \
--hash=sha256:ceb4127c13213f0006a564be82176b968c6b374d20d9fc78555d0658a252b275 \
--hash=sha256:0605c63c656d33c883a05675f5db9cfb85d503f2771c885031800e0da7631abd
letsencrypt==0.5.0 \
--hash=sha256:f90f883e99cdbdf8142335bdbf4f74a8af143ee4b4ec60fb49c6e47418c1114c \
--hash=sha256:e38a2b70b82be79bc195307652244a3e012ec73d897d4dbd3f80cf698496d15a
letsencrypt-apache==0.5.0 \
--hash=sha256:a767882164a7b09d9c12c80684a28a782135fdaf35654ef5a02c0b7b1d27ab8d \
--hash=sha256:c20e7b9c517aa4a7d70e6bd9382da7259f00bc191b9e60d8e312e48837a00c41
acme==0.6.0 \
--hash=sha256:cbe4e7a340a19725a8740ed86e30abdbe18fc22c4c6022b7a8e56642d502bcc3 \
--hash=sha256:ec4e6009dfbd629b58473eb06bbebfd9fb2a79fc8831c149e9205bc38a98ecc6
certbot==0.6.0 \
--hash=sha256:a893632d228864b0a751db9f3fdd93439ed34b988ea21b64fb0f0fa2ceded6a2 \
--hash=sha256:80b0b7dc5afeec2816ef638a61e7c628d73cd72666eebf4984be426d1c2b492d
certbot-apache==0.6.0 \
--hash=sha256:0ab077f0913b81ed5c1b141c3a7c4c0228ef3738d8d61a93db794d9a80718d43 \
--hash=sha256:1cfbe751209079a803758f472200816fac559f2a36fdd582d25e3ba5601423a1
letsencrypt==0.6.0 \
--hash=sha256:93196c7dcd57272a753e525d145c5a9987c8968c22ec954bcf83dcc9d2499a76 \
--hash=sha256:a16d6c395f1bf5fd61a28ef83dc78f42dbecbad9d00be6236f2ad8915645c154
letsencrypt-apache==0.6.0 \
--hash=sha256:02fadc52a0796e53978c508beec9c53e1fc047660240832b9bde5d53ab3a1379 \
--hash=sha256:1c5522d94d7750bdb9bfa6201d2c263e914f662c9d0079e673167233cf4364f1
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -813,7 +890,6 @@ UNLIKELY_EOF
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
PIP_STATUS=$?
set -e
rm -rf "$TEMP_DIR"
if [ "$PIP_STATUS" != 0 ]; then
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages:"
@ -823,18 +899,32 @@ UNLIKELY_EOF
fi
echo "Installation succeeded."
fi
echo "Requesting root privileges to run letsencrypt..."
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
$SUDO "$VENV_BIN/letsencrypt" "$@"
if [ -n "$SUDO" ]; then
# SUDO is su wrapper or sudo
echo "Requesting root privileges to run certbot..."
echo " $VENV_BIN/letsencrypt" "$@"
fi
if [ -z "$SUDO_ENV" ] ; then
# SUDO is su wrapper / noop
$SUDO "$VENV_BIN/letsencrypt" "$@"
else
# sudo
$SUDO "$SUDO_ENV" "$VENV_BIN/letsencrypt" "$@"
fi
else
# Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of
# letsencrypt-auto (which is always the same as that of the letsencrypt
# package). Phase 2 checks the version of the locally installed letsencrypt.
# certbot-auto (which is always the same as that of the certbot
# package). Phase 2 checks the version of the locally installed certbot.
if [ ! -f "$VENV_BIN/letsencrypt" ]; then
if [ "$HELP" = 1 ]; then
echo "$USAGE"
exit 0
fi
# If it looks like we've never bootstrapped before, bootstrap:
Bootstrap
fi
@ -844,8 +934,8 @@ else
fi
if [ "$NO_SELF_UPGRADE" != 1 ]; then
echo "Checking for new version..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# ---------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/fetch.py"
"""Do downloading and JSON parsing without additional dependencies. ::
@ -918,7 +1008,7 @@ def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get(
environ.get('LE_AUTO_JSON_URL',
'https://pypi.python.org/pypi/letsencrypt/json')))
'https://pypi.python.org/pypi/certbot/json')))
# metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most
@ -937,7 +1027,7 @@ def verified_new_le_auto(get, tag, temp_dir):
"""
le_auto_dir = environ.get(
'LE_AUTO_DIR_TEMPLATE',
'https://raw.githubusercontent.com/letsencrypt/letsencrypt/%s/'
'https://raw.githubusercontent.com/certbot/certbot/%s/'
'letsencrypt-auto-source/') % tag
write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto')
write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig')
@ -953,7 +1043,7 @@ def verified_new_le_auto(get, tag, temp_dir):
stderr=dev_null)
except CalledProcessError as exc:
raise ExpectedError("Couldn't verify signature of downloaded "
"letsencrypt-auto.", exc)
"certbot-auto.", exc)
def main():
@ -978,32 +1068,28 @@ if __name__ == '__main__':
UNLIKELY_EOF
# ---------------------------------------------------------------------------
DeterminePythonVersion
REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version`
if [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading letsencrypt-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
if ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
echo "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more
# dependencies (curl, etc.), for better flow control, and for the option of
# future Windows compatibility.
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# Install new copy of letsencrypt-auto.
# Install new copy of certbot-auto.
# TODO: Deal with quotes in pathnames.
echo "Replacing letsencrypt-auto..."
echo "Replacing certbot-auto..."
# Clone permissions with cp. chmod and chown don't have a --reference
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
# Using mv rather than cp leaves the old file descriptor pointing to the
# original copy so the shell can continue to read it unmolested. mv across
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.
fi # Self-upgrading is allowed.

View file

@ -20,10 +20,24 @@ VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="{{ LE_AUTO_VERSION }}"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
to both this script and certbot will be downloaded and installed. After
ensuring you have the latest versions installed, certbot will be invoked with
all arguments you have provided.
Help for certbot itself cannot be provided until it is installed.
--debug attempt experimental installation
-h, --help print this help
-n, --non-interactive, --noninteractive run without asking for user input
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
-v, --verbose provide more output
All arguments are accepted and forwarded to the Certbot client when run."
# This script takes the same arguments as the main letsencrypt program, but it
# additionally responds to --verbose (more output) and --debug (allow support
# for experimental platforms)
for arg in "$@" ; do
case "$arg" in
--debug)
@ -34,25 +48,43 @@ for arg in "$@" ; do
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
NO_SELF_UPGRADE=1;;
--help)
HELP=1;;
--noninteractive|--non-interactive)
ASSUME_YES=1;;
--verbose)
VERBOSE=1;;
[!-]*|-*[!v]*|-)
# Anything that isn't -v, -vv, etc.: that is, anything that does not
# start with a -, contains anything that's not a v, or is just "-"
;;
*) # -v+ remains.
VERBOSE=1;;
-[!-]*)
while getopts ":hnv" short_arg $arg; do
case "$short_arg" in
h)
HELP=1;;
n)
ASSUME_YES=1;;
v)
VERBOSE=1;;
esac
done;;
esac
done
# letsencrypt-auto needs root access to bootstrap OS dependencies, and
if [ $BASENAME = "letsencrypt-auto" ]; then
# letsencrypt-auto does not respect --help or --yes for backwards compatibility
ASSUME_YES=1
HELP=0
fi
# certbot-auto needs root access to bootstrap OS dependencies, and
# certbot itself needs root access for almost all modes of operation
# The "normal" case is that sudo is used for the steps that need root, but
# this script *can* be run as root (not recommended), or fall back to using
# `su`
SUDO_ENV=""
export CERTBOT_AUTO="$0"
if test "`id -u`" -ne "0" ; then
if command -v sudo 1>/dev/null 2>&1; then
SUDO=sudo
SUDO_ENV="CERTBOT_AUTO=$0"
else
echo \"sudo\" is not available, will use \"su\" for installation steps...
# Because the parameters in `su -c` has to be a string,
@ -122,6 +154,7 @@ DeterminePythonVersion() {
{{ bootstrappers/gentoo_common.sh }}
{{ bootstrappers/free_bsd.sh }}
{{ bootstrappers/mac.sh }}
{{ bootstrappers/smartos.sh }}
# Install required OS packages:
Bootstrap() {
@ -154,8 +187,10 @@ Bootstrap() {
ExperimentalBootstrap "FreeBSD" BootstrapFreeBsd
elif uname | grep -iq Darwin ; then
ExperimentalBootstrap "Mac OS X" BootstrapMac
elif grep -iq "Amazon Linux" /etc/issue ; then
elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then
ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
elif [ -f /etc/product ] && grep -q "Joyent Instance" /etc/product ; then
ExperimentalBootstrap "Joyent SmartOS Zone" BootstrapSmartOS
else
echo "Sorry, I don't know how to bootstrap Certbot on your operating system!"
echo
@ -177,7 +212,8 @@ if [ "$1" = "--le-auto-phase2" ]; then
shift 1 # the --le-auto-phase2 arg
if [ -f "$VENV_BIN/letsencrypt" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep ^letsencrypt | cut -d " " -f 2)
# grep for both certbot and letsencrypt until certbot and shim packages have been released
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
else
INSTALLED_VERSION="none"
fi
@ -193,6 +229,7 @@ if [ "$1" = "--le-auto-phase2" ]; then
echo "Installing Python packages..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# There is no $ interpolation due to quotes on starting heredoc delimiter.
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
@ -209,7 +246,6 @@ UNLIKELY_EOF
PIP_OUT=`"$VENV_BIN/pip" install --no-cache-dir --require-hashes -r "$TEMP_DIR/letsencrypt-auto-requirements.txt" 2>&1`
PIP_STATUS=$?
set -e
rm -rf "$TEMP_DIR"
if [ "$PIP_STATUS" != 0 ]; then
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages:"
@ -219,18 +255,32 @@ UNLIKELY_EOF
fi
echo "Installation succeeded."
fi
echo "Requesting root privileges to run certbot..."
echo " " $SUDO "$VENV_BIN/letsencrypt" "$@"
$SUDO "$VENV_BIN/letsencrypt" "$@"
if [ -n "$SUDO" ]; then
# SUDO is su wrapper or sudo
echo "Requesting root privileges to run certbot..."
echo " $VENV_BIN/letsencrypt" "$@"
fi
if [ -z "$SUDO_ENV" ] ; then
# SUDO is su wrapper / noop
$SUDO "$VENV_BIN/letsencrypt" "$@"
else
# sudo
$SUDO "$SUDO_ENV" "$VENV_BIN/letsencrypt" "$@"
fi
else
# Phase 1: Upgrade letsencrypt-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
#
# Each phase checks the version of only the thing it is responsible for
# upgrading. Phase 1 checks the version of the latest release of
# letsencrypt-auto (which is always the same as that of the certbot
# certbot-auto (which is always the same as that of the certbot
# package). Phase 2 checks the version of the locally installed certbot.
if [ ! -f "$VENV_BIN/letsencrypt" ]; then
if [ "$HELP" = 1 ]; then
echo "$USAGE"
exit 0
fi
# If it looks like we've never bootstrapped before, bootstrap:
Bootstrap
fi
@ -240,40 +290,36 @@ else
fi
if [ "$NO_SELF_UPGRADE" != 1 ]; then
echo "Checking for new version..."
TEMP_DIR=$(TempDir)
trap 'rm -rf "$TEMP_DIR"' EXIT
# ---------------------------------------------------------------------------
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..."
if ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
echo "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
echo "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more
# dependencies (curl, etc.), for better flow control, and for the option of
# future Windows compatibility.
"$LE_PYTHON" "$TEMP_DIR/fetch.py" --le-auto-script "v$REMOTE_VERSION"
# Install new copy of letsencrypt-auto.
# Install new copy of certbot-auto.
# TODO: Deal with quotes in pathnames.
echo "Replacing letsencrypt-auto..."
echo "Replacing certbot-auto..."
# Clone permissions with cp. chmod and chown don't have a --reference
# option on OS X or BSD, and stat -c on Linux is stat -f on OS X and BSD:
echo " " $SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp -p "$0" "$TEMP_DIR/letsencrypt-auto.permission-clone"
echo " " $SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
$SUDO cp "$TEMP_DIR/letsencrypt-auto" "$TEMP_DIR/letsencrypt-auto.permission-clone"
# Using mv rather than cp leaves the old file descriptor pointing to the
# original copy so the shell can continue to read it unmolested. mv across
# filesystems is non-atomic, doing `rm dest, cp src dest, rm src`, but the
# cp is unlikely to fail (esp. under sudo) if the rm doesn't.
echo " " $SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
$SUDO mv -f "$TEMP_DIR/letsencrypt-auto.permission-clone" "$0"
# TODO: Clean up temp dir safely, even if it has quotes in its path.
rm -rf "$TEMP_DIR"
fi # A newer version is available.
fi # Self-upgrading is allowed.

View file

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

View file

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

View file

@ -16,7 +16,8 @@ BootstrapMac() {
$pkgcmd augeas
$pkgcmd dialog
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" ]; then
if [ "$(which python)" = "/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python" \
-o "$(which python)" = "/usr/bin/python" ]; then
# We want to avoid using the system Python because it requires root to use pip.
# python.org, MacPorts or HomeBrew Python installations should all be OK.
echo "Installing python..."
@ -26,7 +27,8 @@ BootstrapMac() {
# Workaround for _dlopen not finding augeas on OS X
if [ "$pkgman" = "port" ] && ! [ -e "/usr/local/lib/libaugeas.dylib" ] && [ -e "/opt/local/lib/libaugeas.dylib" ]; then
echo "Applying augeas workaround"
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib
$SUDO mkdir -p /usr/local/lib/
$SUDO ln -s /opt/local/lib/libaugeas.dylib /usr/local/lib/
fi
if ! hash pip 2>/dev/null; then

View file

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

View file

@ -0,0 +1,4 @@
BootstrapSmartOS() {
pkgin update
pkgin -y install 'gcc49' 'py27-augeas' 'py27-virtualenv'
}

View file

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

View file

@ -68,7 +68,7 @@ def latest_stable_version(get):
"""Return the latest stable release of letsencrypt."""
metadata = loads(get(
environ.get('LE_AUTO_JSON_URL',
'https://pypi.python.org/pypi/letsencrypt/json')))
'https://pypi.python.org/pypi/certbot/json')))
# metadata['info']['version'] actually returns the latest of any kind of
# release release, contrary to https://wiki.python.org/moin/PyPIJSON.
# The regex is a sufficient regex for picking out prereleases for most
@ -87,7 +87,7 @@ def verified_new_le_auto(get, tag, temp_dir):
"""
le_auto_dir = environ.get(
'LE_AUTO_DIR_TEMPLATE',
'https://raw.githubusercontent.com/letsencrypt/letsencrypt/%s/'
'https://raw.githubusercontent.com/certbot/certbot/%s/'
'letsencrypt-auto-source/') % tag
write(get(le_auto_dir + 'letsencrypt-auto'), temp_dir, 'letsencrypt-auto')
write(get(le_auto_dir + 'letsencrypt-auto.sig'), temp_dir, 'letsencrypt-auto.sig')
@ -103,7 +103,7 @@ def verified_new_le_auto(get, tag, temp_dir):
stderr=dev_null)
except CalledProcessError as exc:
raise ExpectedError("Couldn't verify signature of downloaded "
"letsencrypt-auto.", exc)
"certbot-auto.", exc)
def main():

View file

@ -1,4 +1,4 @@
# This is the flattened list of packages letsencrypt-auto installs. To generate
# This is the flattened list of packages certbot-auto installs. To generate
# this, do `pip install --no-cache-dir -e acme -e . -e certbot-apache`, and
# then use `hashin` or a more secure method to gather the hashes.
@ -178,12 +178,18 @@ mock==1.0.1 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.5.0 \
--hash=sha256:ceb4127c13213f0006a564be82176b968c6b374d20d9fc78555d0658a252b275 \
--hash=sha256:0605c63c656d33c883a05675f5db9cfb85d503f2771c885031800e0da7631abd
letsencrypt==0.5.0 \
--hash=sha256:f90f883e99cdbdf8142335bdbf4f74a8af143ee4b4ec60fb49c6e47418c1114c \
--hash=sha256:e38a2b70b82be79bc195307652244a3e012ec73d897d4dbd3f80cf698496d15a
letsencrypt-apache==0.5.0 \
--hash=sha256:a767882164a7b09d9c12c80684a28a782135fdaf35654ef5a02c0b7b1d27ab8d \
--hash=sha256:c20e7b9c517aa4a7d70e6bd9382da7259f00bc191b9e60d8e312e48837a00c41
acme==0.6.0 \
--hash=sha256:cbe4e7a340a19725a8740ed86e30abdbe18fc22c4c6022b7a8e56642d502bcc3 \
--hash=sha256:ec4e6009dfbd629b58473eb06bbebfd9fb2a79fc8831c149e9205bc38a98ecc6
certbot==0.6.0 \
--hash=sha256:a893632d228864b0a751db9f3fdd93439ed34b988ea21b64fb0f0fa2ceded6a2 \
--hash=sha256:80b0b7dc5afeec2816ef638a61e7c628d73cd72666eebf4984be426d1c2b492d
certbot-apache==0.6.0 \
--hash=sha256:0ab077f0913b81ed5c1b141c3a7c4c0228ef3738d8d61a93db794d9a80718d43 \
--hash=sha256:1cfbe751209079a803758f472200816fac559f2a36fdd582d25e3ba5601423a1
letsencrypt==0.6.0 \
--hash=sha256:93196c7dcd57272a753e525d145c5a9987c8968c22ec954bcf83dcc9d2499a76 \
--hash=sha256:a16d6c395f1bf5fd61a28ef83dc78f42dbecbad9d00be6236f2ad8915645c154
letsencrypt-apache==0.6.0 \
--hash=sha256:02fadc52a0796e53978c508beec9c53e1fc047660240832b9bde5d53ab3a1379 \
--hash=sha256:1c5522d94d7750bdb9bfa6201d2c263e914f662c9d0079e673167233cf4364f1

View file

@ -183,7 +183,7 @@ def run_le_auto(venv_dir, base_url, **kwargs):
d = dict(XDG_DATA_HOME=venv_dir,
# URL to PyPI-style JSON that tell us the latest released version
# of LE:
LE_AUTO_JSON_URL=base_url + 'letsencrypt/json',
LE_AUTO_JSON_URL=base_url + 'certbot/json',
# URL to dir containing letsencrypt-auto and letsencrypt-auto.sig:
LE_AUTO_DIR_TEMPLATE=base_url + '%s/',
# The public key corresponding to signing.key:
@ -231,7 +231,7 @@ class AutoTests(TestCase):
* The OpenSSL sig mismatches.
For tests which get to the end, we run merely ``letsencrypt --version``.
The functioning of the rest of the letsencrypt script is covered by other
The functioning of the rest of the certbot script is covered by other
test suites.
"""
@ -258,7 +258,7 @@ class AutoTests(TestCase):
with ephemeral_dir() as venv_dir:
# This serves a PyPI page with a higher version, a GitHub-alike
# with a corresponding le-auto script, and a matching signature.
resources = {'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}),
'v99.9.9/letsencrypt-auto': NEW_LE_AUTO,
'v99.9.9/letsencrypt-auto.sig': NEW_LE_AUTO_SIG}
with serving(resources) as base_url:
@ -277,7 +277,7 @@ class AutoTests(TestCase):
ok_(re.match(r'letsencrypt \d+\.\d+\.\d+',
err.strip().splitlines()[-1]))
# Make a few assertions to test the validity of the next tests:
self.assertIn('Upgrading letsencrypt-auto ', out)
self.assertIn('Upgrading certbot-auto ', out)
self.assertIn('Creating virtual environment...', out)
# Now we have le-auto 99.9.9 and LE 99.9.9 installed. This
@ -286,14 +286,14 @@ class AutoTests(TestCase):
# Test when neither phase-1 upgrade nor phase-2 upgrade is
# needed (probably a common case):
out, err = run_letsencrypt_auto()
self.assertNotIn('Upgrading letsencrypt-auto ', out)
self.assertNotIn('Upgrading certbot-auto ', out)
self.assertNotIn('Creating virtual environment...', out)
# Test when a phase-1 upgrade is not needed but a phase-2
# upgrade is:
set_le_script_version(venv_dir, '0.0.1')
out, err = run_letsencrypt_auto()
self.assertNotIn('Upgrading letsencrypt-auto ', out)
self.assertNotIn('Upgrading certbot-auto ', out)
self.assertIn('Creating virtual environment...', out)
def test_openssl_failure(self):
@ -301,8 +301,8 @@ class AutoTests(TestCase):
with ephemeral_dir() as venv_dir:
# Serve an unrelated hash signed with the good key (easier than
# making a bad key, and a mismatch is a mismatch):
resources = {'': '<a href="letsencrypt/">letsencrypt/</a>',
'letsencrypt/json': dumps({'releases': {'99.9.9': None}}),
resources = {'': '<a href="certbot/">certbot/</a>',
'certbot/json': dumps({'releases': {'99.9.9': None}}),
'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'),
'v99.9.9/letsencrypt-auto.sig': signed('something else')}
with serving(resources) as base_url:
@ -312,16 +312,16 @@ class AutoTests(TestCase):
except CalledProcessError as exc:
eq_(exc.returncode, 1)
self.assertIn("Couldn't verify signature of downloaded "
"letsencrypt-auto.",
"certbot-auto.",
exc.output)
else:
self.fail('Signature check on letsencrypt-auto erroneously passed.')
self.fail('Signature check on certbot-auto erroneously passed.')
def test_pip_failure(self):
"""Make sure pip stops us if there is a hash mismatch."""
with ephemeral_dir() as venv_dir:
resources = {'': '<a href="letsencrypt/">letsencrypt/</a>',
'letsencrypt/json': dumps({'releases': {'99.9.9': None}})}
resources = {'': '<a href="certbot/">certbot/</a>',
'certbot/json': dumps({'releases': {'99.9.9': None}})}
with serving(resources) as base_url:
# Build a le-auto script embedding a bad requirements file:
install_le_auto(

View file

@ -16,7 +16,7 @@ here = os.path.abspath(os.path.dirname(__file__))
readme = read_file(os.path.join(here, 'README.rst'))
version = '0.6.0.dev0'
version = '0.7.0.dev0'
# This package is a simple shim around certbot-nginx

View file

@ -20,7 +20,7 @@ readme = read_file(os.path.join(here, 'README.rst'))
install_requires = ['certbot']
version = '0.6.0.dev0'
version = '0.7.0.dev0'
setup(

View file

@ -307,5 +307,5 @@ texinfo_documents = [
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'certbot': ('https://letsencrypt.readthedocs.org/en/latest/', None),
'certbot': ('https://certbot.eff.org/docs/', None),
}

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.6.0.dev0'
version = '0.7.0.dev0'
install_requires = [
'setuptools', # pkg_resources

View file

@ -16,7 +16,7 @@ here = os.path.abspath(os.path.dirname(__file__))
readme = read_file(os.path.join(here, 'README.rst'))
version = '0.6.0.dev0'
version = '0.7.0.dev0'
# This package is a simple shim around letshelp-certbot

View file

@ -39,7 +39,7 @@ install_requires = [
'ConfigArgParse>=0.9.3',
'configobj',
'cryptography>=0.7', # load_pem_x509_certificate
'parsedatetime',
'parsedatetime>=1.3', # Calendar.parseDT
'psutil>=2.1.0', # net_connections introduced in 2.1.0
'PyOpenSSL',
'pyrfc3339',
@ -71,6 +71,7 @@ dev_extras = [
'nose',
'nosexcover',
'pep8',
'pkginfo<=1.2.1',
'pylint==1.4.2', # upstream #248
'tox',
'twine',

View file

@ -1,40 +1,10 @@
#!/bin/bash
# Download and run Boulder instance for integration testing
# ugh, go version output is like:
# go version go1.4.2 linux/amd64
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
# version comparison
function verlte {
#OS X doesn't support version sorting; emulate with sed
if [ `uname` == 'Darwin' ]; then
[ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \
| sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ]
else
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
fi
}
if ! verlte 1.5 "$GOVER" ; then
echo "We require go version 1.5 or later; you have... $GOVER"
exit 1
fi
set -xe
# `/...` avoids `no buildable Go source files` errors, for more info
# see `go help packages`
go get -d github.com/letsencrypt/boulder/...
cd $GOPATH/src/github.com/letsencrypt/boulder
# goose is needed for ./test/create_db.sh
wget https://github.com/jsha/boulder-tools/raw/master/goose.gz && \
mkdir $GOPATH/bin && \
zcat goose.gz > $GOPATH/bin/goose && \
chmod +x $GOPATH/bin/goose
./test/create_db.sh
go run cmd/rabbitmq-setup/main.go -server amqp://localhost
# listenbuddy is needed for ./start.py
go get github.com/jsha/listenbuddy
cd -
# Check out special branch until latest docker changes land in Boulder master.
git clone https://github.com/letsencrypt/boulder $BOULDERPATH
cd $BOULDERPATH
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
docker-compose up -d

View file

@ -43,8 +43,8 @@ export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
common auth --csr "$CSR_PATH" \
--cert-path "${root}/csr/cert.pem" \
--chain-path "${root}/csr/chain.pem"
openssl x509 -in "${root}/csr/0000_cert.pem" -text
openssl x509 -in "${root}/csr/0000_chain.pem" -text
openssl x509 -in "${root}/csr/cert.pem" -text
openssl x509 -in "${root}/csr/chain.pem" -text
common --domains le3.wtf install \
--cert-path "${root}/csr/cert.pem" \

View file

@ -2,27 +2,8 @@
# >>>> only tested on Ubuntu 14.04LTS <<<<
# non-interactive install of mariadb and other dependencies
export DEBIAN_FRONTEND=noninteractive
sudo debconf-set-selections <<< 'mariadb-server mysql-server/root_password password PASS'
sudo debconf-set-selections <<< 'mariadb-server mysql-server/root_password_again password PASS'
apt-get -y --no-upgrade install git make libltdl3-dev mariadb-server rabbitmq-server
sudo mysql -uroot -pPASS -e "SET PASSWORD = PASSWORD(\'\');"
# install go
wget https://storage.googleapis.com/golang/go1.5.1.linux-amd64.tar.gz
tar xzvf go1.5.1.linux-amd64.tar.gz
mkdir gocode
echo "export GOROOT=/home/ubuntu/go \n\
export GOPATH=/home/ubuntu/gocode\n\
export PATH=/home/ubuntu/go/bin:/home/ubuntu/gocode/bin:$PATH" >> .bashrc
# install boulder and its go dependencies
go get -d github.com/letsencrypt/boulder/...
cd $GOPATH/src/github.com/letsencrypt/boulder
wget https://github.com/jsha/boulder-tools/raw/master/goose.gz
mkdir $GOPATH/bin
zcat goose.gz > $GOPATH/bin/goose
chmod +x $GOPATH/bin/goose
./test/create_db.sh
go get github.com/jsha/listenbuddy
# Check out special branch until latest docker changes land in Boulder master.
git clone https://github.com/letsencrypt/boulder $BOULDERPATH
cd $BOULDERPATH
sed -i 's/FAKE_DNS: .*/FAKE_DNS: 172.17.42.1/' docker-compose.yml
docker-compose up -d

View file

@ -6,14 +6,9 @@ set -o errexit
source .tox/$TOXENV/bin/activate
export LETSENCRYPT_PATH=`pwd`
until curl http://boulder:4000/directory 2>/dev/null; do
echo waiting for boulder
sleep 1
done
cd $GOPATH/src/github.com/letsencrypt/boulder/
# boulder's integration-test.py has code that knows to start and wait for the
# boulder processes to start reliably and then will run the certbot
# boulder-interation.sh on its own. The --letsencrypt flag says to run only the
# certbot tests (instead of any other client tests it might run). We're
# going to want to define a more robust interaction point between the boulder
# and certbot tests, but that will be better built off of this.
python test/integration-test.py --letsencrypt
./tests/boulder-integration.sh

View file

@ -18,7 +18,8 @@ virtualenv --no-site-packages $VENV_NAME $VENV_ARGS
# Separately install setuptools and pip to make sure following
# invocations use latest
pip install -U setuptools
pip install -U pip
# --force-reinstall used to fix broken pip installation on some systems
pip install --force-reinstall -U pip
pip install "$@"
set +x

View file

@ -45,7 +45,7 @@ export GPG_TTY=$(tty)
PORT=${PORT:-1234}
# subpackages to be released
SUBPKGS=${SUBPKGS:-"acme certbot-apache certbot-nginx letshelp-certbot"}
SUBPKGS=${SUBPKGS:-"acme certbot-apache certbot-nginx letshelp-certbot letsencrypt letsencrypt-apache letsencrypt-nginx letshelp-letsencrypt"}
subpkgs_modules="$(echo $SUBPKGS | sed s/-/_/g)"
# certbot_compatibility_test is not packaged because:
# - it is not meant to be used by anyone else than Certbot devs
@ -145,6 +145,9 @@ pip install \
kill $!
cd ~-
# get a snapshot of the CLI help for the docs
certbot --help all > docs/cli-help.txt
# freeze before installing anything else, so that we know end-user KGS
# make sure "twine upload" doesn't catch "kgs"
if [ -d ../kgs ] ; then
@ -162,12 +165,12 @@ done
deactivate
# pin pip hashes of the things we just built
for pkg in acme certbot certbot-apache ; do
for pkg in acme certbot certbot-apache letsencrypt letsencrypt-apache ; do
echo $pkg==$version \\
pip hash dist."$version/$pkg"/*.{whl,gz} | grep "^--hash" | python2 -c 'from sys import stdin; input = stdin.read(); print " ", input.replace("\n--hash", " \\\n --hash"),'
done > /tmp/hashes.$$
if ! wc -l /tmp/hashes.$$ | grep -qE "^\s*9 " ; then
if ! wc -l /tmp/hashes.$$ | grep -qE "^\s*15 " ; then
echo Unexpected pip hash output
exit 1
fi
@ -187,10 +190,17 @@ while ! openssl dgst -sha256 -verify $RELEASE_OPENSSL_PUBKEY -signature \
read -p "Please correctly sign letsencrypt-auto with offline-signrequest.sh"
done
# This signature is not quite as strong, but easier for people to verify out of band
gpg -u "$RELEASE_GPG_KEY" --detach-sign --armor --sign letsencrypt-auto-source/letsencrypt-auto
# We can't rename the openssl letsencrypt-auto.sig for compatibility reasons,
# but we can use the right name for cerbot-auto.asc from day one
mv letsencrypt-auto-source/letsencrypt-auto.asc letsencrypt-auto-source/certbot-auto.asc
# copy leauto to the root, overwriting the previous release version
cp -p letsencrypt-auto-source/letsencrypt-auto certbot-auto
cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto
git add letsencrypt-auto letsencrypt-auto-source
git add certbot-auto letsencrypt-auto letsencrypt-auto-source docs/cli-help.txt
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"