Merge branch 'master' into dockerfile++

This commit is contained in:
Brad Warren 2017-03-03 12:13:11 -08:00
commit 3d5c0842cb
131 changed files with 3304 additions and 2708 deletions

2
.gitignore vendored
View file

@ -33,3 +33,5 @@ tags
tests/letstest/letest-*/
tests/letstest/*.pem
tests/letstest/venv/
.venv

4
.pep8
View file

@ -1,4 +0,0 @@
[pep8]
# E265 block comment should start with '# '
# E501 line too long (X > 79 characters)
ignore = E265,E501

View file

@ -1,5 +1,8 @@
[MASTER]
# use as many jobs as there are cores
jobs=0
# Specify a configuration file.
#rcfile=

View file

@ -10,10 +10,6 @@ before_install:
# 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:
- BOULDERPATH=$PWD/boulder/
matrix:
include:
- python: "2.7"
@ -87,16 +83,20 @@ matrix:
env: TOXENV=py34
- python: "3.5"
env: TOXENV=py35
- python: "3.6"
env: TOXENV=py36
- python: "2.7"
env: TOXENV=nginxroundtrip
# Only build pushes to the master branch, PRs, and branches beginning with
# `test-`. This reduces the number of simultaneous Travis runs, which speeds
# turnaround time on review since there is a cap of 5 simultaneous runs.
# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of
# simultaneous Travis runs, which speeds turnaround time on review since there
# is a cap of on the number of simultaneous runs.
branches:
only:
- master
- /^\d+\.\d+\.x$/
- /^test-.*$/
# container-based infrastructure

336
CHANGELOG.md Normal file
View file

@ -0,0 +1,336 @@
# 0.12.0
## 03/02/2017
* Allow non-camelcase Apache VirtualHost names
* Allow more log messages to be silenced
* Fix a regression around using `--cert-name` when getting new certificates
More information about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.12.0
# 0.11.1
## 02/01/2017
* Resolve a problem where Certbot would crash while parsing command line
arguments in some cases.
* Fix a typo.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Aclosed
# 0.11.0
## 02/01/2017
* Providing `--quiet` to `certbot-auto` now silences package manager output.
* The UI has been improved in the standalone plugin. When using the
plugin while running Certbot interactively and a required port is bound
by another process, Certbot will give you the option to retry to grab
the port rather than immediately exiting.
* You are now able to deactivate your account with the Let's Encrypt
server using the `unregister` subcommand.
* When revoking a certificate using the `revoke` subcommand, you now
have the option to provide the reason the certificate is being revoked
to Let's Encrypt with `--reason`.
* Removal of the optional `dnspython` dependency in our `acme` package.
Now the library does not support client side verification of the DNS
challenge.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.11.0+is%3Aclosed
# 0.10.2
## 01/25/2017
* We now save `--preferred-challenges` values for renewal. Previously
these values were discarded causing a different challenge type to be
used when renewing certs in some cases.
* If Certbot receives a request with a `badNonce` error, we
automatically retry the request. Since nonces from Let's Encrypt expire,
this helps people performing the DNS challenge with the `manual` plugin
who may have to wait an extended period of time for their DNS changes to
propagate.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.2+is%3Aclosed
# 0.10.1
## 01/13/2017
* Resolve problems where when asking Certbot to update a certificate at
an existing path to include different domain names, the old names would
continue to be used.
* Fix issues successfully running our unit test suite on some systems.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.1+is%3Aclosed
# 0.10.0
## 01/11/2017
* The ability to customize and automatically complete DNS and HTTP
domain validation challenges with the manual plugin. The flags
`--manual-auth-hook` and `--manual-cleanup-hook` can now be provided
when using the manual plugin to execute commands provided by the user to
perform and clean up challenges provided by the CA. This is best used in
complicated setups where the DNS challenge must be used or Certbot's
existing plugins cannot be used to perform HTTP challenges. For more
information on how this works, see `certbot --help manual`.
* A `--cert-name` flag for specifying the name to use for the
certificate in Certbot's configuration directory. Using this flag in
combination with `-d/--domains`, a user can easily request a new
certificate with different domains and save it with the name provided by
`--cert-name`. Additionally, `--cert-name` can be used to select a
certificate with the `certonly` and `run` subcommands so a full list of
domains in the certificate does not have to be provided.
* The subcommand `certificates` for listing the certificates managed by
Certbot and their properties.
* A `delete` subcommand for removing certificates managed by Certbot
from the configuration directory.
* Support for requesting internationalized domain names (IDNs).
* Removal of the ncurses interface. This change solves problems people
were having on many systems, reduces the number of Certbot dependencies,
and simplifies our code. Certbot's only interface now is the text
interface which was available by providing `-t/--text` to earlier
versions of Certbot.
* Hooks provided to Certbot are now saved to be reused during renewal.
If you run Certbot with `--pre-hook`, `--renew-hook`, or `--post-hook`
flags when obtaining a certificate, the provided commands will
automatically be saved and executed again when renewing the certificate.
A pre-hook and/or post-hook can also be given to the `certbot renew`
command either on the command line or in a [configuration
file](https://certbot.eff.org/docs/using.html#configuration-file) to run
an additional command before/after any certificate is renewed. Hooks
will only be run if a certificate is renewed.
* Recategorized `-h/--help` output to improve documentation and
discoverability.
* Busybox support in certbot-auto.
* Many small bug fixes.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.0is%3Aclosed
# 0.9.3
## 10/13/2016
* Adopt more conservative behavior about reporting a needed port as
unavailable when using the standalone plugin.
* The Apache plugin uses information about your OS to help determine the
layout of your Apache configuration directory. We added a patch to
ensure this code behaves the same way when testing on different systems
as the tests were failing in some cases.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/27?closed=1
# 0.9.2
## 10/12/2016
* Ensuring we properly copy `ssl on;` directives as necessary when
performing domain validation in the Nginx plugin.
* Verifying that our optional dependencies version matches what is
required by Certbot.
* A fix for problems where symlinks were becoming files when they were
packaged, causing errors during testing and OS packaging.
* Stop requiring that all possibly required ports are available when
using the standalone plugin. Only verify the ports are available when
you know they are necessary.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/26?closed=1
# 0.9.1
## 10/06/2016
* This version of Certbot simply fixes a bug that was introduced in version
0.9.0 where the command line flag -q/--quiet wasn't respected in some cases.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/25?closed=1
# 0.9.0
## 10/05/2016
* An alpha version of the Nginx plugin. This plugin fully automates the
process of obtaining and installing certificates with Nginx.
Additionally, it is able to automatically configure security
enhancements such as an HTTP to HTTPS redirect and OCSP stapling. To use
this plugin, you must have the `certbot-nginx` package installed (which
is installed automatically when using `certbot-auto`) and provide
`--nginx` on the command line. This plugin is still in its early stages
so we recommend you use it with some caution and make sure you have a
backup of your Nginx configuration.
* Support for the `DNS` challenge in the `acme` library as well as `DNS`
support in Certbot's `manual` plugin. This allows you to create DNS
records to prove to Let's Encrypt you control the requested the domain
name. To use this feature, include `--manual --preferred-challenges dns`
on the command line.
* Help with enabling Extra Packages for Enterprise Linux (EPEL) on
CentOS 6 when using `certbot-auto`. To use `certbot-auto` on CentOS 6,
the EPEL repository has to be enabled. `certbot-auto` will now prompt
users asking them if they would like the script to enable this for them
automatically. This is done without prompting users when using
`letsencrypt-auto` or if `-n/--non-interactive/--noninteractive` is
included on the command line.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed
# 0.8.1
## 06/14/2016
* Preserving a certificate's common name when using `renew`
* Save webroot values for renewal when they are entered interactively
* Problems with an invalid user-agent string on OS X
* Gracefully reporting the Apache plugin isn't usable when Augeas is not installed
* Experimental support for Mageia has been added to `certbot-auto`
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.1+
# 0.8.0
## 06/02/2016
* The main new feature in this release is the `register` subcommand which
can be used to register an account with the Let's Encrypt CA.
* Additionally, you can run `certbot register --update-registration` to
change the e-mail address associated with your registration.
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.0+
# 0.7.0
## 05/27/2016
* `--must-staple` to request certificates from Let's Encrypt with the
OCSP must staple extension
* automatic configuration of OSCP stapling for Apache
* requesting certificates for domains found in the common name of a
custom CSR
* a number of bug fixes
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=milestone%3A0.7.0+is%3Aissue
# 0.6.0
## 05/12/2016
* Renamed the client from `letsencrypt` to `certbot`
* Fixed a small json deserialization error
* Versioned the datetime dependency in setup.py
* Preserve domain order in generated CSRs
* Some minor bug fixes
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.6.0%20is%3Aclosed%20
# 0.5.0
## 04/05/2016
* The ability to use the webroot plugin interactively.
* The flags --pre-hook, --post-hook, and --renew-hook which can be used
with the renew subcommand to register shell commands to run in
response to renewal events. Pre-hook commands will be run before
any certs are renewed, post-hook commands will be run after any
certs are renewed, and renew-hook commands will be run after each
cert is renewed. If no certs are due for renewal, no command is run.
* Cleaner renewal configuration files. In /etc/letsencrypt/renewal by
default, these files can be used to control what parameters are used
when renewing a specific certificate.
* A -q/--quiet flag which silences all output except errors.
* An --allow-subset-of-domains flag which can be used with the renew
command to prevent renewal failures for a subset of the requested
domains from causing the client to exit.
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.5.0+is%3Aissue
# 0.4.2
## 03/03/2016
* Resolves problems encountered when compiling letsencrypt
against the new OpenSSL release.
* A patch fixing problems of using letsencrypt renew with configuration files
from private beta has been added.
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.2
# 0.4.1
## 02/29/2016
* Fixes Apache parsing errors with some configurations
* Fixes Werkzeug dependency problems on some Red Hat systems
* Fixes bootstrapping failures when using letsencrypt-auto with --no-self-upgrade
* Fixes problems with parsing renewal config files from private beta
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=is:issue+milestone:0.4.1
# 0.4.0
## 02/10/2016
* The new verb/subcommand `renew` can be used to renew your existing
certificates as they approach expiration. Running `letsencrypt renew`
will examine all existing certificate lineages and determine if any are
less than 30 days from expiration. If so, the client will use the
settings provided when you previously obtained the certificate to renew
it. The subcommand finishes by printing a summary of which renewals were
successful, failed, or not yet due.
* A `--dry-run` flag has been added to help with testing configuration
without affecting production rate limits. Currently supported by the
`renew` and `certonly` subcommands, providing `--dry-run` on the command
line will obtain certificates from the staging server without saving the
resulting certificates to disk.
* Major improvements have been added to letsencrypt-auto. This script
has been rewritten to include full support for Python 2.6, the ability
for letsencrypt-auto to update itself, and improvements to the
stability, security, and performance of the script.
* Support for Apache 2.2 has been added to the Apache plugin.
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.0
# 0.3.0
## 01/27/2016
* A non-interactive mode which can be enabled by including `-n` or
`--non-interactive` on the command line. This can be used to
guarantee the client will not prompt when run automatically using
cron/systemd.
* Preparation for the new letsencrypt-auto script. Over the past
couple months, we've been working on increasing the reliability and
security of letsencrypt-auto. A number of changes landed in this
release to prepare for the new version of this script.
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.3.0
# 0.2.0
## 01/14/2016
* Apache plugin support for non-Debian based systems. Support has been
added for modern Red Hat based systems such as Fedora 23, Red Hat 7,
and CentOS 7 running Apache 2.4. In theory, this plugin should be
able to be configured to run on any Unix-like OS running Apache 2.4.
* Relaxed PyOpenSSL version requirements. This adds support for systems
with PyOpenSSL versions 0.13 or 0.14.
* Resolves issues with the Apache plugin enabling an HTTP to HTTPS
redirect on some systems.
* Improved error messages from the client.
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.2.0
# 0.1.1
## 12/15/2015
* Fix a confusing UI path that caused some users to repeatedly renew
their certs while experimenting with the client, in some cases
hitting issuance rate limits
* Fixes numerous Apache configuration parser fixes
* Avoids attempting to issue for unqualified domain names like
"localhost"
* Fixes --webroot permission handling for non-root users
More details about these changes can be found on our GitHub repo:
https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.1.1

View file

@ -32,7 +32,7 @@ RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only
# the above is not likely to change, so by putting it further up the
# Dockerfile we make sure we cache as much as possible
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/certbot/src/
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini .pylintrc /opt/certbot/src/
# all above files are necessary for setup.py, however, package source
# code directory has to be copied separately to a subdirectory...
@ -58,7 +58,8 @@ RUN virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
-e /opt/certbot/src/certbot-nginx \
-e /opt/certbot/src/letshelp-certbot \
-e /opt/certbot/src/certbot-compatibility-test \
-e /opt/certbot/src[dev,docs]
-e /opt/certbot/src[dev,docs] && \
/opt/certbot/venv/bin/pip install -U setuptools
# install in editable mode (-e) to save space: it's not possible to
# "rm -rf /opt/certbot/src" (it's stays in the underlaying image);

14
ISSUE_TEMPLATE.md Normal file
View file

@ -0,0 +1,14 @@
## My operating system is (include version):
## I installed Certbot with (certbot-auto, OS package manager, pip, etc):
## I ran this command and it produced this output:
## Certbot's behavior differed from what I expected because:
## Here is a Certbot log showing the issue (if available):
###### Logs are stored in `/var/log/letsencrypt` by default. Feel free to redact domains, e-mail and IP addresses as you see fit.

View file

@ -88,7 +88,7 @@ Main Website: https://certbot.eff.org
Let's Encrypt Website: https://letsencrypt.org
IRC Channel: #letsencrypt on `Freenode`_ or #certbot on `OFTC`_
IRC Channel: #letsencrypt on `Freenode`_
Community: https://community.letsencrypt.org

41
Vagrantfile vendored
View file

@ -1,41 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
# Setup instructions from docs/contributing.rst
# Script installs dependencies for tox and boulder integration
$ubuntu_setup_script = <<SETUP_SCRIPT
cd /vagrant
./letsencrypt-auto-source/letsencrypt-auto --os-packages-only
./tools/venv.sh
wget https://storage.googleapis.com/golang/go1.5.3.linux-amd64.tar.gz -P /tmp/
sudo tar -C /usr/local -xzf /tmp/go1.5.3.linux-amd64.tar.gz
if ! grep -Fxq "export GOROOT=/usr/local/go" /home/vagrant/.profile ; then echo "export GOROOT=/usr/local/go" >> /home/vagrant/.profile; fi
if ! grep -Fxq "export PATH=\\$GOROOT/bin:\\$PATH" /home/vagrant/.profile ; then echo "export PATH=\\$GOROOT/bin:\\$PATH" >> /home/vagrant/.profile; fi
if ! grep -Fxq "export GOPATH=\\$HOME/go" /home/vagrant/.profile ; then echo "export GOPATH=\\$HOME/go" >> /home/vagrant/.profile; fi
if ! grep -Fxq "cd /vagrant/; ./tests/boulder-start.sh &" /etc/rc.local ; then sed -i -e '$i \cd /vagrant/; ./tests/boulder-start.sh &\n' /etc/rc.local; fi
export DEBIAN_FRONTEND=noninteractive
sudo -E apt-get -q -y install git make libltdl-dev mariadb-server rabbitmq-server nginx-light
SETUP_SCRIPT
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define "ubuntu-trusty", primary: true do |ubuntu_trusty|
ubuntu_trusty.vm.box = "ubuntu/trusty64"
ubuntu_trusty.vm.provision "shell", inline: $ubuntu_setup_script
ubuntu_trusty.vm.provider "virtualbox" do |v|
# VM needs more memory to run test suite, got "OSError: [Errno 12]
# Cannot allocate memory" when running
# letsencrypt.client.tests.display.util_test.NcursesDisplayTest
# We may no longer need this.
v.memory = 1024
# Handle cases when the host is behind a private network by making the
# NAT engine use the host's resolver mechanisms to handle DNS requests.
v.customize ["modifyvm", :id, "--natdnshostresolver1", "on"]
end
end
end

View file

@ -1,4 +0,0 @@
[pep8]
# E265 block comment should start with '# '
# E501 line too long (X > 79 characters)
ignore = E265,E501

View file

@ -1,377 +0,0 @@
[MASTER]
# Specify a configuration file.
#rcfile=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# Profiled execution.
profile=no
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=linter_plugin
# Use multiple processes to speed up Pylint.
jobs=1
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=
[MESSAGES CONTROL]
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=fixme,locally-disabled,abstract-class-not-used
# bstract-class-not-used cannot be disabled locally (at least in
# pylint 1.4.1/2)
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=80
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator
# Maximum number of lines in a module
max-module-lines=1000
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
[LOGGING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,logger
[SPELLING]
# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=
# List of comma separated words that should not be checked.
spelling-ignore-words=
# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=
# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
[SIMILARITIES]
# Minimum lines number of a similarity.
min-similarity-lines=4
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
# Ignore imports when computing similarities.
ignore-imports=no
[VARIABLES]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy|unused
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb
[BASIC]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,input
# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_,logger
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,40}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,49}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__|test_[A-Za-z0-9_]*|_.*|.*Test
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
[CLASSES]
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make
[DESIGN]
# Maximum number of arguments for function / method
max-args=6
# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
# Maximum number of locals for function / method body
max-locals=15
# Maximum number of return / yield for function / method body
max-returns=6
# Maximum number of branch for function / method body
max-branches=12
# Maximum number of statements in function / method body
max-statements=50
# Maximum number of parents for a class (see R0901).
max-parents=12
# Maximum number of attributes for a class (see R0902).
max-attributes=7
# Minimum number of public methods for a class (see R0903).
min-public-methods=2
# Maximum number of public methods for a class (see R0904).
max-public-methods=20
[IMPORTS]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception

View file

@ -9,7 +9,6 @@ from cryptography.hazmat.primitives import hashes
import OpenSSL
import requests
from acme import dns_resolver
from acme import errors
from acme import crypto_util
from acme import fields
@ -183,7 +182,7 @@ class KeyAuthorizationChallenge(_TokenChallenge):
Subclasses must implement this method, but they are likely to
return completely different data structures, depending on what's
necessary to complete the challenge. Interepretation of that
necessary to complete the challenge. Interpretation of that
return value must be known to the caller.
:param JWK account_key:
@ -214,36 +213,24 @@ class DNS01Response(KeyAuthorizationChallengeResponse):
def simple_verify(self, chall, domain, account_public_key):
"""Simple verify.
This method no longer checks DNS records and is a simple wrapper
around `KeyAuthorizationChallengeResponse.verify`.
:param challenges.DNS01 chall: Corresponding challenge.
:param unicode domain: Domain name being verified.
:param JWK account_public_key: Public key for the key pair
being authorized.
:returns: ``True`` iff validation with the TXT records resolved from a
DNS server is successful.
:return: ``True`` iff verification of the key authorization was
successful.
:rtype: bool
"""
if not self.verify(chall, account_public_key):
# pylint: disable=unused-argument
verified = self.verify(chall, account_public_key)
if not verified:
logger.debug("Verification of key authorization in response failed")
return False
validation_domain_name = chall.validation_domain_name(domain)
validation = chall.validation(account_public_key)
logger.debug("Verifying %s at %s...", chall.typ, validation_domain_name)
try:
txt_records = dns_resolver.txt_records_for_name(
validation_domain_name)
except errors.DependencyError:
raise errors.DependencyError("Local validation for 'dns-01' "
"challenges requires 'dnspython'")
exists = validation in txt_records
if not exists:
logger.debug("Key authorization from response (%r) doesn't match "
"any DNS response in %r", self.key_authorization,
txt_records)
return exists
return verified
@Challenge.register # pylint: disable=too-many-ancestors
@ -438,7 +425,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
# TODO: domain is not necessary if host is provided
if "host" not in kwargs:
host = socket.gethostbyname(domain)
logging.debug('%s resolved to %s', domain, host)
logger.debug('%s resolved to %s', domain, host)
kwargs["host"] = host
kwargs.setdefault("port", self.PORT)
@ -458,7 +445,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
"""
# pylint: disable=protected-access
sans = crypto_util._pyopenssl_cert_or_req_san(cert)
logging.debug('Certificate %s. SANs: %s', cert.digest('sha1'), sans)
logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), sans)
return self.z_domain.decode() in sans
def simple_verify(self, chall, domain, account_public_key,

View file

@ -10,7 +10,6 @@ from six.moves.urllib import parse as urllib_parse # pylint: disable=import-err
from acme import errors
from acme import jose
from acme import test_util
from acme.dns_resolver import DNS_REQUIREMENT
CERT = test_util.load_comparable_cert('cert.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
@ -92,7 +91,6 @@ class DNS01ResponseTest(unittest.TestCase):
from acme.challenges import DNS01
self.chall = DNS01(token=(b'x' * 16))
self.response = self.chall.response(KEY)
self.records_for_name_path = "acme.dns_resolver.txt_records_for_name"
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
@ -105,45 +103,16 @@ class DNS01ResponseTest(unittest.TestCase):
from acme.challenges import DNS01Response
hash(DNS01Response.from_json(self.jmsg))
def test_simple_verify_bad_key_authorization(self):
def test_simple_verify_failure(self):
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
self.response.simple_verify(self.chall, "local", key2.public_key())
public_key = key2.public_key()
verified = self.response.simple_verify(self.chall, "local", public_key)
self.assertFalse(verified)
@mock.patch('acme.dns_resolver.DNS_AVAILABLE', False)
def test_simple_verify_without_dns(self):
self.assertRaises(
errors.DependencyError, self.response.simple_verify,
self.chall, 'local', KEY.public_key())
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
def test_simple_verify_good_validation(self): # pragma: no cover
with mock.patch(self.records_for_name_path) as mock_resolver:
mock_resolver.return_value = [
self.chall.validation(KEY.public_key())]
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
mock_resolver.assert_called_once_with(
self.chall.validation_domain_name("local"))
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
def test_simple_verify_good_validation_multitxts(self): # pragma: no cover
with mock.patch(self.records_for_name_path) as mock_resolver:
mock_resolver.return_value = [
"!", self.chall.validation(KEY.public_key())]
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
mock_resolver.assert_called_once_with(
self.chall.validation_domain_name("local"))
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
def test_simple_verify_bad_validation(self): # pragma: no cover
with mock.patch(self.records_for_name_path) as mock_resolver:
mock_resolver.return_value = ["!"]
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
def test_simple_verify_success(self):
public_key = KEY.public_key()
verified = self.response.simple_verify(self.chall, "local", public_key)
self.assertTrue(verified)
class DNS01Test(unittest.TestCase):

View file

@ -132,12 +132,22 @@ class Client(object): # pylint: disable=too-many-instance-attributes
"""
update = regr.body if update is None else update
updated_regr = self._send_recv_regr(
regr, body=messages.UpdateRegistration(**dict(update)))
if updated_regr != regr:
raise errors.UnexpectedUpdate(regr)
body = messages.UpdateRegistration(**dict(update))
updated_regr = self._send_recv_regr(regr, body=body)
return updated_regr
def deactivate_registration(self, regr):
"""Deactivate registration.
:param messages.RegistrationResource regr: The Registration Resource
to be deactivated.
:returns: The Registration resource that was deactivated.
:rtype: `.RegistrationResource`
"""
return self.update_registration(regr, update={'status': 'deactivated'})
def query_registration(self, regr):
"""Query server about registration.
@ -289,7 +299,6 @@ class Client(object): # pylint: disable=too-many-instance-attributes
response = self.net.get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri, authzr.new_cert_uri)
# TODO: check and raise UnexpectedUpdate
return updated_authzr, response
def request_issuance(self, csr, authzrs):
@ -481,17 +490,21 @@ class Client(object): # pylint: disable=too-many-instance-attributes
"Recursion limit reached. Didn't get {0}".format(uri))
return chain
def revoke(self, cert):
def revoke(self, cert, rsn):
"""Revoke certificate.
:param .ComparableX509 cert: `OpenSSL.crypto.X509` wrapped in
`.ComparableX509`
:param int rsn: Reason code for certificate revocation.
:raises .ClientError: If revocation is unsuccessful.
"""
response = self.net.post(self.directory[messages.Revocation],
messages.Revocation(certificate=cert),
messages.Revocation(
certificate=cert,
reason=rsn),
content_type=None)
if response.status_code != http_client.OK:
raise errors.ClientError(
@ -604,13 +617,14 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
"""
if method == "POST":
logging.debug('Sending POST request to %s:\n%s',
logger.debug('Sending POST request to %s:\n%s',
url, kwargs['data'])
else:
logging.debug('Sending %s request to %s.', method, url)
logger.debug('Sending %s request to %s.', method, url)
kwargs['verify'] = self.verify_ssl
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('User-Agent', self.user_agent)
kwargs.setdefault('timeout', 45) # timeout after 45 seconds
response = self.session.request(method, url, *args, **kwargs)
# If content is DER, log the base64 of it instead of raw bytes, to keep
# binary data out of the logs.
@ -654,12 +668,27 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
def _get_nonce(self, url):
if not self._nonces:
logging.debug('Requesting fresh nonce')
logger.debug('Requesting fresh nonce')
self._add_nonce(self.head(url))
return self._nonces.pop()
def post(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
"""POST object wrapped in `.JWS` and check response."""
def post(self, *args, **kwargs):
"""POST object wrapped in `.JWS` and check response.
If the server responded with a badNonce error, the request will
be retried once.
"""
try:
return self._post_once(*args, **kwargs)
except messages.Error as error:
if error.code == 'badNonce':
logger.debug('Retrying request after error:\n%s', error)
return self._post_once(*args, **kwargs)
else:
raise
def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
data = self._wrap_in_jws(obj, self._get_nonce(url))
kwargs.setdefault('headers', {'Content-Type': content_type})
response = self._send_request('POST', url, data=data, **kwargs)

View file

@ -81,6 +81,9 @@ class ClientTest(unittest.TestCase):
uri='https://www.letsencrypt-demo.org/acme/cert/1',
cert_chain_uri='https://www.letsencrypt-demo.org/ca')
# Reason code for revocation
self.rsn = 1
def test_init_downloads_directory(self):
uri = 'http://www.letsencrypt-demo.org/directory'
from acme.client import Client
@ -118,8 +121,12 @@ class ClientTest(unittest.TestCase):
# TODO: split here and separate test
self.response.json.return_value = self.regr.body.update(
contact=()).to_json()
self.assertRaises(
errors.UnexpectedUpdate, self.client.update_registration, self.regr)
def test_deactivate_account(self):
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr,
self.client.deactivate_registration(self.regr))
def test_query_registration(self):
self.response.json.return_value = self.regr.body.to_json()
@ -153,7 +160,7 @@ class ClientTest(unittest.TestCase):
self.directory.new_authz,
messages.NewAuthorization(identifier=self.identifier))
def test_requets_challenges_custom_uri(self):
def test_request_challenges_custom_uri(self):
self._prepare_response_for_request_challenges()
self.client.request_challenges(self.identifier, 'URI')
self.net.post.assert_called_once_with('URI', mock.ANY)
@ -371,7 +378,7 @@ class ClientTest(unittest.TestCase):
errors.PollError, self.client.poll_and_request_issuance,
csr, authzrs=(invalid_authzr,), mintime=mintime)
# exceeded max_attemps | TODO: move to a separate test
# exceeded max_attempts | TODO: move to a separate test
self.assertRaises(
errors.PollError, self.client.poll_and_request_issuance,
csr, authzrs, mintime=mintime, max_attempts=2)
@ -427,13 +434,22 @@ class ClientTest(unittest.TestCase):
self.assertRaises(errors.Error, self.client.fetch_chain, self.certr)
def test_revoke(self):
self.client.revoke(self.certr.body)
self.client.revoke(self.certr.body, self.rsn)
self.net.post.assert_called_once_with(
self.directory[messages.Revocation], mock.ANY, content_type=None)
def test_revocation_payload(self):
obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn)
self.assertTrue('reason' in obj.to_partial_json().keys())
self.assertEquals(self.rsn, obj.to_partial_json()['reason'])
def test_revoke_bad_status_raises_error(self):
self.response.status_code = http_client.METHOD_NOT_ALLOWED
self.assertRaises(errors.ClientError, self.client.revoke, self.certr)
self.assertRaises(
errors.ClientError,
self.client.revoke,
self.certr,
self.rsn)
class ClientNetworkTest(unittest.TestCase):
@ -532,7 +548,7 @@ class ClientNetworkTest(unittest.TestCase):
'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')
headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz')
@mock.patch('acme.client.logger')
def test_send_request_get_der(self, mock_logger):
@ -542,8 +558,9 @@ class ClientNetworkTest(unittest.TestCase):
headers={"Content-Type": "application/pkix-cert"},
content=b"hi")
# pylint: disable=protected-access
self.net._send_request('HEAD', 'http://example.com/', 'foo', bar='baz')
mock_logger.debug.assert_called_once_with(
self.net._send_request('HEAD', 'http://example.com/', 'foo',
timeout=mock.ANY, bar='baz')
mock_logger.debug.assert_called_with(
'Received response:\nHTTP %d\n%s\n\n%s', 200,
'Content-Type: application/pkix-cert', b'aGk=')
@ -555,7 +572,7 @@ class ClientNetworkTest(unittest.TestCase):
'POST', 'http://example.com/', 'foo', data='qux', bar='baz'))
self.net.session.request.assert_called_once_with(
'POST', 'http://example.com/', 'foo',
headers=mock.ANY, verify=mock.ANY, data='qux', bar='baz')
headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz')
def test_send_request_verify_ssl(self):
# pylint: disable=protected-access
@ -568,7 +585,8 @@ class ClientNetworkTest(unittest.TestCase):
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)
'GET', 'http://example.com/', verify=verify,
timeout=mock.ANY, headers=mock.ANY)
def test_send_request_user_agent(self):
self.net.session = mock.MagicMock()
@ -577,13 +595,23 @@ class ClientNetworkTest(unittest.TestCase):
headers={'bar': 'baz'})
self.net.session.request.assert_called_once_with(
'GET', 'http://example.com/', verify=mock.ANY,
timeout=mock.ANY,
headers={'User-Agent': 'acme-python-test', 'bar': 'baz'})
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'})
verify=mock.ANY, timeout=mock.ANY, headers={'User-Agent': 'foo2'})
def test_send_request_timeout(self):
self.net.session = mock.MagicMock()
# pylint: disable=protected-access
self.net._send_request('GET', 'http://example.com/',
headers={'bar': 'baz'})
self.net.session.request.assert_called_once_with(
mock.ANY, mock.ANY, verify=mock.ANY, headers=mock.ANY,
timeout=45)
def test_del(self):
sess = mock.MagicMock()
@ -616,7 +644,9 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.wrapped_obj = mock.MagicMock()
self.content_type = mock.sentinel.content_type
self.all_nonces = [jose.b64encode(b'Nonce'), jose.b64encode(b'Nonce2')]
self.all_nonces = [
jose.b64encode(b'Nonce'),
jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')]
self.available_nonces = self.all_nonces[:]
def send_request(*args, **kwargs):
@ -664,7 +694,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.net._wrap_in_jws.assert_called_once_with(
self.obj, jose.b64decode(self.all_nonces.pop()))
assert not self.available_nonces
self.available_nonces = []
self.assertRaises(errors.MissingNonce, self.net.post,
'uri', self.obj, content_type=self.content_type)
self.net._wrap_in_jws.assert_called_with(
@ -680,6 +710,35 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_failed_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = messages.Error.with_code('badNonce')
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_not_retried(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('malformed'),
self.checked_response]
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
self.obj, content_type=self.content_type)
def test_post_successful_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('badNonce'),
self.checked_response]
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertEqual(self.checked_response, self.net.post(
'uri', self.obj, content_type=self.content_type))
def test_head_get_post_error_passthrough(self):
self.send_request.side_effect = requests.exceptions.RequestException
for method in self.net.head, self.net.get:

View file

@ -59,7 +59,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
def test_probe_not_recognized_name(self):
self.assertRaises(errors.Error, self._probe, b'bar')
# TODO: py33/py34 tox hangs forever on do_hendshake in second probe
# TODO: py33/py34 tox hangs forever on do_handshake in second probe
#def probe_connection_error(self):
# self._probe(b'foo')
# #time.sleep(1) # TODO: avoid race conditions in other way

View file

@ -1,45 +0,0 @@
"""DNS Resolver for ACME client.
Required only for local validation of 'dns-01' challenges.
"""
import logging
from acme import errors
from acme import util
DNS_REQUIREMENT = 'dnspython>=1.12'
try:
util.activate(DNS_REQUIREMENT)
# pragma: no cover
import dns.exception
import dns.resolver
DNS_AVAILABLE = True
except errors.DependencyError: # pragma: no cover
DNS_AVAILABLE = False
logger = logging.getLogger(__name__)
def txt_records_for_name(name):
"""Resolve the name and return the TXT records.
:param unicode name: Domain name being verified.
:returns: A list of txt records, if empty the name could not be resolved
:rtype: list of unicode
"""
if not DNS_AVAILABLE:
raise errors.DependencyError(
'{0} is required to use this function'.format(DNS_REQUIREMENT))
try:
dns_response = dns.resolver.query(name, 'TXT')
except dns.resolver.NXDOMAIN as error:
return []
except dns.exception.DNSException as error:
logger.error("Error resolving %s: %s", name, str(error))
return []
return [txt_rec.decode("utf-8") for rdata in dns_response
for txt_rec in rdata.strings]

View file

@ -1,77 +0,0 @@
"""Tests for acme.dns_resolver."""
import unittest
import mock
from six.moves import reload_module # pylint: disable=import-error
from acme import errors
from acme import test_util
from acme.dns_resolver import DNS_REQUIREMENT
if test_util.requirement_available(DNS_REQUIREMENT):
import dns
def create_txt_response(name, txt_records):
"""
Returns an RRSet containing the 'txt_records' as the result of a DNS
query for 'name'.
This takes advantage of the fact that an Answer object mostly behaves
like an RRset.
"""
return dns.rrset.from_text_list(name, 60, "IN", "TXT", txt_records)
class TxtRecordsForNameTest(unittest.TestCase):
"""Tests for acme.dns_resolver.txt_records_for_name."""
@classmethod
def _call(cls, *args, **kwargs):
from acme.dns_resolver import txt_records_for_name
return txt_records_for_name(*args, **kwargs)
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
"optional dependency dnspython is not available")
class TxtRecordsForNameWithDnsTest(TxtRecordsForNameTest):
"""Tests for acme.dns_resolver.txt_records_for_name with dns."""
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_with_single_response(self, mock_dns):
mock_dns.return_value = create_txt_response('name', ['response'])
self.assertEqual(['response'], self._call('name'))
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_with_multiple_responses(self, mock_dns):
mock_dns.return_value = create_txt_response(
'name', ['response1', 'response2'])
self.assertEqual(['response1', 'response2'], self._call('name'))
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_domain_not_found(self, mock_dns):
mock_dns.side_effect = dns.resolver.NXDOMAIN
self.assertEquals([], self._call('name'))
@mock.patch("acme.dns_resolver.dns.resolver.query")
def test_txt_records_for_name_domain_other_error(self, mock_dns):
mock_dns.side_effect = dns.exception.DNSException
self.assertEquals([], self._call('name'))
class TxtRecordsForNameWithoutDnsTest(TxtRecordsForNameTest):
"""Tests for acme.dns_resolver.txt_records_for_name without dns."""
def setUp(self):
from acme import dns_resolver
dns_resolver.DNS_AVAILABLE = False
def tearDown(self):
from acme import dns_resolver
reload_module(dns_resolver)
def test_exception_raised(self):
self.assertRaises(
errors.DependencyError, self._call, "example.org")
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -1,6 +1,6 @@
"""Javascript Object Signing and Encryption (jose).
This package is a Python implementation of the stadards developed by
This package is a Python implementation of the standards developed by
IETF `Javascript Object Signing and Encryption (Active WG)`_, in
particular the following RFCs:

View file

@ -60,7 +60,7 @@ class Field(object):
@classmethod
def _empty(cls, value):
"""Is the provided value cosidered "empty" for this field?
"""Is the provided value considered "empty" for this field?
This is useful for subclasses that might want to override the
definition of being empty, e.g. for some more exotic data types.

View file

@ -111,7 +111,7 @@ class JWK(json_util.TypedJSONObjectWithFields):
try:
key = cls._load_cryptography_key(data, password, backend)
except errors.Error as error:
logger.debug('Loading symmetric key, assymentric failed: %s', error)
logger.debug('Loading symmetric key, asymmetric failed: %s', error)
return JWKOct(key=data)
if cls.typ is not NotImplemented and not isinstance(

View file

@ -250,6 +250,7 @@ class Registration(ResourceBody):
agreement = jose.Field('agreement', omitempty=True)
authorizations = jose.Field('authorizations', omitempty=True)
certificates = jose.Field('certificates', omitempty=True)
status = jose.Field('status', omitempty=True)
class Authorizations(jose.JSONObjectWithFields):
"""Authorizations granted to Account in the process of registration.
@ -469,3 +470,4 @@ class Revocation(jose.JSONObjectWithFields):
resource = fields.Resource(resource_type)
certificate = jose.Field(
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
reason = jose.Field('reason')

View file

@ -26,7 +26,7 @@ class ErrorTest(unittest.TestCase):
'type': ERROR_PREFIX + 'malformed',
}
self.error_custom = Error(typ='custom', detail='bar')
self.jobj_cusom = {'type': 'custom', 'detail': 'bar'}
self.jobj_custom = {'type': 'custom', 'detail': 'bar'}
def test_default_typ(self):
from acme.messages import Error

View file

@ -11,9 +11,7 @@ from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import OpenSSL
from acme import errors
from acme import jose
from acme import util
def vector_path(*names):
@ -78,20 +76,6 @@ def load_pyopenssl_private_key(*names):
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
def requirement_available(requirement):
"""Checks if requirement can be imported.
:rtype: bool
:returns: ``True`` iff requirement can be imported
"""
try:
util.activate(requirement)
except errors.DependencyError: # pragma: no cover
return False
return True # pragma: no cover
def skip_unless(condition, reason): # pragma: no cover
"""Skip tests unless a condition holds.

View file

@ -1,25 +1,7 @@
"""ACME utilities."""
import pkg_resources
import six
from acme import errors
def map_keys(dikt, func):
"""Map dictionary keys."""
return dict((func(key), value) for key, value in six.iteritems(dikt))
def activate(requirement):
"""Make requirement importable.
:param str requirement: the distribution and version to activate
:raises acme.errors.DependencyError: if cannot activate requirement
"""
try:
for distro in pkg_resources.require(requirement): # pylint: disable=not-callable
distro.activate()
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict):
raise errors.DependencyError('{0} is unavailable'.format(requirement))

View file

@ -1,8 +1,6 @@
"""Tests for acme.util."""
import unittest
from acme import errors
class MapKeysTest(unittest.TestCase):
"""Tests for acme.util.map_keys."""
@ -14,21 +12,5 @@ class MapKeysTest(unittest.TestCase):
self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1))
class ActivateTest(unittest.TestCase):
"""Tests for acme.util.activate."""
@classmethod
def _call(cls, *args, **kwargs):
from acme.util import activate
return activate(*args, **kwargs)
def test_failure(self):
self.assertRaises(errors.DependencyError, self._call, 'acme>99.0.0')
def test_success(self):
self._call('acme')
import acme as unused_acme
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.10.0.dev0'
version = '0.13.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@ -15,7 +15,11 @@ install_requires = [
'PyOpenSSL>=0.13',
'pyrfc3339',
'pytz',
'requests[security]>=2.4.1', # security extras added in 2.4.1
# requests>=2.10 is required to fix
# https://github.com/shazow/urllib3/issues/556. This requirement can be
# relaxed to 'requests[security]>=2.4.1', however, less useful errors
# will be raised for some network/SSL errors.
'requests[security]>=2.10',
# For pkg_resources. >=1.0 so pip resolves it to a version cryptography
# will tolerate; see #2599:
'setuptools>=1.0',
@ -33,14 +37,8 @@ if sys.version_info < (2, 7):
else:
install_requires.append('mock')
# dnspython 1.12 is required to support both Python 2 and Python 3.
dns_extras = [
'dnspython>=1.12',
]
dev_extras = [
'nose',
'pep8',
'tox',
]
@ -78,7 +76,6 @@ setup(
include_package_data=True,
install_requires=install_requires,
extras_require={
'dns': dns_extras,
'dev': dev_extras,
'docs': docs_extras,
},

View file

@ -472,7 +472,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"\n\nUnfortunately mod_macro is not yet supported".format(
"\n ".join(vhost_macro)), force_interactive=True)
return all_names
return util.get_filtered_names(all_names)
def get_name_from_ip(self, addr): # pylint: disable=no-self-use
"""Returns a reverse dns name if available.
@ -580,7 +580,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
("/files%s//*[label()=~regexp('%s')]" %
(vhost_path, parser.case_i("VirtualHost"))))
paths = [path for path in paths if
os.path.basename(path) == "VirtualHost"]
os.path.basename(path.lower()) == "virtualhost"]
for path in paths:
new_vhost = self._create_vhost(path)
if not new_vhost:
@ -1315,18 +1315,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# even with save() and load()
if not self._is_rewrite_engine_on(general_vh):
self.parser.add_dir(general_vh.path, "RewriteEngine", "on")
names = ssl_vhost.get_names()
for idx, name in enumerate(names):
args = ["%{SERVER_NAME}", "={0}".format(name), "[OR]"]
if idx == len(names) - 1:
args.pop()
self.parser.add_dir(general_vh.path, "RewriteCond", args)
if self.get_version() >= (2, 3, 9):
self.parser.add_dir(general_vh.path, "RewriteRule",
constants.REWRITE_HTTPS_ARGS_WITH_END)
else:
self.parser.add_dir(general_vh.path, "RewriteRule",
constants.REWRITE_HTTPS_ARGS)
self._set_https_redirection_rewrite_rule(general_vh)
self.save_notes += ("Redirecting host in %s to ssl vhost in %s\n" %
(general_vh.filep, ssl_vhost.filep))
@ -1336,12 +1333,24 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logger.info("Redirecting vhost in %s to ssl vhost in %s",
general_vh.filep, ssl_vhost.filep)
def _set_https_redirection_rewrite_rule(self, vhost):
if self.get_version() >= (2, 3, 9):
self.parser.add_dir(vhost.path, "RewriteRule",
constants.REWRITE_HTTPS_ARGS_WITH_END)
else:
self.parser.add_dir(vhost.path, "RewriteRule",
constants.REWRITE_HTTPS_ARGS)
def _verify_no_certbot_redirect(self, vhost):
"""Checks to see if a redirect was already installed by certbot.
Checks to see if virtualhost already contains a rewrite rule that is
identical to Certbot's redirection rewrite rule.
For graceful transition to new rewrite rules for HTTPS redireciton we
delete certbot's old rewrite rules and set the new one instead.
:param vhost: vhost to check
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
@ -1355,19 +1364,29 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# rewrite_args_dict keys are directive ids and the corresponding value
# for each is a list of arguments to that directive.
rewrite_args_dict = defaultdict(list)
pat = r'.*(directive\[\d+\]).*'
pat = r'(.*directive\[\d+\]).*'
for match in rewrite_path:
m = re.match(pat, match)
if m:
dir_id = m.group(1)
rewrite_args_dict[dir_id].append(match)
dir_path = m.group(1)
rewrite_args_dict[dir_path].append(match)
if rewrite_args_dict:
redirect_args = [constants.REWRITE_HTTPS_ARGS,
constants.REWRITE_HTTPS_ARGS_WITH_END]
for matches in rewrite_args_dict.values():
if [self.aug.get(x) for x in matches] in redirect_args:
for dir_path, args_paths in rewrite_args_dict.items():
arg_vals = [self.aug.get(x) for x in args_paths]
# Search for past redirection rule, delete it, set the new one
if arg_vals in constants.OLD_REWRITE_HTTPS_ARGS:
self.aug.remove(dir_path)
self._set_https_redirection_rewrite_rule(vhost)
self.save()
raise errors.PluginEnhancementAlreadyPresent(
"Certbot has already enabled redirection")
if arg_vals in redirect_args:
raise errors.PluginEnhancementAlreadyPresent(
"Certbot has already enabled redirection")
@ -1807,7 +1826,7 @@ def get_file_path(vhost_path):
else:
return None
except AttributeError:
# If we recieved a None path
# If we received a None path
return None
last_good = ""

View file

@ -136,15 +136,19 @@ AUGEAS_LENS_DIR = pkg_resources.resource_filename(
"""Path to the Augeas lens directory"""
REWRITE_HTTPS_ARGS = [
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"]
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,NE,R=permanent]"]
"""Apache version<2.3.9 rewrite rule arguments used for redirections to
https vhost"""
REWRITE_HTTPS_ARGS_WITH_END = [
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"]
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,NE,R=permanent]"]
"""Apache version >= 2.3.9 rewrite rule arguments used for redirections to
https vhost"""
OLD_REWRITE_HTTPS_ARGS = [
["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"],
["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"]]
HSTS_ARGS = ["always", "set", "Strict-Transport-Security",
"\"max-age=31536000\""]
"""Apache header arguments for HSTS"""

View file

@ -8,7 +8,7 @@ class Addr(common.Addr):
"""Represents an Apache address."""
def __eq__(self, other):
"""This is defined as equalivalent within Apache.
"""This is defined as equivalent within Apache.
ip_addr:* == ip_addr
@ -25,6 +25,11 @@ class Addr(common.Addr):
def __repr__(self):
return "certbot_apache.obj.Addr(" + repr(self.tup) + ")"
def __hash__(self):
# Python 3 requires explicit overridden for __hash__ if __eq__ or
# __cmp__ is overridden. See https://bugs.python.org/issue2235
return super(Addr, self).__hash__()
def _addr_less_specific(self, addr):
"""Returns if addr.get_addr() is more specific than self.get_addr()."""
# pylint: disable=protected-access
@ -174,6 +179,11 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.filep, self.path,
tuple(self.addrs), tuple(self.get_names()),
self.ssl, self.enabled, self.modmacro))
def conflicts(self, addrs):
"""See if vhost conflicts with any of the addrs.

View file

@ -1,10 +1,12 @@
"""ApacheParser is a member object of the ApacheConfigurator class."""
import fnmatch
import itertools
import logging
import os
import re
import subprocess
import sys
import six
from certbot import errors
@ -87,7 +89,7 @@ class ApacheParser(object):
while len(self.modules) != prev_size:
prev_size = len(self.modules)
for match_name, match_filename in itertools.izip(
for match_name, match_filename in six.moves.zip(
iterator, iterator):
self.modules.add(self.get_arg(match_name))
self.modules.add(
@ -460,8 +462,12 @@ class ApacheParser(object):
:rtype: str
"""
# This strips off final /Z(?ms)
return fnmatch.translate(clean_fn_match)[:-7]
if sys.version_info < (3, 6):
# This strips off final /Z(?ms)
return fnmatch.translate(clean_fn_match)[:-7]
else: # pragma: no cover
# Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z
return fnmatch.translate(clean_fn_match)[4:-3]
def _parse_file(self, filepath):
"""Parse file with Augeas

View file

@ -263,7 +263,7 @@
#
# Set the following policy settings here and they will be propagated to the 30 rules
# file (modsecurity_crs_30_http_policy.conf) by using macro expansion.
# If you run into false positves, you can adjust the settings here.
# If you run into false positives, you can adjust the settings here.
#
#SecAction \
"id:'900012', \
@ -349,7 +349,7 @@
#
# -- [[ Check UTF enconding ]] -----------------------------------------------------------
# -- [[ Check UTF encoding ]] -----------------------------------------------------------
#
# We only want to apply this check if UTF-8 encoding is actually used by the site, otherwise
# it will result in false positives.

View file

@ -6,6 +6,8 @@ import socket
import unittest
import mock
# six is used in mock.patch()
import six # pylint: disable=unused-import
from acme import challenges
@ -16,6 +18,7 @@ from certbot.tests import acme_util
from certbot.tests import util as certbot_util
from certbot_apache import configurator
from certbot_apache import constants
from certbot_apache import parser
from certbot_apache import obj
@ -103,8 +106,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", "ocspvhost.com", "encryption-example.demo",
"ip-172-30-0-17", "*.blue.purple.com"]))
["certbot.demo", "ocspvhost.com", "encryption-example.demo"]
))
@certbot_util.patch_get_utility()
@mock.patch("certbot_apache.configurator.socket.gethostbyaddr")
@ -123,7 +126,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.vhosts.append(vhost)
names = self.config.get_all_names()
self.assertEqual(len(names), 7)
# Names get filtered, only 5 are returned
self.assertEqual(len(names), 5)
self.assertTrue("zombo.com" in names)
self.assertTrue("google.com" in names)
self.assertTrue("certbot.demo" in names)
@ -516,12 +520,12 @@ class MultipleVhostsTest(util.ApacheTest):
# Test
self.config.prepare_server_https("8080", temp=True)
self.assertEqual(mock_add_dir.call_count, 3)
self.assertEqual(mock_add_dir.call_args_list[0][0][2],
["1.2.3.4:8080", "https"])
self.assertEqual(mock_add_dir.call_args_list[1][0][2],
["[::1]:8080", "https"])
self.assertEqual(mock_add_dir.call_args_list[2][0][2],
["1.1.1.1:8080", "https"])
call_args_list = [mock_add_dir.call_args_list[i][0][2] for i in range(3)]
self.assertEqual(
sorted(call_args_list),
sorted([["1.2.3.4:8080", "https"],
["[::1]:8080", "https"],
["1.1.1.1:8080", "https"]]))
# mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"]
# mock_find.return_value = ["test1", "test2", "test3"]
@ -661,7 +665,7 @@ class MultipleVhostsTest(util.ApacheTest):
# This calls open
self.config.reverter.register_file_creation = mock.Mock()
mock_open.side_effect = IOError
with mock.patch("__builtin__.open", mock_open):
with mock.patch("six.moves.builtins.open", mock_open):
self.assertRaises(
errors.PluginError,
self.config.make_vhost_ssl, self.vh_truth[0])
@ -1044,6 +1048,36 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue("rewrite_module" in self.config.parser.modules)
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_with_old_https_redirection(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, 0))
ssl_vhost = self.config.choose_vhost("certbot.demo")
# pylint: disable=protected-access
http_vhost = self.config._get_http_vhost(ssl_vhost)
# Create an old (previously suppoorted) https redirectoin rewrite rule
self.config.parser.add_dir(
http_vhost.path, "RewriteRule",
["^",
"https://%{SERVER_NAME}%{REQUEST_URI}",
"[L,QSA,R=permanent]"])
self.config.save()
try:
self.config.enhance("certbot.demo", "redirect")
except errors.PluginEnhancementAlreadyPresent:
args_paths = self.config.parser.find_dir(
"RewriteRule", None, http_vhost.path, False)
arg_vals = [self.config.aug.get(x) for x in args_paths]
self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS)
def test_redirect_with_conflict(self):
self.config.parser.modules.add("rewrite_module")
ssl_vh = obj.VirtualHost(
@ -1131,7 +1165,7 @@ class MultipleVhostsTest(util.ApacheTest):
http_vhost.path, "RewriteRule",
["^",
"https://%{SERVER_NAME}%{REQUEST_URI}",
"[L,QSA,R=permanent]"])
"[L,NE,R=permanent]"])
self.config.save()
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
@ -1142,7 +1176,7 @@ class MultipleVhostsTest(util.ApacheTest):
conf_text = open(ssl_vhost.filep).read()
commented_rewrite_rule = ("# RewriteRule ^ "
"https://%{SERVER_NAME}%{REQUEST_URI} "
"[L,QSA,R=permanent]")
"[L,NE,R=permanent]")
self.assertTrue(commented_rewrite_rule in conf_text)
mock_get_utility().add_message.assert_called_once_with(mock.ANY,
@ -1161,7 +1195,7 @@ class MultipleVhostsTest(util.ApacheTest):
"RewriteCond", ["%{DOCUMENT_ROOT}/%{REQUEST_FILENAME}", "!-f"])
self.config.parser.add_dir(
http_vhost.path, "RewriteRule",
["^(.*)$", "b://u%{REQUEST_URI}", "[P,QSA,L]"])
["^(.*)$", "b://u%{REQUEST_URI}", "[P,NE,L]"])
# Add a chunk that should be commented out.
self.config.parser.add_dir(http_vhost.path,
@ -1172,7 +1206,7 @@ class MultipleVhostsTest(util.ApacheTest):
http_vhost.path, "RewriteRule",
["^",
"https://%{SERVER_NAME}%{REQUEST_URI}",
"[L,QSA,R=permanent]"])
"[L,NE,R=permanent]"])
self.config.save()
@ -1183,13 +1217,13 @@ class MultipleVhostsTest(util.ApacheTest):
not_commented_cond1 = ("RewriteCond "
"%{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f")
not_commented_rewrite_rule = ("RewriteRule "
"^(.*)$ b://u%{REQUEST_URI} [P,QSA,L]")
"^(.*)$ b://u%{REQUEST_URI} [P,NE,L]")
commented_cond1 = "# RewriteCond %{HTTPS} !=on"
commented_cond2 = "# RewriteCond %{HTTPS} !^$"
commented_rewrite_rule = ("# RewriteRule ^ "
"https://%{SERVER_NAME}%{REQUEST_URI} "
"[L,QSA,R=permanent]")
"[L,NE,R=permanent]")
self.assertTrue(not_commented_cond1 in conf_line_set)
self.assertTrue(not_commented_rewrite_rule in conf_line_set)
@ -1207,13 +1241,13 @@ class MultipleVhostsTest(util.ApacheTest):
achall1 = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.chall_to_challb(
challenges.TLSSNI01(
token="jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"),
token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"),
"pending"),
domain="encryption-example.demo", account_key=account_key)
achall2 = achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.chall_to_challb(
challenges.TLSSNI01(
token="uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"),
token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"),
"pending"),
domain="certbot.demo", account_key=account_key)

View file

@ -38,7 +38,7 @@ class SelectVhostTest(unittest.TestCase):
try:
self._call(self.vhosts)
except errors.MissingCommandlineFlag as e:
self.assertTrue("vhost ambiguity" in e.message)
self.assertTrue("vhost ambiguity" in str(e))
@certbot_util.patch_get_utility()
def test_more_info_cancel(self, mock_util):

View file

@ -178,7 +178,7 @@ class ParserInitTest(util.ApacheTest):
shutil.rmtree(self.work_dir)
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
def test_unparsable(self, mock_cfg):
def test_unparseable(self, mock_cfg):
from certbot_apache.parser import ApacheParser
mock_cfg.return_value = ('Define: TEST')
self.assertRaises(

View file

@ -105,7 +105,7 @@ class TlsSniPerformTest(util.ApacheTest):
for achall in self.achalls:
self.sni.add_chall(achall)
z_domain = achall.response(self.auth_key).z_domain
z_domains.append(set([z_domain]))
z_domains.append(set([z_domain.decode('ascii')]))
self.sni._mod_config() # pylint: disable=protected-access
self.sni.configurator.save()

View file

@ -177,14 +177,14 @@ class ApacheTlsSni01(common.TLSSNI01):
ips = " ".join(str(i) for i in ip_addrs)
document_root = os.path.join(
self.configurator.config.work_dir, "tls_sni_01_page/")
# TODO: Python docs is not clear how mutliline string literal
# TODO: Python docs is not clear how multiline string literal
# newlines are parsed on different platforms. At least on
# Linux (Debian sid), when source file uses CRLF, Python still
# parses it as "\n"... c.f.:
# https://docs.python.org/2.7/reference/lexical_analysis.html
return self.VHOST_TEMPLATE.format(
vhost=ips,
server_name=achall.response(achall.account_key).z_domain,
server_name=achall.response(achall.account_key).z_domain.decode('ascii'),
ssl_options_conf_path=self.configurator.mod_ssl_conf,
cert_path=self.get_cert_path(achall),
key_path=self.get_key_path(achall),

View file

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

View file

@ -15,11 +15,15 @@ set -e # Work even if somebody does "sh thisscript.sh".
# Note: you can set XDG_DATA_HOME or VENV_PATH before running this script,
# if you want to change where the virtual environment will be installed
XDG_DATA_HOME=${XDG_DATA_HOME:-~/.local/share}
if [ -z "$XDG_DATA_HOME" ]; then
XDG_DATA_HOME=~/.local/share
fi
VENV_NAME="letsencrypt"
VENV_PATH=${VENV_PATH:-"$XDG_DATA_HOME/$VENV_NAME"}
if [ -z "$VENV_PATH" ]; then
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
fi
VENV_BIN="$VENV_PATH/bin"
LE_AUTO_VERSION="0.9.3"
LE_AUTO_VERSION="0.12.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -34,8 +38,9 @@ Help for certbot itself cannot be provided until it is installed.
-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
-q, --quiet provide only update/error output
-v, --verbose provide more output
-q, --quiet provide only update/error output;
implies --non-interactive
All arguments are accepted and forwarded to the Certbot client when run."
@ -58,6 +63,7 @@ for arg in "$@" ; do
--verbose)
VERBOSE=1;;
-[!-]*)
OPTIND=1
while getopts ":hnvq" short_arg $arg; do
case "$short_arg" in
h)
@ -79,43 +85,79 @@ if [ $BASENAME = "letsencrypt-auto" ]; then
HELP=0
fi
# Set ASSUME_YES to 1 if QUIET (i.e. --quiet implies --non-interactive)
if [ "$QUIET" = 1 ]; then
ASSUME_YES=1
fi
# Support for busybox and others where there is no "command",
# but "which" instead
if command -v command > /dev/null 2>&1 ; then
export EXISTS="command -v"
elif which which > /dev/null 2>&1 ; then
export EXISTS="which"
else
echo "Cannot find command nor which... please install one!"
exit 1
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`
# `su`. Auto-detection can be overridden by explicitly setting the
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
# Because the parameters in `su -c` has to be a string,
# we need to properly escape it.
su_sudo() {
args=""
# This `while` loop iterates over all parameters given to this function.
# For each parameter, all `'` will be replace by `'"'"'`, and the escaped string
# will be wrapped in a pair of `'`, then appended to `$args` string
# For example, `echo "It's only 1\$\!"` will be escaped to:
# 'echo' 'It'"'"'s only 1$!'
# │ │└┼┘│
# │ │ │ └── `'s only 1$!'` the literal string
# │ │ └── `\"'\"` is a single quote (as a string)
# │ └── `'It'`, to be concatenated with the strings following it
# └── `echo` wrapped in a pair of `'`, it's totally fine for the shell command itself
while [ $# -ne 0 ]; do
args="$args'$(printf "%s" "$1" | sed -e "s/'/'\"'\"'/g")' "
shift
done
su root -c "$args"
}
SUDO_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,
# we need properly escape it
su_sudo() {
args=""
# This `while` loop iterates over all parameters given to this function.
# For each parameter, all `'` will be replace by `'"'"'`, and the escaped string
# will be wrapped in a pair of `'`, then appended to `$args` string
# For example, `echo "It's only 1\$\!"` will be escaped to:
# 'echo' 'It'"'"'s only 1$!'
# │ │└┼┘│
# │ │ │ └── `'s only 1$!'` the literal string
# │ │ └── `\"'\"` is a single quote (as a string)
# │ └── `'It'`, to be concatenated with the strings following it
# └── `echo` wrapped in a pair of `'`, it's totally fine for the shell command itself
while [ $# -ne 0 ]; do
args="$args'$(printf "%s" "$1" | sed -e "s/'/'\"'\"'/g")' "
shift
done
su root -c "$args"
}
SUDO=su_sudo
fi
if [ -n "${LE_AUTO_SUDO+x}" ]; then
case "$LE_AUTO_SUDO" in
su_sudo|su)
SUDO=su_sudo
;;
sudo)
SUDO=sudo
SUDO_ENV="CERTBOT_AUTO=$0"
;;
'') ;; # Nothing to do for plain root method.
*)
echo "Error: unknown root authorization mechanism '$LE_AUTO_SUDO'."
exit 1
esac
echo "Using preset root authorization mechanism '$LE_AUTO_SUDO'."
else
SUDO=
if test "`id -u`" -ne "0" ; then
if $EXISTS 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...
SUDO=su_sudo
fi
else
SUDO=
fi
fi
ExperimentalBootstrap() {
@ -136,7 +178,7 @@ ExperimentalBootstrap() {
DeterminePythonVersion() {
for LE_PYTHON in "$LE_PYTHON" python2.7 python27 python2 python; do
# Break (while keeping the LE_PYTHON value) if found.
command -v "$LE_PYTHON" > /dev/null && break
$EXISTS "$LE_PYTHON" > /dev/null && break
done
if [ "$?" != "0" ]; then
echo "Cannot find any Pythons; please install one!"
@ -171,14 +213,21 @@ BootstrapDebCommon() {
#
# - Debian 6.0.10 "squeeze" (x64)
$SUDO apt-get update || echo apt-get update hit problems but continuing anyway...
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='-qq'
fi
$SUDO apt-get $QUIET_FLAG update || echo apt-get update hit problems but continuing anyway...
# virtualenv binary can be found in different packages depending on
# distro version (#346)
virtualenv=
if apt-cache show virtualenv > /dev/null 2>&1 && ! apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
# virtual env is known to apt and is installable
if apt-cache show virtualenv > /dev/null 2>&1 ; then
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
virtualenv="virtualenv"
fi
fi
if apt-cache show python-virtualenv > /dev/null 2>&1; then
@ -186,77 +235,76 @@ BootstrapDebCommon() {
fi
augeas_pkg="libaugeas0 augeas-lenses"
AUGVERSION=`apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
AUGVERSION=`LC_ALL=C 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
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
fi
fi
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
# 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
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 $QUIET_FLAG update
fi
fi
fi
if [ "$add_backports" != 0 ]; then
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
augeas_pkg=
fi
}
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
if lsb_release -a | grep -q wheezy ; then
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
elif lsb_release -a | grep -q precise ; then
# XXX add ARM case
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
if lsb_release -a | grep -q wheezy ; then
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
elif lsb_release -a | grep -q precise ; then
# XXX add ARM case
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
else
echo "No libaugeas0 version is available that's new enough to run the"
echo "Certbot apache plugin..."
fi
# XXX add a case for ubuntu PPAs
fi
$SUDO apt-get install $YES_FLAG --no-install-recommends \
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \
python \
python-dev \
$virtualenv \
gcc \
dialog \
$augeas_pkg \
libssl-dev \
openssl \
libffi-dev \
ca-certificates \
if ! command -v virtualenv > /dev/null ; then
if ! $EXISTS virtualenv > /dev/null ; then
echo Failed to install a working \"virtualenv\" command, exiting
exit 1
fi
@ -284,6 +332,9 @@ BootstrapRpmCommon() {
if [ "$ASSUME_YES" = 1 ]; then
yes_flag="-y"
fi
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='--quiet'
fi
if ! $SUDO $tool list *virtualenv >/dev/null 2>&1; then
echo "To use Certbot, packages from the EPEL repository need to be installed."
@ -292,14 +343,14 @@ BootstrapRpmCommon() {
exit 1
fi
if [ "$ASSUME_YES" = 1 ]; then
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
sleep 1s
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
sleep 1s
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
sleep 1s
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
sleep 1s
fi
if ! $SUDO $tool install $yes_flag epel-release; then
if ! $SUDO $tool install $yes_flag $QUIET_FLAG epel-release; then
echo "Could not enable EPEL. Aborting bootstrap!"
exit 1
fi
@ -307,7 +358,6 @@ BootstrapRpmCommon() {
pkgs="
gcc
dialog
augeas-libs
openssl
openssl-devel
@ -342,9 +392,9 @@ BootstrapRpmCommon() {
"
fi
if ! $SUDO $tool install $yes_flag $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
if ! $SUDO $tool install $yes_flag $QUIET_FLAG $pkgs; then
echo "Could not install OS dependencies. Aborting bootstrap!"
exit 1
fi
}
@ -356,12 +406,15 @@ BootstrapSuseCommon() {
install_flags="-l"
fi
$SUDO zypper $zypper_flags in $install_flags \
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='-qq'
fi
$SUDO zypper $QUIET_FLAG $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \
gcc \
dialog \
augeas-lenses \
libopenssl-devel \
libffi-devel \
@ -380,7 +433,6 @@ BootstrapArchCommon() {
python2
python-virtualenv
gcc
dialog
augeas
openssl
libffi
@ -396,7 +448,11 @@ BootstrapArchCommon() {
fi
if [ "$missing" ]; then
$SUDO pacman -S --needed $missing $noconfirm
if [ "$QUIET" = 1]; then
$SUDO pacman -S --needed $missing $noconfirm > /dev/null
else
$SUDO pacman -S --needed $missing $noconfirm
fi
fi
}
@ -404,28 +460,36 @@ BootstrapGentooCommon() {
PACKAGES="
dev-lang/python:2.7
dev-python/virtualenv
dev-util/dialog
app-admin/augeas
dev-libs/openssl
dev-libs/libffi
app-misc/ca-certificates
virtual/pkgconfig"
ASK_OPTION="--ask"
if [ "$ASSUME_YES" = 1 ]; then
ASK_OPTION=""
fi
case "$PACKAGE_MANAGER" in
(paludis)
$SUDO cave resolve --preserve-world --keep-targets if-possible $PACKAGES -x
;;
(pkgcore)
$SUDO pmerge --noreplace --oneshot $PACKAGES
$SUDO pmerge --noreplace --oneshot $ASK_OPTION $PACKAGES
;;
(portage|*)
$SUDO emerge --noreplace --oneshot $PACKAGES
$SUDO emerge --noreplace --oneshot $ASK_OPTION $PACKAGES
;;
esac
}
BootstrapFreeBsd() {
$SUDO pkg install -Ay \
if [ "$QUIET" = 1 ]; then
QUIET_FLAG="--quiet"
fi
$SUDO pkg install -Ay $QUIET_FLAG \
python \
py27-virtualenv \
augeas \
@ -449,7 +513,6 @@ BootstrapMac() {
fi
$pkgcmd augeas
$pkgcmd dialog
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.
@ -458,7 +521,7 @@ BootstrapMac() {
$pkgcmd python
fi
# Workaround for _dlopen not finding augeas on OS X
# Workaround for _dlopen not finding augeas on macOS
if [ "$pkgman" = "port" ] && ! [ -e "/usr/local/lib/libaugeas.dylib" ] && [ -e "/opt/local/lib/libaugeas.dylib" ]; then
echo "Applying augeas workaround"
$SUDO mkdir -p /usr/local/lib/
@ -466,15 +529,15 @@ BootstrapMac() {
fi
if ! hash pip 2>/dev/null; then
echo "pip not installed"
echo "Installing pip..."
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
echo "pip not installed"
echo "Installing pip..."
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
fi
if ! hash virtualenv 2>/dev/null; then
echo "virtualenv not installed."
echo "Installing with pip..."
pip install virtualenv
echo "virtualenv not installed."
echo "Installing with pip..."
pip install virtualenv
fi
}
@ -484,26 +547,29 @@ BootstrapSmartOS() {
}
BootstrapMageiaCommon() {
if ! $SUDO urpmi --force \
python \
libpython-devel \
python-virtualenv
if [ "$QUIET" = 1 ]; then
QUIET_FLAG='--quiet'
fi
if ! $SUDO urpmi --force $QUIET_FLAG \
python \
libpython-devel \
python-virtualenv
then
echo "Could not install Python dependencies. Aborting bootstrap!"
exit 1
fi
fi
if ! $SUDO urpmi --force \
git \
gcc \
cdialog \
python-augeas \
libopenssl-devel \
libffi-devel \
rootcerts
if ! $SUDO urpmi --force $QUIET_FLAG \
git \
gcc \
python-augeas \
libopenssl-devel \
libffi-devel \
rootcerts
then
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
echo "Could not install additional dependencies. Aborting bootstrap!"
exit 1
fi
}
@ -541,7 +607,7 @@ Bootstrap() {
elif uname | grep -iq FreeBSD ; then
ExperimentalBootstrap "FreeBSD" BootstrapFreeBsd
elif uname | grep -iq Darwin ; then
ExperimentalBootstrap "Mac OS X" BootstrapMac
ExperimentalBootstrap "macOS" BootstrapMac
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
@ -557,7 +623,7 @@ Bootstrap() {
}
TempDir() {
mktemp -d 2>/dev/null || mktemp -d -t 'le' # Linux || OS X
mktemp -d 2>/dev/null || mktemp -d -t 'le' # Linux || macOS
}
@ -570,6 +636,11 @@ if [ "$1" = "--le-auto-phase2" ]; then
# --version output ran through grep due to python-cryptography DeprecationWarnings
# 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)
if [ -z "$INSTALLED_VERSION" ]; then
echo "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2
"$VENV_BIN/letsencrypt" --version
exit 1
fi
else
INSTALLED_VERSION="none"
fi
@ -594,6 +665,11 @@ if [ "$1" = "--le-auto-phase2" ]; then
# `pip install --no-cache-dir -e acme -e . -e certbot-apache -e certbot-nginx`,
# and then use `hashin` or a more secure method to gather the hashes.
# Hashin example:
# pip install hashin
# hashin -r letsencrypt-auto-requirements.txt cryptography==1.5.2
# sets the new certbot-auto pinned version of cryptography to 1.5.2
argparse==1.4.0 \
--hash=sha256:c31647edb69fd3d465a847ea3157d37bed1f95f19760b11a47aa91c04b666314 \
--hash=sha256:62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4
@ -601,7 +677,8 @@ argparse==1.4.0 \
# This comes before cffi because cffi will otherwise install an unchecked
# version via setup_requires.
pycparser==2.14 \
--hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73
--hash=sha256:7959b4a74abdc27b312fed1c21e6caf9309ce0b29ea86b591fd2e99ecdf27f73 \
--no-binary pycparser
cffi==1.4.2 \
--hash=sha256:53c1c9ddb30431513eb7f3cdef0a3e06b0f1252188aaa7744af0f5a4cd45dbaf \
@ -624,29 +701,29 @@ ConfigArgParse==0.10.0 \
--hash=sha256:3b50a83dd58149dfcee98cb6565265d10b53e9c0a2bca7eeef7fb5f5524890a7
configobj==5.0.6 \
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902
cryptography==1.3.4 \
--hash=sha256:bede00edd11a2a62c8c98c271cc103fa3a3d72acf64f6e5e4eaf251128897b17 \
--hash=sha256:53b39e687b744bb548a98f40736cc529d9f60959b4e6cc551322cf9505d35eb3 \
--hash=sha256:474b73ad1139b4e423e46bbd818efd0d5c0df1c65d9f7c957d64c9215d77afde \
--hash=sha256:aaddf9592d5b99e32dd518bb4a25b147c124f9d6b4ad64b94f01b15d1666b8c8 \
--hash=sha256:6dcad2f407db8c3cd6ecd78361439c449a4f94786b46c54507e7e68f51e1709d \
--hash=sha256:475c153fc622e656f1f10a9c9941d0ac7ab18df7c38d35d563a437c1c0e34f24 \
--hash=sha256:86dd61df581cba04e89e45081efbc531faff1c9d99c77b1ce97f87216c356353 \
--hash=sha256:75cc697e4ef5fdd0102ca749114c6370dbd11db0c9132a18834858c2566247e3 \
--hash=sha256:ea03ad5b9df6d79fc9fc1ab23729e01e1c920d2974c5e3c634ccf45a5c378452 \
--hash=sha256:c8872b8fe4f3416d6338ab99612f49ab314f7856cb43bffab2a32d28a6267be8 \
--hash=sha256:468fc6e16eaec6ceaa6bc341273e6e9912d01b42b740f8cf896ace7fcd6a321d \
--hash=sha256:d6fea3c6502735011c5d61a62aef1c1d770fc6a2def45d9e6c0d94c9651e3317 \
--hash=sha256:3cf95f179f4bead3d5649b91860ef4cf60ad4244209190fc405908272576d961 \
--hash=sha256:141f77e60a5b9158309b2b60288c7f81d37faa15c22a69b94c190ceefaaa6236 \
--hash=sha256:87b7a1fe703c6424451f3372d1879dae91c7fe5e13375441a72833db76fee30e \
--hash=sha256:f5ee3cb0cf1a6550bf483ccffa6608db267a377b45f7e3a8201a86d1d8feb19f \
--hash=sha256:4e097286651ea318300af3251375d48b71b8228481c56cd617ddd4459a1ff261 \
--hash=sha256:1e3d3ae3f22f22d50d340f47f25227511326f3f1396c6d2446a5b45b516c4313 \
--hash=sha256:6a057941cb64d79834ea3cf99093fcc4787c2a5d44f686c4f297361ddc419bcd \
--hash=sha256:68b3d5390b92559ddd3353c73ab2dfcff758f9c4ec4f5d5226ccede0e5d779f4 \
--hash=sha256:545dc003b4b6081f9c3e452da15d819b04b696f49484aff64c0a2aedf766bef8 \
--hash=sha256:423ff890c01be7c70dbfeaa967eeef5146f1a43a5f810ffdc07b178e48a105a9
cryptography==1.5.3 \
--hash=sha256:e514d92086246b53ae9b048df652cf3036b462e50a6ce9fac6b6253502679991 \
--hash=sha256:10ee414f4b5af403a0d8f20dfa80f7dad1fc7ae5452ec5af03712d5b6e78c664 \
--hash=sha256:7234456d1f4345a144ed07af2416c7c0659d4bb599dd1a963103dc8c183b370e \
--hash=sha256:d3b9587406f94642bd70b3d666b813f446e95f84220c9e416ad94cbfb6be2eaa \
--hash=sha256:b15fc6b59f1474eef62207c85888afada8acc47fae8198ba2b0197d54538961a \
--hash=sha256:3b62d65d342704fc07ed171598db2a2775bdf587b1b6abd2cba2261bfe3ccde3 \
--hash=sha256:059343022ec904c867a13bc55d2573e36c8cfb2c250e30d8a2e9825f253b07ba \
--hash=sha256:c7897cf13bc8b4ee0215d83cbd51766d87c06b277fcca1f9108595508e5bcfb4 \
--hash=sha256:9b69e983e5bf83039ddd52e52a28c7faedb2b22bdfb5876377b95aac7d3be63e \
--hash=sha256:61e40905c426d02b3fae38088dc66ce4ef84830f7eb223dec6b3ac3ccdc676fb \
--hash=sha256:00783a32bcd91a12177230d35bfcf70a2333ade4a6b607fac94a633a7971c671 \
--hash=sha256:d11973f49b648cde1ea1a30e496d7557dbfeccd08b3cd9ba58d286a9c274ff8e \
--hash=sha256:f24bedf28b81932ba6063aec9a826669f5237ea3b755efe04d98b072faa053a5 \
--hash=sha256:3ab5725367239e3deb9b92e917aa965af3fef008f25b96a3000821869e208181 \
--hash=sha256:8a53209de822e22b5f73bf4b99e68ac4ccc91051fd6751c8252982983e86a77d \
--hash=sha256:5a07439d4b1e4197ac202b7eea45e26a6fd65757652dc50f1a63367f711df933 \
--hash=sha256:26b1c4b40aec7b0074bceabe6e06565aa28176eca7323a31df66ebf89fe916d3 \
--hash=sha256:eaa4a7b5a6682adcf8d6ebb2a08a008802657643655bb527c95c8a3860253d8e \
--hash=sha256:8156927dcf8da274ff205ad0612f75c380df45385bacf98531a5b3348c88d135 \
--hash=sha256:61ec0d792749d0e91e84b1d58b6dfd204806b10b5811f846c2ceca0de028c53a \
--hash=sha256:26330c88041569ca621cc42274d0ea2667a48b6deab41467272c3aba0b6e8f07 \
--hash=sha256:cf82ddac919b587f5e44247579b433224cc2e03332d2ea4d89aa70d7e6b64ae5
enum34==1.1.2 \
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
@ -662,8 +739,6 @@ ipaddress==1.0.16 \
linecache2==1.0.0 \
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c
ndg-httpsclient==0.4.0 \
--hash=sha256:e8c155fdebd9c4bcb0810b4ed01ae1987554b1ee034dd7532d7b8fdae38a6274
ordereddict==1.1 \
--hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f
parsedatetime==2.1 \
@ -684,9 +759,9 @@ pyasn1==0.1.9 \
--hash=sha256:5191ff6b9126d2c039dd87f8ff025bed274baf07fa78afa46f556b1ad7265d6e \
--hash=sha256:8323e03637b2d072cc7041300bac6ec448c3c28950ab40376036788e9a1af629 \
--hash=sha256:853cacd96d1f701ddd67aa03ecc05f51890135b7262e922710112f12a2ed2a7f
pyopenssl==16.0.0 \
--hash=sha256:5add70cf00273bf957ca31fdb0df9b0ae4639e081897d5f86a0ae1f104901230 \
--hash=sha256:363d10ee43d062285facf4e465f4f5163f9f702f9134f0a5896f134cbb92d17d
pyOpenSSL==16.2.0 \
--hash=sha256:26ca380ddf272f7556e48064bbcd5bd71f83dfc144f3583501c7ddbd9434ee17 \
--hash=sha256:7779a3bbb74e79db234af6a08775568c6769b5821faecf6e2f4143edb227516e
pyparsing==2.1.8 \
--hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \
--hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \
@ -701,9 +776,6 @@ pyRFC3339==1.0 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
python2-pythondialog==3.3.0 \
--hash=sha256:04e93f24995c43dd90f338d5d865ca72ce3fb5a5358d4daa4965571db35fc3ec \
--hash=sha256:3e6f593fead98f8a526bc3e306933533236e33729f552f52896ea504f55313fa
pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
@ -718,9 +790,9 @@ pytz==2015.7 \
--hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \
--hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \
--hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3
requests==2.9.1 \
--hash=sha256:113fbba5531a9e34945b7d36b33a084e8ba5d0664b703c81a7c572d91919a5b8 \
--hash=sha256:c577815dd00f1394203fc44eb979724b098f88264a9ef898ee45b8e5e9cf587f
requests==2.12.1 \
--hash=sha256:3f3f27a9d0f9092935efc78054ef324eb9f8166718270aefe036dfa1e4f68e1e \
--hash=sha256:2109ecea94df90980be040490ff1d879971b024861539abb00054062388b612e
six==1.10.0 \
--hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \
--hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a
@ -761,18 +833,18 @@ letsencrypt==0.7.0 \
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
acme==0.9.3 \
--hash=sha256:d18ce17a75ad24d27981dfaef0524aa905eab757b267e027162b56a8967ab8fb \
--hash=sha256:a6eff1f955eb2e4316abd9aa2fedb6d9345e6b5b8a2d64ea0ad35e05d6124099
certbot==0.9.3 \
--hash=sha256:a87ef4c53c018df4e52ee2f2e906ad16bbb37789f29e6f284c495a2eb4d9b243 \
--hash=sha256:68149cb8392b29f5d5246e7226d25f913f2b10482bf3bc7368e8c8821d25f3b0
certbot-apache==0.9.3 \
--hash=sha256:f379b1053e10709692654d7a6fcea9eaed19b66c49a753b61e31bd06a04b0aac \
--hash=sha256:a5d98cf972072de08f984db4e6a7f20269f3f023c43f6d4e781fe43be7c10086
certbot-nginx==0.9.3 \
--hash=sha256:3c26f18f0b57550f069263bd9b2984ef33eab6693e7796611c1b2cc16574069c \
--hash=sha256:7337a2e90e0b28a1ab09e31d9fb81c6d78e6453500c824c0f18bab5d31b63058
acme==0.12.0 \
--hash=sha256:a6050619b3e07b41d197992bb15b32c755dfa0665cfa1c20faa82806a798265b \
--hash=sha256:a05cba6b5b0fffdfa246b32492a44769011d45205f3ee8efde1f37ee9843fbdf
certbot==0.12.0 \
--hash=sha256:d018d13665eb4cfe7038c2df636e3f4928742b83769b95edfdb0311277f0eb48 \
--hash=sha256:4a71925c035b62dfb7c3343c619ee090add76188b47225272b57798ad63388b7
certbot-apache==0.12.0 \
--hash=sha256:de86907ea60e7bc35d252b87dec04eab3c7f3a1ea768774876e7ff582d89d640 \
--hash=sha256:77dde63cf97292b09da8ae09ef8a7a6d83a3b1ee0f8d1fefe513fc77a6292509
certbot-nginx==0.12.0 \
--hash=sha256:c66d848c4577f1f91a06a8119b40f1ab90af1546addea27905434bd070f3924d \
--hash=sha256:4dab2c93304c80d8d0d2e5214939f016804fd46859dd7a39b892d8b7195ab5ec
UNLIKELY_EOF
# -------------------------------------------------------------------------
@ -940,7 +1012,28 @@ UNLIKELY_EOF
# Report error. (Otherwise, be quiet.)
echo "Had a problem while installing Python packages."
if [ "$VERBOSE" != 1 ]; then
echo
echo "pip prints the following errors: "
echo "====================================================="
echo "$PIP_OUT"
echo "====================================================="
echo
echo "Certbot has problem setting up the virtual environment."
if `echo $PIP_OUT | grep -q Killed` || `echo $PIP_OUT | grep -q "allocate memory"` ; then
echo
echo "Based on your pip output, the problem can likely be fixed by "
echo "increasing the available memory."
else
echo
echo "We were not be able to guess the right solution from your pip "
echo "output."
fi
echo
echo "Consult https://certbot.eff.org/docs/install.html#problems-with-python-virtual-environment"
echo "for possible solutions."
echo "You may also find some support resources at https://certbot.eff.org/support/ ."
fi
rm -rf "$VENV_PATH"
exit 1
@ -963,7 +1056,7 @@ UNLIKELY_EOF
fi
else
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
# Phase 1: Upgrade certbot-auto if necessary, 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
@ -1132,7 +1225,7 @@ UNLIKELY_EOF
# TODO: Deal with quotes in pathnames.
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:
# option on macOS or BSD, and stat -c on Linux is stat -f on macOS and BSD:
$SUDO cp -p "$0" "$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

View file

@ -14,7 +14,7 @@ RUN /opt/certbot/src/certbot-auto -n --os-packages-only
# the above is not likely to change, so by putting it further up the
# Dockerfile we make sure we cache as much as possible
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini pep8.travis.sh .pep8 .pylintrc /opt/certbot/src/
COPY setup.py README.rst CHANGES.rst MANIFEST.in linter_plugin.py tox.cover.sh tox.ini .pylintrc /opt/certbot/src/
# all above files are necessary for setup.py, however, package source
# code directory has to be copied separately to a subdirectory...

View file

@ -8,6 +8,7 @@ import zope.interface
from certbot import configuration
from certbot import errors as le_errors
from certbot import util as certbot_util
from certbot_apache import configurator
from certbot_apache import constants
from certbot_compatibility_test import errors
@ -106,4 +107,7 @@ def _get_names(config):
not util.IP_REGEX.match(words[1]) and
words[1].find(".") != -1):
all_names.add(words[1])
return all_names, non_ip_names
return (
certbot_util.get_filtered_names(all_names),
certbot_util.get_filtered_names(non_ip_names)
)

View file

@ -147,7 +147,7 @@ def test_deploy_cert(plugin, temp_dir, domains):
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path, cert_path)
plugin.save() # Needed by the Apache plugin
except le_errors.Error as error:
logger.error("Plugin failed to deploy ceritificate for %s:", domain)
logger.error("Plugin failed to deploy certificate for %s:", domain)
logger.exception(error)
return False
@ -202,7 +202,7 @@ def test_enhancements(plugin, domains):
success = False
if success:
logger.info("Enhancments test succeeded")
logger.info("Enhancements test succeeded")
return success

View file

@ -4,6 +4,8 @@ import socket
import requests
import zope.interface
import six
from acme import crypto_util
from acme import errors as acme_errors
from certbot import interfaces
@ -19,7 +21,14 @@ class Validator(object):
def certificate(self, cert, name, alt_host=None, port=443):
"""Verifies the certificate presented at name is cert"""
host = alt_host if alt_host else socket.gethostbyname(name)
if alt_host is None:
host = socket.gethostbyname(name)
elif isinstance(alt_host, six.binary_type):
host = alt_host
else:
host = alt_host.encode()
name = name if isinstance(name, six.binary_type) else name.encode()
try:
presented_cert = crypto_util.probe_sni(name, host, port)
except acme_errors.Error as error:

View file

@ -38,7 +38,7 @@ http {
## Define a zone for limiting the number of simultaneous
## connections nginx accepts. 1m means 32000 simultaneous
## sessions. We need to define for each server the limit_conn
## value refering to this or other zones.
## value referring to this or other zones.
## ** This syntax requires nginx version >=
## ** 1.1.8. Cf. http://nginx.org/en/CHANGES. If using an older
## ** version then use the limit_zone directive below

View file

@ -37,7 +37,7 @@ charset_map windows-1251 utf-8 {
AA D084 ; # capital Ukrainian YE
AB C2AB ; # left-pointing double angle quotation mark
AC C2AC ; # not sign
AD C2AD ; # soft hypen
AD C2AD ; # soft hyphen
AE C2AE ; # (R)
AF D087 ; # capital Ukrainian YI

View file

@ -8,7 +8,7 @@ http {
keepalive_timeout 60;
# http-redirects to https; even if using of hsts;
# usefull if users are typing your server-name w/out https://
# useful if users are typing your server-name w/out https://
# logjam and a good idea anyway
@ -98,7 +98,7 @@ http {
#ssl_ciphers ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!RC4:+HIGH:+MEDIUM;
#
# suggestions by mozilla-server-team - good compatibility, pfs, preferrable ciphers
# suggestions by mozilla-server-team - good compatibility, pfs, preferable ciphers
#
# modern ciphers
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
@ -106,7 +106,7 @@ http {
# intermediate ciphers
#ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
# old ciphers (would need SSLv3, but is not recommende as of oct 2014
# old ciphers (would need SSLv3, but is not recommended as of oct 2014
#ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
# logjam / cipher suggested from weakdh.org

View file

@ -29,7 +29,7 @@ server {
location ~* (index\.php|upload\.php|connector\.php|dl\.php|ut\.php|lt\.php|download\.php)$ {
fastcgi_split_path_info ^(.|\.php)(/.+)$;
include /etc/nginx/fastcgi_params.conf; #standar fastcgi config file
include /etc/nginx/fastcgi_params.conf; #standard fastcgi config file
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_intercept_errors on;
fastcgi_pass 127.0.0.1:9000;

View file

@ -4,11 +4,12 @@ from setuptools import setup
from setuptools import find_packages
version = '0.10.0.dev0'
version = '0.13.0.dev0'
install_requires = [
'certbot',
'certbot-apache',
'six',
'requests',
'zope.interface',
]

View file

@ -403,25 +403,7 @@ class NginxConfigurator(common.Plugin):
except (socket.error, socket.herror, socket.timeout):
continue
return self._get_filtered_names(all_names)
def _get_filtered_names(self, all_names):
"""Removes names that aren't considered valid by Let's Encrypt.
:param set all_names: all names found in the Nginx configuration
:returns: all found names that are considered valid by LE
:rtype: set
"""
filtered_names = set()
for name in all_names:
try:
filtered_names.add(util.enforce_le_validity(name))
except errors.ConfigurationError as error:
logger.debug('Not suggesting name "%s"', name)
logger.debug(error)
return filtered_names
return util.get_filtered_names(all_names)
def _get_snakeoil_paths(self):
# TODO: generate only once
@ -648,7 +630,7 @@ class NginxConfigurator(common.Plugin):
stderr=subprocess.PIPE)
text = proc.communicate()[1] # nginx prints output to stderr
except (OSError, ValueError) as error:
logging.debug(error, exc_info=True)
logger.debug(error, exc_info=True)
raise errors.PluginError(
"Unable to run %s -V" % self.conf('ctl'))

View file

@ -52,10 +52,10 @@ class RawNginxParser(object):
map_statement = space + Literal("map") + space + nonspace + space + dollar_var + space
# This is NOT an accurate way to parse nginx map entries; it's almost
# certianly too permissive and may be wrong in other ways, but it should
# certainly too permissive and may be wrong in other ways, but it should
# preserve things correctly in mmmmost or all cases.
#
# - I can neither prove nor disprove that it is corect wrt all escaped
# - I can neither prove nor disprove that it is correct wrt all escaped
# semicolon situations
# Addresses https://github.com/fatiherikli/nginxparser/issues/19
map_pattern = Regex(r'".*"') | Regex(r"'.*'") | nonspace
@ -143,7 +143,7 @@ class RawNginxDumper(object):
def loads(source):
"""Parses from a string.
:param str souce: The string to parse
:param str source: The string to parse
:returns: The parsed tree
:rtype: list

View file

@ -205,8 +205,8 @@ class NginxParser(object):
trees.append(parsed)
except IOError:
logger.warning("Could not open file: %s", item)
except pyparsing.ParseException:
logger.debug("Could not parse file: %s", item)
except pyparsing.ParseException as err:
logger.debug("Could not parse file: %s due to %s", item, err)
return trees
def _parse_ssl_options(self, ssl_options):
@ -216,8 +216,8 @@ class NginxParser(object):
return nginxparser.load(_file).spaced
except IOError:
logger.warn("Missing NGINX TLS options file: %s", ssl_options)
except pyparsing.ParseBaseException:
logger.debug("Could not parse file: %s", ssl_options)
except pyparsing.ParseBaseException as err:
logger.debug("Could not parse file: %s due to %s", ssl_options, err)
return []
def _set_locations(self, ssl_options):
@ -586,9 +586,10 @@ def _parse_server_raw(server):
continue
if directive[0] == 'listen':
addr = obj.Addr.fromstring(directive[1])
parsed_server['addrs'].add(addr)
if addr.ssl:
parsed_server['ssl'] = True
if addr:
parsed_server['addrs'].add(addr)
if addr.ssl:
parsed_server['ssl'] = True
elif directive[0] == 'server_name':
parsed_server['names'].update(
_get_servernames(directive[1]))

View file

@ -323,6 +323,12 @@ class NginxParserTest(util.NginxTest):
])
self.assertTrue(server['ssl'])
def test_parse_server_raw_unix(self):
server = parser._parse_server_raw([ #pylint: disable=protected-access
['listen', 'unix:/var/run/nginx.sock']
])
self.assertEqual(len(server['addrs']), 0)
def test_parse_server_global_ssl_applied(self):
nparser = parser.NginxParser(self.config_path, self.ssl_options)
server = nparser.parse_server([

View file

@ -67,7 +67,7 @@ MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Co
####################################
MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400;
MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401;
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither mulipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither multipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
#############################
## File uploads: 1500-1600 ##

View file

@ -36,7 +36,7 @@ charset_map windows-1251 utf-8 {
AA D084; # capital Ukrainian YE
AB C2AB; # left-pointing double angle quotation mark
AC C2AC; # not sign
AD C2AD; # soft hypen
AD C2AD; # soft hyphen
AE C2AE; # (R)
AF D087; # capital Ukrainian YI

View file

@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.10.0.dev0'
version = '0.13.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.10.0.dev0'
__version__ = '0.13.0.dev0'

View file

@ -3,6 +3,7 @@ import datetime
import hashlib
import logging
import os
import shutil
import socket
from cryptography.hazmat.primitives import serialization
@ -81,7 +82,7 @@ class Account(object): # pylint: disable=too-few-public-methods
self.meta == other.meta)
def report_new_account(acc, config):
def report_new_account(config):
"""Informs the user about their new ACME account."""
reporter = zope.component.queryUtility(interfaces.IReporter)
if reporter is None:
@ -95,12 +96,6 @@ def report_new_account(acc, config):
config.config_dir),
reporter.MEDIUM_PRIORITY)
if acc.regr.body.emails:
recovery_msg = ("If you lose your account credentials, you can "
"recover through e-mails sent to {0}.".format(
", ".join(acc.regr.body.emails)))
reporter.add_message(recovery_msg, reporter.MEDIUM_PRIORITY)
class AccountMemoryStorage(interfaces.AccountStorage):
"""In-memory account strage."""
@ -197,6 +192,18 @@ class AccountFileStorage(interfaces.AccountStorage):
"""
self._save(account, regr_only=True)
def delete(self, account_id):
"""Delete registration info from disk
:param account_id: id of account which should be deleted
"""
account_dir_path = self._account_dir_path(account_id)
if not os.path.isdir(account_dir_path):
raise errors.AccountNotFound(
"Account at %s does not exist" % account_dir_path)
shutil.rmtree(account_dir_path)
def _save(self, account, regr_only):
account_dir_path = self._account_dir_path(account.id)
util.make_or_verify_dir(account_dir_path, 0o700, os.geteuid(),

View file

@ -1,6 +1,6 @@
"""Client annotated ACME challenges.
Please use names such as ``achall`` to distiguish from variables "of type"
Please use names such as ``achall`` to distinguish from variables "of type"
:class:`acme.challenges.Challenge` (denoted by ``chall``)
and :class:`.ChallengeBody` (denoted by ``challb``)::

View file

@ -34,8 +34,7 @@ class AuthHandler(object):
:ivar list achalls: DV challenges in the form of
:class:`certbot.achallenges.AnnotatedChallenge`
:ivar list pref_challs: sorted user specified preferred challenges
in the form of subclasses of :class:`acme.challenges.Challenge`
with the most preferred challenge listed first
type strings with the most preferred challenge listed first
"""
def __init__(self, auth, acme, account, pref_challs):
@ -252,8 +251,10 @@ class AuthHandler(object):
# Make sure to make a copy...
plugin_pref = self.auth.get_chall_pref(domain)
if self.pref_challs:
chall_prefs.extend(pref for pref in self.pref_challs
if pref in plugin_pref)
plugin_pref_types = set(chall.typ for chall in plugin_pref)
for typ in self.pref_challs:
if typ in plugin_pref_types:
chall_prefs.append(challenges.Challenge.TYPES[typ])
if chall_prefs:
return chall_prefs
raise errors.AuthorizationError(

View file

@ -95,27 +95,26 @@ def delete(config):
# Public Helpers
###################
def lineage_for_certname(config, certname):
def lineage_for_certname(cli_config, certname):
"""Find a lineage object with name certname."""
def update_cert_for_name_match(candidate_lineage, rv):
"""Return cert if it has name certname, else return rv
"""
matching_lineage_name_cert = rv
if candidate_lineage.lineagename == certname:
matching_lineage_name_cert = candidate_lineage
return matching_lineage_name_cert
return _search_lineages(config, update_cert_for_name_match, None)
configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there
util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid())
try:
renewal_file = storage.renewal_file_for_certname(cli_config, certname)
except errors.CertStorageError:
return None
try:
return storage.RenewableCert(renewal_file, cli_config)
except (errors.CertStorageError, IOError):
logger.debug("Renewal conf file %s is broken.", renewal_file)
logger.debug("Traceback was:\n%s", traceback.format_exc())
return None
def domains_for_certname(config, certname):
"""Find the domains in the cert with name certname."""
def update_domains_for_name_match(candidate_lineage, rv):
"""Return domains if certname matches, else return rv
"""
matching_domains = rv
if candidate_lineage.lineagename == certname:
matching_domains = candidate_lineage.names()
return matching_domains
return _search_lineages(config, update_domains_for_name_match, None)
lineage = lineage_for_certname(config, certname)
return lineage.names() if lineage else None
def find_duplicative_certs(config, domains):
"""Find existing certs that duplicate the request."""

View file

@ -1,4 +1,5 @@
"""Certbot command line argument & config processing."""
# pylint: disable=too-many-lines
from __future__ import print_function
import argparse
import copy
@ -149,7 +150,7 @@ def possible_deprecation_warning(config):
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
# users setting --no-self-upgrade might be hanging on a client 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
@ -317,7 +318,7 @@ class CustomHelpFormatter(argparse.HelpFormatter):
# The attributes here are:
# short: a string that will be displayed by "certbot -h commands"
# opts: a string that heads the section of flags with which this command is documented,
# both for "cerbot -h SUBCOMMAND" and "certbot -h all"
# both for "certbot -h SUBCOMMAND" and "certbot -h all"
# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND"
VERB_HELP = [
("run (default)", {
@ -333,7 +334,7 @@ VERB_HELP = [
"This command obtains a TLS/SSL certificate without installing it anywhere.")
}),
("renew", {
"short": "Renew all certificates (or one specifed with --cert-name)",
"short": "Renew all certificates (or one specified with --cert-name)",
"opts": ("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"
@ -366,6 +367,10 @@ VERB_HELP = [
"short": "Register for account with Let's Encrypt / other ACME server",
"opts": "Options for account registration & modification"
}),
("unregister", {
"short": "Irrevocably deactivate your account",
"opts": "Options for account deactivation."
}),
("install", {
"short": "Install an arbitrary cert in a server",
"opts": "Options for modifying how a cert is deployed"
@ -407,13 +412,14 @@ class HelpfulArgumentParser(object):
def __init__(self, args, plugins, detect_defaults=False):
from certbot import main
self.VERBS = {
"auth": main.obtain_cert,
"certonly": main.obtain_cert,
"auth": main.certonly,
"certonly": main.certonly,
"config_changes": main.config_changes,
"run": main.run,
"install": main.install,
"plugins": main.plugins_cmd,
"register": main.register,
"unregister": main.unregister,
"renew": main.renew,
"revoke": main.revoke,
"rollback": main.rollback,
@ -432,6 +438,10 @@ class HelpfulArgumentParser(object):
self.detect_defaults = detect_defaults
self.args = args
if self.args and self.args[0] == 'help':
self.args[0] = '--help'
self.determine_verb()
help1 = self.prescan_for_flag("-h", self.help_topics)
help2 = self.prescan_for_flag("--help", self.help_topics)
@ -486,7 +496,7 @@ class HelpfulArgumentParser(object):
if "apache" in plugins:
apache_doc = "--apache Use the Apache plugin for authentication & installation"
else:
apache_doc = "(the cerbot apache plugin is not installed)"
apache_doc = "(the certbot apache plugin is not installed)"
usage = SHORT_USAGE
if help_arg == True:
@ -867,7 +877,15 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
help="With the register verb, indicates that details associated "
"with an existing registration, such as the e-mail address, "
"should be updated, rather than registering a new account.")
helpful.add(["register", "automation"], "-m", "--email", help=config_help("email"))
helpful.add(
["register", "unregister", "automation"], "-m", "--email",
help=config_help("email"))
helpful.add(["register", "automation"], "--eff-email", action="store_true",
default=None, dest="eff_email",
help="Share your e-mail address with EFF")
helpful.add(["register", "automation"], "--no-eff-email", action="store_false",
default=None, dest="eff_email",
help="Don't share your e-mail address with EFF")
helpful.add(
["automation", "certonly", "run"],
"--keep-until-expiring", "--keep", "--reinstall",
@ -909,7 +927,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
"automation", "--agree-tos", dest="tos", action="store_true",
help="Agree to the ACME Subscriber Agreement (default: Ask)")
helpful.add(
"automation", "--account", metavar="ACCOUNT_ID",
["unregister", "automation"], "--account", metavar="ACCOUNT_ID",
help="Account ID to use")
helpful.add(
"automation", "--duplicate", dest="duplicate", action="store_true",
@ -1072,6 +1090,11 @@ def _create_subparsers(helpful):
"--csr", type=read_file,
help="Path to a Certificate Signing Request (CSR) in DER or PEM format."
" Currently --csr only works with the 'certonly' subcommand.")
helpful.add("revoke",
"--reason", dest="reason",
choices=CaseInsensitiveList(constants.REVOCATION_REASONS.keys()),
action=_EncodeReasonAction, default=0,
help="Specify reason for revoking certificate.")
helpful.add("rollback",
"--checkpoints", type=int, metavar="N",
default=flag_default("rollback_checkpoints"),
@ -1088,6 +1111,16 @@ def _create_subparsers(helpful):
const=interfaces.IInstaller, help="Limit to installer plugins only.")
class CaseInsensitiveList(list):
"""A list that will ignore case when searching.
This class is passed to the `choices` argument of `argparse.add_arguments`
through the `helpful` wrapper. It is necessary due to special handling of
command line arguments by `set_by_cli` in which the `type_func` is not applied."""
def __contains__(self, element):
return super(CaseInsensitiveList, self).__contains__(element.lower())
def _paths_parser(helpful):
add = helpful.add
verb = helpful.verb
@ -1166,6 +1199,15 @@ def _plugins_parsing(helpful, plugins):
helpful.add_plugin_args(plugins)
class _EncodeReasonAction(argparse.Action):
"""Action class for parsing revocation reason."""
def __call__(self, parser, namespace, reason, option_string=None):
"""Encodes the reason for certificate revocation."""
code = constants.REVOCATION_REASONS[reason.lower()]
setattr(namespace, self.dest, code)
class _DomainsAction(argparse.Action):
"""Action class for parsing domains."""
@ -1201,13 +1243,31 @@ class _PrefChallAction(argparse.Action):
"""Action class for parsing preferred challenges."""
def __call__(self, parser, namespace, pref_challs, option_string=None):
aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"}
challs = [c.strip() for c in pref_challs.split(",")]
challs = [aliases[c] if c in aliases else c for c in challs]
unrecognized = ", ".join(name for name in challs
if name not in challenges.Challenge.TYPES)
if unrecognized:
raise argparse.ArgumentTypeError(
"Unrecognized challenges: {0}".format(unrecognized))
namespace.pref_challs.extend(challenges.Challenge.TYPES[name]
for name in challs)
try:
challs = parse_preferred_challenges(pref_challs.split(","))
except errors.Error as error:
raise argparse.ArgumentTypeError(str(error))
namespace.pref_challs.extend(challs)
def parse_preferred_challenges(pref_challs):
"""Translate and validate preferred challenges.
:param pref_challs: list of preferred challenge types
:type pref_challs: `list` of `str`
:returns: validated list of preferred challenge types
:rtype: `list` of `str`
:raises errors.Error: if pref_challs is invalid
"""
aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"}
challs = [c.strip() for c in pref_challs]
challs = [aliases.get(c, c) for c in challs]
unrecognized = ", ".join(name for name in challs
if name not in challenges.Challenge.TYPES)
if unrecognized:
raise errors.Error(
"Unrecognized challenges: {0}".format(unrecognized))
return challs

View file

@ -15,15 +15,16 @@ import certbot
from certbot import account
from certbot import auth_handler
from certbot import cli
from certbot import constants
from certbot import crypto_util
from certbot import errors
from certbot import eff
from certbot import error_handler
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot import reverter
from certbot import storage
from certbot import cli
from certbot import util
from certbot.display import ops as display_ops
from certbot.display import enhancements
@ -93,7 +94,7 @@ def register(config, account_storage, tos_cb=None):
Terms of Service present in the contained
`.Registration.terms_of_service` is accepted by the client, and
``False`` otherwise. ``tos_cb`` will be called only if the
client acction is necessary, i.e. when ``terms_of_service is not
client action is necessary, i.e. when ``terms_of_service is not
None``. This argument is optional, if not supplied it will
default to automatic acceptance!
@ -136,9 +137,11 @@ def register(config, account_storage, tos_cb=None):
regr = acme.agree_to_tos(regr)
acc = account.Account(regr, key)
account.report_new_account(acc, config)
account.report_new_account(config)
account_storage.save(acc)
eff.handle_subscription(config)
return acc, acme
@ -152,8 +155,6 @@ def perform_registration(acme, config):
:returns: Registration Resource.
:rtype: `acme.messages.RegistrationResource`
:raises .UnexpectedUpdate:
"""
try:
return acme.register(messages.NewRegistration.from_data(email=config.email))
@ -172,7 +173,7 @@ def perform_registration(acme, config):
class Client(object):
"""ACME protocol client.
"""Certbot's client.
:ivar .IConfig config: Client configuration.
:ivar .Account account: Account registered with `register`.
@ -438,7 +439,7 @@ class Client(object):
self.installer.restart()
def apply_enhancement(self, domains, enhancement, options=None):
"""Applies an enhacement on all domains.
"""Applies an enhancement on all domains.
:param domains: list of ssl_vhosts
:type list of str
@ -494,7 +495,7 @@ class Client(object):
self.installer.rollback_checkpoints()
self.installer.restart()
except:
# TODO: suggest letshelp-letsencypt here
# TODO: suggest letshelp-letsencrypt here
reporter.add_message(
"An error occurred and we failed to restore your config and "
"restart your server. Please submit a bug report to "

View file

@ -35,6 +35,17 @@ CLI_DEFAULTS = dict(
)
STAGING_URI = "https://acme-staging.api.letsencrypt.org/directory"
# The set of reasons for revoking a certificate is defined in RFC 5280 in
# section 5.3.1. The reasons that users are allowed to submit are restricted to
# those accepted by the ACME server implementation. They are listed in
# `letsencrypt.boulder.revocation.reasons.go`.
REVOCATION_REASONS = {
"unspecified": 0,
"keycompromise": 1,
"affiliationchanged": 3,
"superseded": 4,
"cessationofoperation": 5}
"""Defaults for CLI flags and `.IConfig` attributes."""
QUIET_LOGGING_LEVEL = logging.WARNING
@ -95,3 +106,6 @@ RENEWAL_CONFIGS_DIR = "renewal"
FORCE_INTERACTIVE_FLAG = "--force-interactive"
"""Flag to disable TTY checking in IDisplay."""
EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot"
"""EFF URI used to submit the e-mail address of users who opt-in."""

95
certbot/eff.py Normal file
View file

@ -0,0 +1,95 @@
"""Subscribes users to the EFF newsletter."""
import logging
import requests
import zope.component
from certbot import constants
from certbot import interfaces
logger = logging.getLogger(__name__)
def handle_subscription(config):
"""High level function to take care of EFF newsletter subscriptions.
The user may be asked if they want to sign up for the newsletter if
they have not already specified.
:param .IConfig config: Client configuration.
"""
if config.email is None:
if config.eff_email:
_report_failure("you didn't provide an e-mail address")
return
if config.eff_email is None:
config.eff_email = _want_subscription()
if config.eff_email:
subscribe(config.email)
def _want_subscription():
"""Does the user want to be subscribed to the EFF newsletter?
:returns: True if we should subscribe the user, otherwise, False
:rtype: bool
"""
prompt = (
'Would you be willing to share your email address with the '
"Electronic Frontier Foundation, a founding partner of the Let's "
'Encrypt project and the non-profit organization that develops '
"Certbot? We'd like to send you email about EFF and our work to "
'encrypt the web, protect its users and defend digital rights.')
display = zope.component.getUtility(interfaces.IDisplay)
return display.yesno(prompt, default=False)
def subscribe(email):
"""Subscribe the user to the EFF mailing list.
:param str email: the e-mail address to subscribe
"""
url = constants.EFF_SUBSCRIBE_URI
data = {'data_type': 'json',
'email': email,
'form_id': 'eff_supporters_library_subscribe_form'}
logger.debug('Sending POST request to %s:\n%s', url, data)
_check_response(requests.post(url, data=data))
def _check_response(response):
"""Check for errors in the server's response.
If an error occurred, it will be reported to the user.
:param requests.Response response: the server's response to the
subscription request
"""
logger.debug('Received response:\n%s', response.content)
if response.ok:
if not response.json()['status']:
_report_failure('your e-mail address appears to be invalid')
else:
_report_failure()
def _report_failure(reason=None):
"""Notify the user of failing to sign them up for the newsletter.
:param reason: a phrase describing what the problem was
beginning with a lowercase letter and no closing punctuation
:type reason: `str` or `None`
"""
msg = ['We were unable to subscribe you the EFF mailing list']
if reason is not None:
msg.append(' because ')
msg.append(reason)
msg.append('. You can try again later by visiting https://act.eff.org.')
reporter = zope.component.getUtility(interfaces.IReporter)
reporter.add_message(''.join(msg), reporter.LOW_PRIORITY)

View file

@ -118,9 +118,9 @@ class ErrorHandler(object):
self.prev_handlers.clear()
def _signal_handler(self, signum, unused_frame):
"""Replacement function for handling recieved signals.
"""Replacement function for handling received signals.
Store the recieved signal. If we are executing the code block in
Store the received signal. If we are executing the code block in
the body of the context manager, stop by raising signal exit.
:param int signum: number of current signal

View file

@ -30,7 +30,7 @@ class HookCommandNotFound(Error):
class SignalExit(Error):
"""A Unix signal was recieved while in the ErrorHandler context manager."""
"""A Unix signal was received while in the ErrorHandler context manager."""
# Auth Handler Errors

View file

@ -44,8 +44,12 @@ def validate_hook(shell_cmd, hook_name):
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(
cmd, path, hook_name)
if os.path.exists(cmd):
msg = "{1}-hook command {0} exists, but is not executable.".format(cmd, hook_name)
else:
msg = "Unable to find {2}-hook command {0} in the PATH.\n(PATH is {1})".format(
cmd, path, hook_name)
raise errors.HookCommandNotFound(msg)
def pre_hook(config):

View file

@ -24,6 +24,7 @@ from certbot import crypto_util
from certbot import colored_logging
from certbot import configuration
from certbot import constants
from certbot import eff
from certbot import errors
from certbot import hooks
from certbot import interfaces
@ -39,7 +40,7 @@ from certbot.plugins import selection as plug_sel
_PERM_ERR_FMT = os.linesep.join((
"The following error was encountered:", "{0}",
"If running as non-root, set --config-dir, "
"--logs-dir, and --work-dir to writeable paths."))
"--work-dir, and --logs-dir to writeable paths."))
USER_CANCELLED = ("User chose to cancel the operation and may "
"reinvoke the client.")
@ -48,61 +49,45 @@ USER_CANCELLED = ("User chose to cancel the operation and may "
logger = logging.getLogger(__name__)
def _suggest_donation_if_appropriate(config, action):
def _suggest_donation_if_appropriate(config):
"""Potentially suggest a donation to support Certbot."""
if config.staging or config.verb == "renew":
assert config.verb != "renew"
if config.staging:
# --dry-run implies --staging
return
if action not in ["renew", "newcert"]:
return
reporter_util = zope.component.getUtility(interfaces.IReporter)
msg = ("If you like Certbot, please consider supporting our work by:\n\n"
"Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n"
"Donating to EFF: https://eff.org/donate-le\n\n")
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
def _report_successful_dry_run(config):
reporter_util = zope.component.getUtility(interfaces.IReporter)
if config.verb != "renew":
reporter_util.add_message("The dry run was successful.",
reporter_util.HIGH_PRIORITY, on_crash=False)
assert config.verb != "renew"
reporter_util.add_message("The dry run was successful.",
reporter_util.HIGH_PRIORITY, on_crash=False)
def _auth_from_available(le_client, config, domains=None, certname=None, lineage=None):
def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=None):
"""Authenticate and enroll certificate.
This method finds the relevant lineage, figures out what to do with it,
then performs that action. Includes calls to hooks, various reports,
checks, and requests for user input.
:returns: Tuple of (str action, cert_or_None) as per _find_lineage_for_domains_and_certname
action can be: "newcert" | "renew" | "reinstall"
:returns: the issued certificate or `None` if doing a dry run
:rtype: `storage.RenewableCert` or `None`
"""
# If lineage is specified, use that one instead of looking around for
# a matching one.
if lineage is None:
# This will find a relevant matching lineage that exists
action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname)
else:
# Renewal, where we already know the specific lineage we're
# interested in
action = "renew"
if action == "reinstall":
# The lineage already exists; allow the caller to try installing
# it without getting a new certificate at all.
logger.info("Keeping the existing certificate")
return "reinstall", lineage
hooks.pre_hook(config)
try:
if action == "renew":
if lineage is not None:
# Renewal, where we already know the specific lineage we're
# interested in
logger.info("Renewing an existing certificate")
renewal.renew_cert(config, le_client, lineage)
elif action == "newcert":
renewal.renew_cert(config, domains, le_client, lineage)
else:
# TREAT AS NEW REQUEST
assert domains is not None
logger.info("Obtaining a new certificate")
lineage = le_client.obtain_and_enroll_certificate(domains, certname)
if lineage is False:
@ -110,10 +95,7 @@ def _auth_from_available(le_client, config, domains=None, certname=None, lineage
finally:
hooks.post_hook(config)
if not config.dry_run and not config.verb == "renew":
_report_new_cert(config, lineage.cert, lineage.fullchain)
return action, lineage
return lineage
def _handle_subset_cert_request(config, domains, cert):
@ -235,6 +217,18 @@ def _find_lineage_for_domains(config, domains):
elif subset_names_cert is not None:
return _handle_subset_cert_request(config, domains, subset_names_cert)
def _find_cert(config, domains, certname):
"""Finds an existing certificate object given domains and/or a certificate name.
:returns: Two-element tuple of a boolean that indicates if this function should be
followed by a call to fetch a certificate from the server, and either a
RenewableCert instance or None.
"""
action, lineage = _find_lineage_for_domains_and_certname(config, domains, certname)
if action == "reinstall":
logger.info("Keeping the existing certificate")
return (action != "reinstall"), lineage
def _find_lineage_for_domains_and_certname(config, domains, certname):
"""Find appropriate lineage based on given domains and/or certname.
@ -313,26 +307,25 @@ def _report_new_cert(config, cert_path, fullchain_path):
:param str fullchain_path: path to full chain
"""
if config.dry_run:
_report_successful_dry_run(config)
return
assert cert_path and fullchain_path, "No certificates saved to report."
expiry = crypto_util.notAfter(cert_path).date()
reporter_util = zope.component.getUtility(interfaces.IReporter)
if fullchain_path:
# Print the path to fullchain.pem because that's what modern webservers
# (Nginx and Apache2.4) will want.
and_chain = "and chain have"
path = fullchain_path
else:
# Unless we're in .csr mode and there really isn't one
and_chain = "has "
path = cert_path
# Print the path to fullchain.pem because that's what modern webservers
# (Nginx and Apache2.4) will want.
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 or tweaked version of this '
'certificate in the future, simply run {3} again{4}. '
'To non-interactively renew *all* of your certificates, run "{3} renew"'
.format(and_chain, path, expiry, cli.cli_command, verbswitch))
msg = ('Congratulations! Your certificate and chain have been saved at {0}.'
' Your cert will expire on {1}. To obtain a new or tweaked version of this '
'certificate in the future, simply run {2} again{3}. '
'To non-interactively renew *all* of your certificates, run "{2} renew"'
.format(fullchain_path, expiry, cli.cli_command, verbswitch))
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
@ -406,6 +399,35 @@ def _init_le_client(config, authenticator, installer):
return client.Client(config, acc, authenticator, installer, acme=acme)
def unregister(config, unused_plugins):
"""Deactivate account on server"""
account_storage = account.AccountFileStorage(config)
accounts = account_storage.find_all()
reporter_util = zope.component.getUtility(interfaces.IReporter)
if not accounts:
return "Could not find existing account to deactivate."
yesno = zope.component.getUtility(interfaces.IDisplay).yesno
prompt = ("Are you sure you would like to irrevocably deactivate "
"your account?")
wants_deactivate = yesno(prompt, yes_label='Deactivate', no_label='Abort',
default=True)
if not wants_deactivate:
return "Deactivation aborted."
acc, acme = _determine_account(config)
acme_client = client.Client(config, acc, None, None, acme=acme)
# delete on boulder
acme_client.acme.deactivate_registration(acc.regr)
account_files = account.AccountFileStorage(config)
# delete local account files
account_files.delete(config.account)
reporter_util.add_message("Account deactivated.", reporter_util.MEDIUM_PRIORITY)
def register(config, unused_plugins):
"""Create or modify accounts on the server."""
@ -413,6 +435,8 @@ def register(config, unused_plugins):
# exist or not.
account_storage = account.AccountFileStorage(config)
accounts = account_storage.find_all()
reporter_util = zope.component.getUtility(interfaces.IReporter)
add_msg = lambda m: reporter_util.add_message(m, reporter_util.MEDIUM_PRIORITY)
# registering a new account
if not config.update_registration:
@ -443,10 +467,16 @@ def register(config, unused_plugins):
acc.regr = acme_client.acme.update_registration(acc.regr.update(
body=acc.regr.body.update(contact=('mailto:' + config.email,))))
account_storage.save_regr(acc)
reporter_util = zope.component.getUtility(interfaces.IReporter)
msg = "Your e-mail address was updated to {0}.".format(config.email)
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
eff.handle_subscription(config)
add_msg("Your e-mail address was updated to {0}.".format(config.email))
def _install_cert(config, le_client, domains, lineage=None):
path_provider = lineage if lineage else config
assert path_provider.cert_path is not None
le_client.deploy_certificate(domains, path_provider.key_path,
path_provider.cert_path, path_provider.chain_path, path_provider.fullchain_path)
le_client.enhance_config(domains, path_provider.chain_path)
def install(config, plugins):
"""Install a previously obtained cert in a server."""
@ -461,11 +491,7 @@ def install(config, plugins):
domains, _ = _find_domains_or_certname(config, installer)
le_client = _init_le_client(config, authenticator=None, installer=installer)
assert config.cert_path is not None # required=True in the subparser
le_client.deploy_certificate(
domains, config.key_path, config.cert_path, config.chain_path,
config.fullchain_path)
le_client.enhance_config(domains, config.chain_path)
_install_cert(config, le_client, domains)
def plugins_cmd(config, plugins): # TODO: Use IDisplay rather than print
@ -550,8 +576,10 @@ def revoke(config, unused_plugins): # TODO: coop with renewal config
key = acc.key
acme = client.acme_from_config_key(config, key)
cert = crypto_util.pyopenssl_load_certificate(config.cert_path[1])[0]
logger.debug("Reason code for revocation: %s", config.reason)
try:
acme.revoke(jose.ComparableX509(cert))
acme.revoke(jose.ComparableX509(cert), config.reason)
except acme_errors.ClientError as e:
return e.message
@ -567,28 +595,32 @@ def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals
except errors.PluginSelectionError as e:
return e.message
domains, certname = _find_domains_or_certname(config, installer)
# TODO: Handle errors from _init_le_client?
le_client = _init_le_client(config, authenticator, installer)
action, lineage = _auth_from_available(le_client, config, domains, certname)
domains, certname = _find_domains_or_certname(config, installer)
should_get_cert, lineage = _find_cert(config, domains, certname)
le_client.deploy_certificate(
domains, lineage.privkey, lineage.cert,
lineage.chain, lineage.fullchain)
new_lineage = lineage
if should_get_cert:
new_lineage = _get_and_save_cert(le_client, config, domains,
certname, lineage)
le_client.enhance_config(domains, lineage.chain)
cert_path = new_lineage.cert_path if new_lineage else None
fullchain_path = new_lineage.fullchain_path if new_lineage else None
_report_new_cert(config, cert_path, fullchain_path)
if action in ("newcert", "reinstall",):
_install_cert(config, le_client, domains, new_lineage)
if lineage is None or not should_get_cert:
display_ops.success_installation(domains)
else:
display_ops.success_renewal(domains)
_suggest_donation_if_appropriate(config, action)
_suggest_donation_if_appropriate(config)
def _csr_obtain_cert(config, le_client):
def _csr_get_and_save_cert(config, le_client):
"""Obtain a cert using a user-supplied CSR
This works differently in the CSR case (for now) because we don't
@ -600,16 +632,39 @@ def _csr_obtain_cert(config, le_client):
if config.dry_run:
logger.debug(
"Dry run: skipping saving certificate to %s", config.cert_path)
else:
cert_path, _, cert_fullchain = le_client.save_certificate(
return None, None
cert_path, _, fullchain_path = le_client.save_certificate(
certr, chain, config.cert_path, config.chain_path, config.fullchain_path)
_report_new_cert(config, cert_path, cert_fullchain)
return cert_path, fullchain_path
def obtain_cert(config, plugins, lineage=None):
def renew_cert(config, plugins, lineage):
"""Renew & save an existing cert. Do not install it."""
try:
# installers are used in auth mode to determine domain names
installer, auth = plug_sel.choose_configurator_plugins(config, plugins, "certonly")
except errors.PluginSelectionError as e:
logger.info("Could not choose appropriate plugin: %s", e)
raise
le_client = _init_le_client(config, auth, installer)
_get_and_save_cert(le_client, config, lineage=lineage)
notify = zope.component.getUtility(interfaces.IDisplay).notification
if installer is None:
notify("new certificate deployed without reload, fullchain is {0}".format(
lineage.fullchain), pause=False)
else:
# In case of a renewal, reload server to pick up new certificate.
# In principle we could have a configuration option to inhibit this
# from happening.
installer.restart()
notify("new certificate deployed with reload of {0} server; fullchain is {1}".format(
config.installer, lineage.fullchain), pause=False)
def certonly(config, plugins):
"""Authenticate & obtain cert, but do not install it.
This implements the 'certonly' subcommand, and is also called from within the
'renew' command."""
This implements the 'certonly' subcommand."""
# SETUP: Select plugins and construct a client instance
try:
@ -620,34 +675,26 @@ def obtain_cert(config, plugins, lineage=None):
raise
le_client = _init_le_client(config, auth, installer)
# SHOWTIME: Possibly obtain/renew a cert, and set action to renew | newcert | reinstall
if config.csr is None: # the common case
domains, certname = _find_domains_or_certname(config, installer)
action, _ = _auth_from_available(le_client, config, domains, certname, lineage)
else:
assert lineage is None, "Did not expect a CSR with a RenewableCert"
_csr_obtain_cert(config, le_client)
action = "newcert"
if config.csr:
cert_path, fullchain_path = _csr_get_and_save_cert(config, le_client)
_report_new_cert(config, cert_path, fullchain_path)
_suggest_donation_if_appropriate(config)
return
# POSTPRODUCTION: Cleanup, deployment & reporting
notify = zope.component.getUtility(interfaces.IDisplay).notification
if config.dry_run:
_report_successful_dry_run(config)
elif config.verb == "renew":
if installer is None:
notify("new certificate deployed without reload, fullchain is {0}".format(
lineage.fullchain), pause=False)
else:
# In case of a renewal, reload server to pick up new certificate.
# In principle we could have a configuration option to inhibit this
# from happening.
installer.restart()
notify("new certificate deployed with reload of {0} server; fullchain is {1}".format(
config.installer, lineage.fullchain), pause=False)
elif action == "reinstall" and config.verb == "certonly":
domains, certname = _find_domains_or_certname(config, installer)
should_get_cert, lineage = _find_cert(config, domains, certname)
if not should_get_cert:
notify = zope.component.getUtility(interfaces.IDisplay).notification
notify("Certificate not yet due for renewal; no action taken.", pause=False)
_suggest_donation_if_appropriate(config, action)
return
lineage = _get_and_save_cert(le_client, config, domains, certname, lineage)
cert_path = lineage.cert_path if lineage else None
fullchain_path = lineage.fullchain_path if lineage else None
_report_new_cert(config, cert_path, fullchain_path)
_suggest_donation_if_appropriate(config)
def renew(config, unused_plugins):
"""Renew previously-obtained certificates."""
@ -781,7 +828,7 @@ def make_or_verify_core_dir(directory, mode, uid, strict):
raise errors.Error(_PERM_ERR_FMT.format(error))
def make_or_verify_needed_dirs(config):
"""Create or verify existance of config, work, or logs directories"""
"""Create or verify existence of config, work, or logs directories"""
make_or_verify_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE,
os.geteuid(), config.strict_permissions)
make_or_verify_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE,

View file

@ -16,7 +16,7 @@ class RevocationChecker(object):
self.broken = False
if not util.exe_exists("openssl"):
logging.info("openssl not installed, can't check revocation")
logger.info("openssl not installed, can't check revocation")
self.broken = True
return
@ -61,7 +61,7 @@ class RevocationChecker(object):
logger.debug("Querying OCSP for %s", cert_path)
logger.debug(" ".join(cmd))
try:
output, err = util.run_script(cmd, log=logging.debug)
output, err = util.run_script(cmd, log=logger.debug)
except errors.SubprocessError:
logger.info("OCSP check failed for %s (are we offline?)", cert_path)
return False
@ -80,7 +80,7 @@ class RevocationChecker(object):
try:
url, _err = util.run_script(
["openssl", "x509", "-in", cert_path, "-noout", "-ocsp_uri"],
log=logging.debug)
log=logger.debug)
except errors.SubprocessError:
logger.info("Cannot extract OCSP URI from %s", cert_path)
return None, None

View file

@ -1,4 +1,7 @@
"""Tests for certbot.plugins.common."""
import os
import shutil
import tempfile
import unittest
import mock
@ -170,8 +173,16 @@ class TLSSNI01Test(unittest.TestCase):
]
def setUp(self):
self.tempdir = tempfile.mkdtemp()
configurator = mock.MagicMock()
configurator.config.config_dir = os.path.join(self.tempdir, "config")
configurator.config.work_dir = os.path.join(self.tempdir, "work")
from certbot.plugins.common import TLSSNI01
self.sni = TLSSNI01(configurator=mock.MagicMock())
self.sni = TLSSNI01(configurator=configurator)
def tearDown(self):
shutil.rmtree(self.tempdir)
def test_add_chall(self):
self.sni.add_chall(self.achalls[0], 0)
@ -187,6 +198,7 @@ class TLSSNI01Test(unittest.TestCase):
response = challenges.TLSSNI01Response()
achall = mock.MagicMock()
achall.chall.encode.return_value = "token"
key = test_util.load_pyopenssl_private_key("rsa512_key.pem")
achall.response_and_validation.return_value = (
response, (test_util.load_cert("cert.pem"), key))

View file

@ -79,7 +79,7 @@ class PluginEntryPoint(object):
return self._initialized is not None
def init(self, config=None):
"""Memoized plugin inititialization."""
"""Memoized plugin initialization."""
if not self.initialized:
self.entry_point.require() # fetch extras!
self._initialized = self.plugin_cls(config, self.name)
@ -230,7 +230,7 @@ class PluginsRegistry(collections.Mapping):
def available(self):
"""Filter plugins based on availability."""
return self.filter(lambda p_ep: p_ep.available)
# succefully prepared + misconfigured
# successfully prepared + misconfigured
def find_init(self, plugin):
"""Find an initialized plugin.

View file

@ -255,7 +255,7 @@ class PluginsRegistryTest(unittest.TestCase):
def test_find_init(self):
self.assertTrue(self.reg.find_init(mock.Mock()) is None)
self.plugin_ep.initalized = True
self.plugin_ep.initialized = True
self.assertTrue(
self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep)

View file

@ -1,4 +1,4 @@
"""Tests for letsenecrypt.plugins.selection"""
"""Tests for letsencrypt.plugins.selection"""
import sys
import unittest
@ -6,6 +6,7 @@ import mock
import zope.component
from certbot.display import util as display_util
from certbot.tests import util as test_util
from certbot import interfaces
@ -126,14 +127,14 @@ class ChoosePluginTest(unittest.TestCase):
from certbot.plugins.selection import choose_plugin
return choose_plugin(self.plugins, "Question?")
@mock.patch("certbot.plugins.selection.z_util")
@test_util.patch_get_utility("certbot.plugins.selection.z_util")
def test_selection(self, mock_util):
mock_util().menu.side_effect = [(display_util.OK, 0),
(display_util.OK, 1)]
self.assertEqual(self.mock_stand, self._call())
self.assertEqual(mock_util().notification.call_count, 1)
@mock.patch("certbot.plugins.selection.z_util")
@test_util.patch_get_utility("certbot.plugins.selection.z_util")
def test_more_info(self, mock_util):
mock_util().menu.side_effect = [
(display_util.HELP, 0),
@ -144,7 +145,7 @@ class ChoosePluginTest(unittest.TestCase):
self.assertEqual(self.mock_stand, self._call())
self.assertEqual(mock_util().notification.call_count, 2)
@mock.patch("certbot.plugins.selection.z_util")
@test_util.patch_get_utility("certbot.plugins.selection.z_util")
def test_no_choice(self, mock_util):
mock_util().menu.return_value = (display_util.CANCEL, 0)
self.assertTrue(self._call() is None)

View file

@ -18,7 +18,6 @@ from certbot import errors
from certbot import interfaces
from certbot.plugins import common
from certbot.plugins import util
logger = logging.getLogger(__name__)
@ -208,74 +207,38 @@ class Authenticator(common.Plugin):
# pylint: disable=unused-argument,missing-docstring
return self.supported_challenges
def _verify_ports_are_available(self, achalls):
"""Confirm the ports are available to solve all achalls.
:param list achalls: list of
:class:`~certbot.achallenges.AnnotatedChallenge`
:raises .errors.MisconfigurationError: if required port is
unavailable
"""
ports = []
if any(isinstance(ac.chall, challenges.HTTP01) for ac in achalls):
ports.append(self.config.http01_port)
if any(isinstance(ac.chall, challenges.TLSSNI01) for ac in achalls):
ports.append(self.config.tls_sni_01_port)
renewer = (self.config.verb == "renew")
if any(util.already_listening(port, renewer) for port in ports):
raise errors.MisconfigurationError(
"At least one of the required ports is already taken.")
def perform(self, achalls): # pylint: disable=missing-docstring
self._verify_ports_are_available(achalls)
return [self._try_perform_single(achall) for achall in achalls]
try:
return self.perform2(achalls)
except errors.StandaloneBindError as error:
display = zope.component.getUtility(interfaces.IDisplay)
def _try_perform_single(self, achall):
while True:
try:
return self._perform_single(achall)
except errors.StandaloneBindError as error:
_handle_perform_error(error)
if error.socket_error.errno == socket.errno.EACCES:
display.notification(
"Could not bind TCP port {0} because you don't have "
"the appropriate permissions (for example, you "
"aren't running this program as "
"root).".format(error.port), force_interactive=True)
elif error.socket_error.errno == socket.errno.EADDRINUSE:
display.notification(
"Could not bind TCP port {0} because it is already in "
"use by another process on this system (such as a web "
"server). Please stop the program in question and then "
"try again.".format(error.port), force_interactive=True)
else:
raise # XXX: How to handle unknown errors in binding?
def _perform_single(self, achall):
if isinstance(achall.chall, challenges.HTTP01):
server, response = self._perform_http_01(achall)
else: # tls-sni-01
server, response = self._perform_tls_sni_01(achall)
self.served[server].add(achall)
return response
def perform2(self, achalls):
"""Perform achallenges without IDisplay interaction."""
responses = []
def _perform_http_01(self, achall):
server = self.servers.run(self.config.http01_port, challenges.HTTP01)
response, validation = achall.response_and_validation()
resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource(
chall=achall.chall, response=response, validation=validation)
self.http_01_resources.add(resource)
return server, response
for achall in achalls:
if isinstance(achall.chall, challenges.HTTP01):
server = self.servers.run(
self.config.http01_port, challenges.HTTP01)
response, validation = achall.response_and_validation()
self.http_01_resources.add(
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
chall=achall.chall, response=response,
validation=validation))
else: # tls-sni-01
server = self.servers.run(
self.config.tls_sni_01_port, challenges.TLSSNI01)
response, (cert, _) = achall.response_and_validation(
cert_key=self.key)
self.certs[response.z_domain] = (self.key, cert)
self.served[server].add(achall)
responses.append(response)
return responses
def _perform_tls_sni_01(self, achall):
port = self.config.tls_sni_01_port
server = self.servers.run(port, challenges.TLSSNI01)
response, (cert, _) = achall.response_and_validation(cert_key=self.key)
self.certs[response.z_domain] = (self.key, cert)
return server, response
def cleanup(self, achalls): # pylint: disable=missing-docstring
# reduce self.served and close servers if none challenges are served
@ -286,3 +249,25 @@ class Authenticator(common.Plugin):
for port, server in six.iteritems(self.servers.running()):
if not self.served[server]:
self.servers.stop(port)
def _handle_perform_error(error):
if error.socket_error.errno == socket.errno.EACCES:
raise errors.PluginError(
"Could not bind TCP port {0} because you don't have "
"the appropriate permissions (for example, you "
"aren't running this program as "
"root).".format(error.port))
elif error.socket_error.errno == socket.errno.EADDRINUSE:
display = zope.component.getUtility(interfaces.IDisplay)
msg = (
"Could not bind TCP port {0} because it is already in "
"use by another process on this system (such as a web "
"server). Please stop the program in question and "
"then try again.".format(error.port))
should_retry = display.yesno(msg, "Retry",
"Cancel", default=False)
if not should_retry:
raise errors.PluginError(msg)
else:
raise

View file

@ -8,11 +8,9 @@ import six
from acme import challenges
from acme import jose
from acme import standalone as acme_standalone
from certbot import achallenges
from certbot import errors
from certbot import interfaces
from certbot.tests import acme_util
from certbot.tests import util as test_util
@ -114,6 +112,7 @@ def get_open_port():
open_socket.close()
return port
class AuthenticatorTest(unittest.TestCase):
"""Tests for certbot.plugins.standalone.Authenticator."""
@ -124,6 +123,7 @@ class AuthenticatorTest(unittest.TestCase):
tls_sni_01_port=get_open_port(), http01_port=get_open_port(),
standalone_supported_challenges="tls-sni-01,http-01")
self.auth = Authenticator(self.config, name="standalone")
self.auth.servers = mock.MagicMock()
def test_supported_challenges(self):
self.assertEqual(self.auth.supported_challenges,
@ -146,6 +146,52 @@ class AuthenticatorTest(unittest.TestCase):
self.assertEqual(self.auth.get_chall_pref(domain=None),
[challenges.TLSSNI01])
def test_perform(self):
achalls = self._get_achalls()
response = self.auth.perform(achalls)
expected = [achall.response(achall.account_key) for achall in achalls]
self.assertEqual(response, expected)
@test_util.patch_get_utility()
def test_perform_eaddrinuse_retry(self, mock_get_utility):
errno = socket.errno.EADDRINUSE
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()]
mock_yesno = mock_get_utility.return_value.yesno
mock_yesno.return_value = True
self.test_perform()
self._assert_correct_yesno_call(mock_yesno)
@test_util.patch_get_utility()
def test_perform_eaddrinuse_no_retry(self, mock_get_utility):
mock_yesno = mock_get_utility.return_value.yesno
mock_yesno.return_value = False
errno = socket.errno.EADDRINUSE
self.assertRaises(errors.PluginError, self._fail_perform, errno)
self._assert_correct_yesno_call(mock_yesno)
def _assert_correct_yesno_call(self, mock_yesno):
yesno_args, yesno_kwargs = mock_yesno.call_args
self.assertTrue("in use" in yesno_args[0])
self.assertFalse(yesno_kwargs.get("default", True))
def test_perform_eacces(self):
errno = socket.errno.EACCES
self.assertRaises(errors.PluginError, self._fail_perform, errno)
def test_perform_unexpected_socket_error(self):
errno = socket.errno.ENOTCONN
self.assertRaises(
errors.StandaloneBindError, self._fail_perform, errno)
def _fail_perform(self, errno):
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
self.auth.servers.run.side_effect = error
self.auth.perform(self._get_achalls())
@classmethod
def _get_achalls(cls):
domain = b'localhost'
@ -157,84 +203,7 @@ class AuthenticatorTest(unittest.TestCase):
return [http_01, tls_sni_01]
@mock.patch("certbot.plugins.standalone.util")
def test_perform_already_listening(self, mock_util):
http_01, tls_sni_01 = self._get_achalls()
for achall, port in ((http_01, self.config.http01_port,),
(tls_sni_01, self.config.tls_sni_01_port)):
mock_util.already_listening.return_value = True
self.assertRaises(
errors.MisconfigurationError, self.auth.perform, [achall])
mock_util.already_listening.assert_called_once_with(port, False)
mock_util.already_listening.reset_mock()
@test_util.patch_get_utility()
def test_perform(self, unused_mock_get_utility):
achalls = self._get_achalls()
self.auth.perform2 = mock.Mock(return_value=mock.sentinel.responses)
self.assertEqual(mock.sentinel.responses, self.auth.perform(achalls))
self.auth.perform2.assert_called_once_with(achalls)
@test_util.patch_get_utility()
def _test_perform_bind_errors(self, errno, achalls, mock_get_utility):
port = get_open_port()
def _perform2(unused_achalls):
raise errors.StandaloneBindError(mock.Mock(errno=errno), port)
self.auth.perform2 = mock.MagicMock(side_effect=_perform2)
self.auth.perform(achalls)
mock_get_utility.assert_called_once_with(interfaces.IDisplay)
notification = mock_get_utility.return_value.notification
self.assertEqual(1, notification.call_count)
self.assertTrue(str(port) in notification.call_args[0][0])
def test_perform_eacces(self):
# pylint: disable=no-value-for-parameter
self._test_perform_bind_errors(socket.errno.EACCES, [])
def test_perform_eaddrinuse(self):
# pylint: disable=no-value-for-parameter
self._test_perform_bind_errors(socket.errno.EADDRINUSE, [])
def test_perfom_unknown_bind_error(self):
self.assertRaises(
errors.StandaloneBindError, self._test_perform_bind_errors,
socket.errno.ENOTCONN, [])
def test_perform2(self):
http_01, tls_sni_01 = self._get_achalls()
self.auth.servers = mock.MagicMock()
def _run(port, tls): # pylint: disable=unused-argument
return "server{0}".format(port)
self.auth.servers.run.side_effect = _run
responses = self.auth.perform2([http_01, tls_sni_01])
self.assertTrue(isinstance(responses, list))
self.assertEqual(2, len(responses))
self.assertTrue(isinstance(responses[0], challenges.HTTP01Response))
self.assertTrue(isinstance(responses[1], challenges.TLSSNI01Response))
self.assertEqual(self.auth.servers.run.mock_calls, [
mock.call(self.config.http01_port, challenges.HTTP01),
mock.call(self.config.tls_sni_01_port, challenges.TLSSNI01),
])
self.assertEqual(self.auth.served, {
"server" + str(self.config.tls_sni_01_port): set([tls_sni_01]),
"server" + str(self.config.http01_port): set([http_01]),
})
self.assertEqual(1, len(self.auth.http_01_resources))
self.assertEqual(1, len(self.auth.certs))
self.assertEqual(list(self.auth.http_01_resources), [
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
acme_util.HTTP01, responses[0], mock.ANY)])
def test_cleanup(self):
self.auth.servers = mock.Mock()
self.auth.servers.running.return_value = {
1: "server1",
2: "server2",

View file

@ -1,34 +1,11 @@
"""Plugin utilities."""
import logging
import os
import socket
import zope.component
from acme import errors as acme_errors
from acme import util as acme_util
from certbot import interfaces
from certbot import util
PSUTIL_REQUIREMENT = "psutil>=2.2.1"
try:
acme_util.activate(PSUTIL_REQUIREMENT)
import psutil # pragma: no cover
USE_PSUTIL = True
except acme_errors.DependencyError: # pragma: no cover
USE_PSUTIL = False
logger = logging.getLogger(__name__)
RENEWER_EXTRA_MSG = (
" For automated renewal, you may want to use a script that stops"
" and starts your webserver. You can find an example at"
" https://certbot.eff.org/docs/using.html#renewal ."
" Alternatively you can use the webroot plugin to renew without"
" needing to stop and start your webserver.")
def path_surgery(cmd):
"""Attempt to perform PATH surgery to find cmd
@ -59,105 +36,3 @@ def path_surgery(cmd):
logger.warning("Failed to find %s in%s PATH: %s", cmd,
expanded, path)
return False
def already_listening(port, renewer=False):
"""Check if a process is already listening on the port.
If so, also tell the user via a display notification.
.. warning::
On some operating systems, this function can only usefully be
run as root.
:param int port: The TCP port in question.
:returns: True or False.
"""
if USE_PSUTIL:
return already_listening_psutil(port, renewer=renewer)
else:
logger.debug("Psutil not found, using simple socket check.")
return already_listening_socket(port, renewer=renewer)
def already_listening_socket(port, renewer=False):
"""Simple socket based check to find out if port is already in use
:param int port: The TCP port in question.
:returns: True or False
"""
try:
testsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
testsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
testsocket.bind(("", port))
except socket.error:
display = zope.component.getUtility(interfaces.IDisplay)
extra = ""
if renewer:
extra = RENEWER_EXTRA_MSG
display.notification(
"Port {0} is already in use by another process. This will "
"prevent us from binding to that port. Please stop the "
"process that is populating the port in question and try "
"again. {1}".format(port, extra), force_interactive=True)
return True
finally:
testsocket.close()
except socket.error:
pass
return False
def already_listening_psutil(port, renewer=False):
"""Psutil variant of the open port check
:param int port: The TCP port in question.
:returns: True or False.
"""
try:
net_connections = psutil.net_connections()
except psutil.AccessDenied as error:
logger.info("Access denied when trying to list network "
"connections: %s. Are you root?", error)
# this function is just a pre-check that often causes false
# positives and problems in testing (c.f. #680 on Mac, #255
# generally); we will fail later in bind() anyway
return False
listeners = [conn.pid for conn in net_connections
if conn.status == 'LISTEN' and
conn.type == socket.SOCK_STREAM and
conn.laddr[1] == port]
try:
if listeners and listeners[0] is not None:
# conn.pid may be None if the current process doesn't have
# permission to identify the listening process! Additionally,
# listeners may have more than one element if separate
# sockets have bound the same port on separate interfaces.
# We currently only have UI to notify the user about one
# of them at a time.
pid = listeners[0]
name = psutil.Process(pid).name()
display = zope.component.getUtility(interfaces.IDisplay)
extra = ""
if renewer:
extra = RENEWER_EXTRA_MSG
display.notification(
"The program {0} (process ID {1}) is already listening "
"on TCP port {2}. This will prevent us from binding to "
"that port. Please stop the {0} program temporarily "
"and then try again.{3}".format(name, pid, port, extra),
force_interactive=True)
return True
except (psutil.NoSuchProcess, psutil.AccessDenied):
# Perhaps the result of a race where the process could have
# exited or relinquished the port (NoSuchProcess), or the result
# of an OS policy where we're not allowed to look up the process
# name (AccessDenied).
pass
return False

View file

@ -1,13 +1,9 @@
"""Tests for certbot.plugins.util."""
import os
import socket
import unittest
import mock
from certbot.plugins.util import PSUTIL_REQUIREMENT
from certbot.tests import util as test_util
class PathSurgeryTest(unittest.TestCase):
"""Tests for certbot.plugins.path_surgery."""
@ -34,140 +30,5 @@ class PathSurgeryTest(unittest.TestCase):
self.assertTrue("/tmp" in os.environ["PATH"])
class AlreadyListeningTest(unittest.TestCase):
"""Tests for certbot.plugins.already_listening."""
@classmethod
def _call(cls, *args, **kwargs):
from certbot.plugins.util import already_listening
return already_listening(*args, **kwargs)
class AlreadyListeningTestNoPsutil(AlreadyListeningTest):
"""Tests for certbot.plugins.already_listening when
psutil is not available"""
@classmethod
def _call(cls, *args, **kwargs):
with mock.patch("certbot.plugins.util.USE_PSUTIL", False):
return super(
AlreadyListeningTestNoPsutil, cls)._call(*args, **kwargs)
@test_util.patch_get_utility()
def test_ports_available(self, mock_getutil):
# Ensure we don't get error
with mock.patch("socket.socket.bind"):
self.assertFalse(self._call(80))
self.assertFalse(self._call(80, True))
self.assertEqual(mock_getutil.call_count, 0)
@test_util.patch_get_utility()
def test_ports_blocked(self, mock_getutil):
with mock.patch("certbot.plugins.util.socket.socket.bind") as mock_bind:
mock_bind.side_effect = socket.error
self.assertTrue(self._call(80))
self.assertTrue(self._call(80, True))
with mock.patch("certbot.plugins.util.socket.socket") as mock_socket:
mock_socket.side_effect = socket.error
self.assertFalse(self._call(80))
self.assertEqual(mock_getutil.call_count, 2)
@test_util.skip_unless(test_util.requirement_available(PSUTIL_REQUIREMENT),
"optional dependency psutil is not available")
class AlreadyListeningTestPsutil(AlreadyListeningTest):
"""Tests for certbot.plugins.already_listening."""
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_race_condition(self, mock_get_utility, mock_process, mock_net):
# This tests a race condition, or permission problem, or OS
# incompatibility in which, for some reason, no process name can be
# found to match the identified listening PID.
import psutil
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
raddr=(), status="LISTEN", pid=4416)]
mock_net.return_value = conns
mock_process.side_effect = psutil.NoSuchProcess("No such PID")
# We simulate being unable to find the process name of PID 4416,
# which results in returning False.
self.assertFalse(self._call(17))
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
mock_process.assert_called_once_with(4416)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_not_listening(self, mock_get_utility, mock_process, mock_net):
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None)]
mock_net.return_value = conns
mock_process.name.return_value = "inetd"
self.assertFalse(self._call(17))
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
self.assertEqual(mock_process.call_count, 0)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_listening_ipv4(self, mock_get_utility, mock_process, mock_net):
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
raddr=(), status="LISTEN", pid=4416)]
mock_net.return_value = conns
mock_process.name.return_value = "inetd"
result = self._call(17, True)
self.assertTrue(result)
self.assertEqual(mock_get_utility.call_count, 1)
mock_process.assert_called_once_with(4416)
@mock.patch("certbot.plugins.util.psutil.net_connections")
@mock.patch("certbot.plugins.util.psutil.Process")
@test_util.patch_get_utility()
def test_listening_ipv6(self, mock_get_utility, mock_process, mock_net):
from psutil._common import sconn
conns = [
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
raddr=(), status="LISTEN", pid=None),
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
sconn(fd=3, family=10, type=1, laddr=("::", 12345), raddr=(),
status="LISTEN", pid=4420),
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
raddr=(), status="LISTEN", pid=4416)]
mock_net.return_value = conns
mock_process.name.return_value = "inetd"
result = self._call(12345)
self.assertTrue(result)
self.assertEqual(mock_get_utility.call_count, 1)
mock_process.assert_called_once_with(4420)
@mock.patch("certbot.plugins.util.psutil.net_connections")
def test_access_denied_exception(self, mock_net):
import psutil
mock_net.side_effect = psutil.AccessDenied("")
self.assertFalse(self._call(12345))
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -35,7 +35,7 @@ INT_CONFIG_ITEMS = ["rsa_key_size", "tls_sni_01_port", "http01_port"]
BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names"]
CONFIG_ITEMS = set(itertools.chain(
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS))
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',)))
def _reconstitute(config, full_path):
@ -165,6 +165,7 @@ def restore_required_config_elements(config, renewalparams):
"""
required_items = itertools.chain(
(("pref_challs", _restore_pref_challs),),
six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)),
six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)),
six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str)))
@ -174,6 +175,28 @@ def restore_required_config_elements(config, renewalparams):
setattr(config.namespace, item_name, value)
def _restore_pref_challs(unused_name, value):
"""Restores preferred challenges from a renewal config file.
If value is a `str`, it should be a single challenge type.
:param str unused_name: option name
:param value: option value
:type value: `list` of `str` or `str`
:returns: converted option value to be stored in the runtime config
:rtype: `list` of `str`
:raises errors.Error: if value can't be converted to an bool
"""
# If pref_challs has only one element, configobj saves the value
# with a trailing comma so it's parsed as a list. If this comma is
# removed by the user, the value is parsed as a str.
value = [value] if isinstance(value, str) else value
return cli.parse_preferred_challenges(value)
def _restore_bool(name, value):
"""Restores an boolean key-value pair from a renewal config file.
@ -263,12 +286,14 @@ def _avoid_invalidating_lineage(config, lineage, original_server):
"unless you use the --break-my-certs flag!".format(names))
def renew_cert(config, le_client, lineage):
def renew_cert(config, domains, le_client, lineage):
"Renew a certificate lineage."
renewal_params = lineage.configuration["renewalparams"]
original_server = renewal_params.get("server", cli.flag_default("server"))
_avoid_invalidating_lineage(config, lineage, original_server)
new_certr, new_chain, new_key, _ = le_client.obtain_certificate(lineage.names())
if not domains:
domains = lineage.names()
new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains)
if config.dry_run:
logger.debug("Dry run: skipping updating lineage at %s",
os.path.dirname(lineage.cert))
@ -281,7 +306,7 @@ def renew_cert(config, le_client, lineage):
lineage.save_successor(prior_version, new_cert, new_key.pem, new_chain, config)
lineage.update_all_links_to(lineage.latest_common_version())
hooks.renew_hook(config, lineage.names(), lineage.live_dir)
hooks.renew_hook(config, domains, lineage.live_dir)
def report(msgs, category):
@ -385,7 +410,12 @@ def handle_renewal_request(config):
if should_renew(lineage_config, renewal_candidate):
plugins = plugins_disco.PluginsRegistry.find_all()
from certbot import main
main.obtain_cert(lineage_config, plugins, renewal_candidate)
# domains have been restored into lineage_config by reconstitute
# but they're unnecessary anyway because renew_cert here
# will just grab them from the certificate
# we already know it's time to renew based on should_renew
# and we have a lineage in renewal_candidate
main.renew_cert(lineage_config, plugins, renewal_candidate)
renew_successes.append(renewal_candidate.fullchain)
else:
renew_skipped.append(renewal_candidate.fullchain)

View file

@ -391,6 +391,26 @@ class RenewableCert(object):
self._update_symlinks()
self._check_symlinks()
@property
def key_path(self):
"""Duck type for self.privkey"""
return self.privkey
@property
def cert_path(self):
"""Duck type for self.cert"""
return self.cert
@property
def chain_path(self):
"""Duck type for self.chain"""
return self.chain
@property
def fullchain_path(self):
"""Duck type for self.fullchain"""
return self.fullchain
@property
def target_expiry(self):
"""The current target certificate's expiration datetime
@ -716,7 +736,7 @@ class RenewableCert(object):
:returns: ``True`` if there is a complete version of this
lineage with a larger version number than the current
version, and ``False`` otherwis
version, and ``False`` otherwise
:rtype: bool
"""

View file

@ -62,13 +62,10 @@ class ReportNewAccountTest(unittest.TestCase):
def setUp(self):
self.config = mock.MagicMock(config_dir="/etc/letsencrypt")
reg = messages.Registration.from_data(email="rhino@jungle.io")
self.acc = mock.MagicMock(regr=messages.RegistrationResource(
uri=None, new_authzr_uri=None, body=reg))
def _call(self):
from certbot.account import report_new_account
report_new_account(self.acc, self.config)
report_new_account(self.config)
@mock.patch("certbot.account.zope.component.queryUtility")
def test_no_reporter(self, mock_zope):
@ -80,8 +77,6 @@ class ReportNewAccountTest(unittest.TestCase):
self._call()
call_list = mock_zope().add_message.call_args_list
self.assertTrue(self.config.config_dir in call_list[0][0][0])
self.assertTrue(
", ".join(self.acc.regr.body.emails) in call_list[1][0][0])
class AccountMemoryStorageTest(unittest.TestCase):
@ -190,6 +185,14 @@ class AccountFileStorageTest(unittest.TestCase):
self.assertRaises(
errors.AccountStorageError, self.storage.save, self.acc)
def test_delete(self):
self.storage.save(self.acc)
self.storage.delete(self.acc.id)
self.assertRaises(errors.AccountNotFound, self.storage.load, self.acc.id)
def test_delete_no_account(self):
self.assertRaises(errors.AccountNotFound, self.storage.delete, self.acc.id)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -176,7 +176,8 @@ class GetAuthorizationsTest(unittest.TestCase):
mock_poll.side_effect = self._validate_all
self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01)
self.handler.pref_challs.extend((challenges.HTTP01, challenges.DNS01,))
self.handler.pref_challs.extend((challenges.HTTP01.typ,
challenges.DNS01.typ,))
self.handler.get_authorizations(["0"])
@ -187,7 +188,7 @@ class GetAuthorizationsTest(unittest.TestCase):
def test_preferred_challenges_not_supported(self):
self.mock_net.request_domain_challenges.side_effect = functools.partial(
gen_dom_authzr, challs=acme_util.CHALLENGES)
self.handler.pref_challs.append(challenges.HTTP01)
self.handler.pref_challs.append(challenges.HTTP01.typ)
self.assertRaises(
errors.AuthorizationError, self.handler.get_authorizations, ["0"])

View file

@ -268,11 +268,11 @@ class LineageForCertnameTest(BaseCertManagerTest):
"""Tests for certbot.cert_manager.lineage_for_certname"""
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.renewal_file_for_certname')
@mock.patch('certbot.storage.RenewableCert')
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_files,
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_renewal_conf_file.return_value = "somefile.conf"
mock_match = mock.Mock(lineagename="example.com")
mock_renewable_cert.return_value = mock_match
from certbot import cert_manager
@ -281,13 +281,20 @@ class LineageForCertnameTest(BaseCertManagerTest):
self.assertTrue(mock_make_or_verify_dir.called)
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.RenewableCert')
def test_no_match(self, mock_renewable_cert, mock_renewal_conf_files,
@mock.patch('certbot.storage.renewal_file_for_certname')
def test_no_match(self, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_match = mock.Mock(lineagename="other.com")
mock_renewable_cert.return_value = mock_match
mock_renewal_conf_file.return_value = "other.com.conf"
from certbot import cert_manager
self.assertEqual(cert_manager.lineage_for_certname(self.cli_config, "example.com"),
None)
self.assertTrue(mock_make_or_verify_dir.called)
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_file_for_certname')
def test_no_renewal_file(self, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_file.side_effect = errors.CertStorageError()
from certbot import cert_manager
self.assertEqual(cert_manager.lineage_for_certname(self.cli_config, "example.com"),
None)
@ -298,11 +305,11 @@ class DomainsForCertnameTest(BaseCertManagerTest):
"""Tests for certbot.cert_manager.domains_for_certname"""
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.renewal_file_for_certname')
@mock.patch('certbot.storage.RenewableCert')
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_files,
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_renewal_conf_file.return_value = "somefile.conf"
mock_match = mock.Mock(lineagename="example.com")
domains = ["example.com", "example.org"]
mock_match.names.return_value = domains
@ -313,15 +320,10 @@ class DomainsForCertnameTest(BaseCertManagerTest):
self.assertTrue(mock_make_or_verify_dir.called)
@mock.patch('certbot.util.make_or_verify_dir')
@mock.patch('certbot.storage.renewal_conf_files')
@mock.patch('certbot.storage.RenewableCert')
def test_no_match(self, mock_renewable_cert, mock_renewal_conf_files,
@mock.patch('certbot.storage.renewal_file_for_certname')
def test_no_match(self, mock_renewal_conf_file,
mock_make_or_verify_dir):
mock_renewal_conf_files.return_value = ["somefile.conf"]
mock_match = mock.Mock(lineagename="example.com")
domains = ["example.com", "example.org"]
mock_match.names.return_value = domains
mock_renewable_cert.return_value = mock_match
mock_renewal_conf_file.return_value = "somefile.conf"
from certbot import cert_manager
self.assertEqual(cert_manager.domains_for_certname(self.cli_config, "other.com"),
None)

View file

@ -1,22 +1,22 @@
"""Tests for certbot.cli."""
import argparse
import functools
import unittest
import os
import tempfile
import six
import mock
import six
from six.moves import reload_module # pylint: disable=import-error
from acme import challenges
from certbot import cli
from certbot import constants
from certbot import errors
from certbot.plugins import disco
def reset_set_by_cli():
'''Reset the state of the `set_by_cli` function'''
cli.set_by_cli.detector = None
PLUGINS = disco.PluginsRegistry.find_all()
class TestReadFile(unittest.TestCase):
'''Test cli.read_file'''
@ -43,16 +43,16 @@ class ParseTest(unittest.TestCase):
_multiprocess_can_split_ = True
@classmethod
def setUpClass(cls):
cls.plugins = disco.PluginsRegistry.find_all()
cls.parse = functools.partial(cli.prepare_and_parse_args, cls.plugins)
def setUp(self):
reset_set_by_cli()
reload_module(cli)
@staticmethod
def parse(*args, **kwargs):
"""Get result of cli.prepare_and_parse_args."""
return cli.prepare_and_parse_args(PLUGINS, *args, **kwargs)
def _help_output(self, args):
"Run a command, and return the ouput string for scrutiny"
"Run a command, and return the output string for scrutiny"
output = six.StringIO()
with mock.patch('certbot.main.sys.stdout', new=output):
@ -60,6 +60,11 @@ class ParseTest(unittest.TestCase):
self.assertRaises(SystemExit, self.parse, args, output)
return output.getvalue()
def test_no_args(self):
namespace = self.parse([])
for d in ('config_dir', 'logs_dir', 'work_dir'):
self.assertEqual(getattr(namespace, d), cli.flag_default(d))
def test_install_abspath(self):
cert = 'cert'
key = 'key'
@ -88,7 +93,7 @@ class ParseTest(unittest.TestCase):
self.assertTrue("{0}" not in out)
out = self._help_output(['-h', 'nginx'])
if "nginx" in self.plugins:
if "nginx" in PLUGINS:
# may be false while building distributions without plugins
self.assertTrue("--nginx-ctl" in out)
self.assertTrue("--webroot-path" not in out)
@ -96,7 +101,7 @@ class ParseTest(unittest.TestCase):
out = self._help_output(['-h'])
self.assertTrue("letsencrypt-auto" not in out) # test cli.cli_command
if "nginx" in self.plugins:
if "nginx" in PLUGINS:
self.assertTrue("Use the Nginx plugin" in out)
else:
self.assertTrue("(the certbot nginx plugin is not" in out)
@ -121,6 +126,7 @@ class ParseTest(unittest.TestCase):
out = self._help_output(['--help', 'revoke'])
self.assertTrue("--cert-path" in out)
self.assertTrue("--key-path" in out)
self.assertTrue("--reason" in out)
out = self._help_output(['-h', 'config_changes'])
self.assertTrue("--cert-path" not in out)
@ -132,6 +138,26 @@ class ParseTest(unittest.TestCase):
self.assertTrue("%s" not in out)
self.assertTrue("{0}" not in out)
def test_help_no_dashes(self):
self._help_output(['help']) # assert SystemExit is raised here
out = self._help_output(['help', 'all'])
self.assertTrue("--configurator" in out)
self.assertTrue("how a cert is deployed" in out)
self.assertTrue("--webroot-path" in out)
self.assertTrue("--text" not in out)
self.assertTrue("--dialog" not in out)
self.assertTrue("%s" not in out)
self.assertTrue("{0}" not in out)
out = self._help_output(['help', 'install'])
self.assertTrue("--cert-path" in out)
self.assertTrue("--key-path" in out)
out = self._help_output(['help', 'revoke'])
self.assertTrue("--cert-path" in out)
self.assertTrue("--key-path" in out)
def test_parse_domains(self):
short_args = ['-d', 'example.com']
namespace = self.parse(short_args)
@ -159,12 +185,12 @@ class ParseTest(unittest.TestCase):
self.assertEqual(namespace.domains, ['example.com', 'another.net'])
def test_preferred_challenges(self):
from acme.challenges import HTTP01, TLSSNI01, DNS01
short_args = ['--preferred-challenges', 'http, tls-sni-01, dns']
namespace = self.parse(short_args)
self.assertEqual(namespace.pref_challs, [HTTP01, TLSSNI01, DNS01])
expected = [challenges.HTTP01.typ,
challenges.TLSSNI01.typ, challenges.DNS01.typ]
self.assertEqual(namespace.pref_challs, expected)
short_args = ['--preferred-challenges', 'jumping-over-the-moon']
self.assertRaises(argparse.ArgumentTypeError, self.parse, short_args)
@ -262,6 +288,14 @@ class ParseTest(unittest.TestCase):
self.assertFalse(cli.option_was_set(
config_dir_option, cli.flag_default(config_dir_option)))
def test_encode_revocation_reason(self):
for reason, code in constants.REVOCATION_REASONS.items():
namespace = self.parse(['--reason', reason])
self.assertEqual(namespace.reason, code)
for reason, code in constants.REVOCATION_REASONS.items():
namespace = self.parse(['--reason', reason.upper()])
self.assertEqual(namespace.reason, code)
def test_force_interactive(self):
self.assertRaises(
errors.Error, self.parse, "renew --force-interactive".split())

View file

@ -44,20 +44,25 @@ class RegisterTest(unittest.TestCase):
def test_no_tos(self):
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client.register().terms_of_service = "http://tos"
with mock.patch("certbot.account.report_new_account"):
self.tos_cb.return_value = False
self.assertRaises(errors.Error, self._call)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
with mock.patch("certbot.account.report_new_account"):
self.tos_cb.return_value = False
self.assertRaises(errors.Error, self._call)
self.assertFalse(mock_handle.called)
self.tos_cb.return_value = True
self._call()
self.tos_cb.return_value = True
self._call()
self.assertTrue(mock_handle.called)
self.tos_cb = None
self._call()
self.tos_cb = None
self._call()
self.assertEqual(mock_handle.call_count, 2)
def test_it(self):
with mock.patch("certbot.client.acme_client.Client"):
with mock.patch("certbot.account.report_new_account"):
self._call()
with mock.patch("certbot.eff.handle_subscription"):
self._call()
@mock.patch("certbot.account.report_new_account")
@mock.patch("certbot.client.display_ops.get_email")
@ -67,9 +72,11 @@ class RegisterTest(unittest.TestCase):
msg = "DNS problem: NXDOMAIN looking up MX for example.com"
mx_err = messages.Error.with_code('invalidContact', detail=msg)
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self._call()
self.assertEqual(mock_get_email.call_count, 1)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self._call()
self.assertEqual(mock_get_email.call_count, 1)
self.assertTrue(mock_handle.called)
@mock.patch("certbot.account.report_new_account")
def test_email_invalid_noninteractive(self, _rep):
@ -77,8 +84,9 @@ class RegisterTest(unittest.TestCase):
msg = "DNS problem: NXDOMAIN looking up MX for example.com"
mx_err = messages.Error.with_code('invalidContact', detail=msg)
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(errors.Error, self._call)
with mock.patch("certbot.eff.handle_subscription"):
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(errors.Error, self._call)
def test_needs_email(self):
self.config.email = None
@ -86,21 +94,25 @@ class RegisterTest(unittest.TestCase):
@mock.patch("certbot.client.logger")
def test_without_email(self, mock_logger):
with mock.patch("certbot.client.acme_client.Client"):
with mock.patch("certbot.account.report_new_account"):
self.config.email = None
self.config.register_unsafely_without_email = True
self.config.dry_run = False
self._call()
mock_logger.warning.assert_called_once_with(mock.ANY)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
with mock.patch("certbot.client.acme_client.Client"):
with mock.patch("certbot.account.report_new_account"):
self.config.email = None
self.config.register_unsafely_without_email = True
self.config.dry_run = False
self._call()
mock_logger.warning.assert_called_once_with(mock.ANY)
self.assertTrue(mock_handle.called)
def test_unsupported_error(self):
from acme import messages
msg = "Test"
mx_err = messages.Error(detail=msg, typ="malformed", title="title")
with mock.patch("certbot.client.acme_client.Client") as mock_client:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(messages.Error, self._call)
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
self.assertRaises(messages.Error, self._call)
self.assertFalse(mock_handle.called)
class ClientTestCommon(unittest.TestCase):

View file

@ -12,7 +12,8 @@ class NamespaceConfigTest(unittest.TestCase):
def setUp(self):
self.namespace = mock.MagicMock(
config_dir='/tmp/config', work_dir='/tmp/foo', foo='bar',
config_dir='/tmp/config', work_dir='/tmp/foo',
logs_dir="/tmp/bar", foo='bar',
server='https://acme-server.org:443/new',
tls_sni_01_port=1234, http01_port=4321)
from certbot.configuration import NamespaceConfig

View file

@ -336,8 +336,8 @@ class CertLoaderTest(unittest.TestCase):
from certbot.crypto_util import pyopenssl_load_certificate
cert, file_type = pyopenssl_load_certificate(CERT)
self.assertEqual(cert.digest('sha1'),
OpenSSL.crypto.load_certificate(file_type, CERT).digest('sha1'))
self.assertEqual(cert.digest('sha256'),
OpenSSL.crypto.load_certificate(file_type, CERT).digest('sha256'))
def test_load_invalid_cert(self):
from certbot.crypto_util import pyopenssl_load_certificate

View file

@ -115,17 +115,17 @@ class ChooseAccountTest(unittest.TestCase):
from certbot.display import ops
return ops.choose_account(accounts)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_one(self, mock_util):
mock_util().menu.return_value = (display_util.OK, 0)
self.assertEqual(self._call([self.acc1]), self.acc1)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_two(self, mock_util):
mock_util().menu.return_value = (display_util.OK, 1)
self.assertEqual(self._call([self.acc1, self.acc2]), self.acc2)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_cancel(self, mock_util):
mock_util().menu.return_value = (display_util.CANCEL, 1)
self.assertTrue(self._call([self.acc1, self.acc2]) is None)
@ -216,12 +216,12 @@ class ChooseNamesTest(unittest.TestCase):
self._call(None)
self.assertEqual(mock_manual.call_count, 1)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_no_installer_cancel(self, mock_util):
mock_util().input.return_value = (display_util.CANCEL, [])
self.assertEqual(self._call(None), [])
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_no_names_choose(self, mock_util):
self.mock_install().get_all_names.return_value = set()
domain = "example.com"
@ -272,7 +272,7 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(_sort_names(to_sort), sortd)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_filter_names_valid_return(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (display_util.OK, ["example.com"])
@ -281,14 +281,14 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(names, ["example.com"])
self.assertEqual(mock_util().checklist.call_count, 1)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_filter_names_nothing_selected(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (display_util.OK, [])
self.assertEqual(self._call(self.mock_install), [])
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_filter_names_cancel(self, mock_util):
self.mock_install.get_all_names.return_value = set(["example.com"])
mock_util().checklist.return_value = (
@ -307,7 +307,7 @@ class ChooseNamesTest(unittest.TestCase):
self.assertEqual(get_valid_domains(all_invalid), [])
self.assertEqual(len(get_valid_domains(two_valid)), 2)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_choose_manually(self, mock_util):
from certbot.display.ops import _choose_names_manually
# No retry
@ -350,7 +350,7 @@ class SuccessInstallationTest(unittest.TestCase):
from certbot.display.ops import success_installation
success_installation(names)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_success_installation(self, mock_util):
mock_util().notification.return_value = None
names = ["example.com", "abc.com"]
@ -372,7 +372,7 @@ class SuccessRenewalTest(unittest.TestCase):
from certbot.display.ops import success_renewal
success_renewal(names)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_success_renewal(self, mock_util):
mock_util().notification.return_value = None
names = ["example.com", "abc.com"]
@ -393,12 +393,16 @@ class SuccessRevocationTest(unittest.TestCase):
from certbot.display.ops import success_revocation
success_revocation(path)
@mock.patch("certbot.display.ops.z_util")
@test_util.patch_get_utility("certbot.display.ops.z_util")
def test_success_revocation(self, mock_util):
mock_util().notification.return_value = None
path = "/path/to/cert.pem"
self._call(path)
mock_util().notification.assert_called_once()
mock_util().notification.assert_called_once_with(
"Congratulations! You have successfully revoked the certificate "
"that was located at {0}{1}{1}".format(
path,
os.linesep), pause=False)
self.assertTrue(path in mock_util().notification.call_args[0][0])
if __name__ == "__main__":

135
certbot/tests/eff_test.py Normal file
View file

@ -0,0 +1,135 @@
"""Tests for certbot.eff."""
import unittest
import mock
from certbot import constants
from certbot.tests import util
class HandleSubscriptionTest(unittest.TestCase):
"""Tests for certbot.eff.handle_subscription."""
def setUp(self):
self.email = 'certbot@example.org'
self.config = mock.Mock(email=self.email, eff_email=None)
def _call(self):
from certbot.eff import handle_subscription
return handle_subscription(self.config)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_failure(self, mock_subscribe, mock_get_utility):
self.config.email = None
self.config.eff_email = True
self._call()
self.assertFalse(mock_subscribe.called)
self.assertFalse(mock_get_utility().yesno.called)
actual = mock_get_utility().add_message.call_args[0][0]
expected_part = "because you didn't provide an e-mail address"
self.assertTrue(expected_part in actual)
@mock.patch('certbot.eff.subscribe')
def test_no_subscribe_with_no_prompt(self, mock_subscribe):
self.config.eff_email = False
with util.patch_get_utility() as mock_get_utility:
self._call()
self.assertFalse(mock_subscribe.called)
self._assert_no_get_utility_calls(mock_get_utility)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility):
self.config.eff_email = True
self._call()
self._assert_subscribed(mock_subscribe)
self._assert_no_get_utility_calls(mock_get_utility)
def _assert_no_get_utility_calls(self, mock_get_utility):
self.assertFalse(mock_get_utility().yesno.called)
self.assertFalse(mock_get_utility().add_message.called)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
mock_get_utility().yesno.return_value = True
self._call()
self._assert_subscribed(mock_subscribe)
self.assertFalse(mock_get_utility().add_message.called)
self._assert_correct_yesno_call(mock_get_utility)
def _assert_subscribed(self, mock_subscribe):
self.assertTrue(mock_subscribe.called)
self.assertEqual(mock_subscribe.call_args[0][0], self.email)
@util.patch_get_utility()
@mock.patch('certbot.eff.subscribe')
def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
mock_get_utility().yesno.return_value = False
self._call()
self.assertFalse(mock_subscribe.called)
self.assertFalse(mock_get_utility().add_message.called)
self._assert_correct_yesno_call(mock_get_utility)
def _assert_correct_yesno_call(self, mock_get_utility):
self.assertTrue(mock_get_utility().yesno.called)
call_args, call_kwargs = mock_get_utility().yesno.call_args
actual = call_args[0]
expected_part = 'Electronic Frontier Foundation'
self.assertTrue(expected_part in actual)
self.assertFalse(call_kwargs.get('default', True))
class SubscribeTest(unittest.TestCase):
"""Tests for certbot.eff.subscribe."""
def setUp(self):
self.email = 'certbot@example.org'
self.json = {'status': True}
self.response = mock.Mock(ok=True)
self.response.json.return_value = self.json
@mock.patch('certbot.eff.requests.post')
def _call(self, mock_post):
mock_post.return_value = self.response
from certbot.eff import subscribe
subscribe(self.email)
self._check_post_call(mock_post)
def _check_post_call(self, mock_post):
self.assertEqual(mock_post.call_count, 1)
call_args, call_kwargs = mock_post.call_args
self.assertEqual(call_args[0], constants.EFF_SUBSCRIBE_URI)
data = call_kwargs.get('data')
self.assertFalse(data is None)
self.assertEqual(data.get('email'), self.email)
@util.patch_get_utility()
def test_bad_status(self, mock_get_utility):
self.json['status'] = False
self._call() # pylint: disable=no-value-for-parameter
actual = self._get_reported_message(mock_get_utility)
expected_part = 'because your e-mail address appears to be invalid.'
self.assertTrue(expected_part in actual)
@util.patch_get_utility()
def test_not_ok(self, mock_get_utility):
self.response.ok = False
self._call() # pylint: disable=no-value-for-parameter
actual = self._get_reported_message(mock_get_utility)
unexpected_part = 'because'
self.assertFalse(unexpected_part in actual)
def _get_reported_message(self, mock_get_utility):
self.assertTrue(mock_get_utility().add_message.called)
return mock_get_utility().add_message.call_args[0][0]
@util.patch_get_utility()
def test_subscribe(self, mock_get_utility):
self._call() # pylint: disable=no-value-for-parameter
self.assertFalse(mock_get_utility.called)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -70,7 +70,7 @@ class ErrorHandlerTest(unittest.TestCase):
send_signal(self.signals[0])
should_be_42 *= 10
# check exectuion stoped when the signal was sent
# check execution stoped when the signal was sent
self.assertEqual(42, should_be_42)
# assert signals were caught
self.assertEqual([self.signals[0]], signals_received)

View file

@ -9,7 +9,7 @@ from certbot import achallenges
from certbot.tests import acme_util
class FaiiledChallengesTest(unittest.TestCase):
class FailedChallengesTest(unittest.TestCase):
"""Tests for certbot.errors.FailedChallenges."""
def setUp(self):

View file

@ -5,16 +5,14 @@ import os
import unittest
import mock
from six.moves import reload_module # pylint: disable=import-error
from certbot import errors
from certbot import hooks
class HookTest(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
reload_module(hooks)
@mock.patch('certbot.hooks._prog')
def test_validate_hooks(self, mock_prog):
@ -47,7 +45,6 @@ class HookTest(unittest.TestCase):
return mock_logger.warning
def test_pre_hook(self):
hooks.pre_hook.already = set()
config = mock.MagicMock(pre_hook="true")
self._test_a_hook(config, hooks.pre_hook, 1)
self._test_a_hook(config, hooks.pre_hook, 0)

View file

@ -1,4 +1,5 @@
"""Tests for certbot.main."""
# pylint: disable=too-many-lines
from __future__ import print_function
import itertools
@ -12,6 +13,7 @@ import datetime
import pytz
import six
from six.moves import reload_module # pylint: disable=import-error
from acme import jose
@ -55,17 +57,21 @@ class RunTest(unittest.TestCase):
def setUp(self):
self.domain = 'example.org'
self.patches = [
mock.patch('certbot.main._auth_from_available'),
mock.patch('certbot.main._get_and_save_cert'),
mock.patch('certbot.main.display_ops.success_installation'),
mock.patch('certbot.main.display_ops.success_renewal'),
mock.patch('certbot.main._init_le_client'),
mock.patch('certbot.main._suggest_donation_if_appropriate')]
mock.patch('certbot.main._suggest_donation_if_appropriate'),
mock.patch('certbot.main._report_new_cert'),
mock.patch('certbot.main._find_cert')]
self.mock_auth = self.patches[0].start()
self.mock_success_installation = self.patches[1].start()
self.mock_success_renewal = self.patches[2].start()
self.mock_init = self.patches[3].start()
self.mock_suggest_donation = self.patches[4].start()
self.mock_report_cert = self.patches[5].start()
self.mock_find_cert = self.patches[6].start()
def tearDown(self):
for patch in self.patches:
@ -81,23 +87,26 @@ class RunTest(unittest.TestCase):
run(config, plugins)
def test_newcert_success(self):
self.mock_auth.return_value = ('newcert', mock.Mock())
self.mock_auth.return_value = mock.Mock()
self.mock_find_cert.return_value = True, None
self._call()
self.mock_success_installation.assert_called_once_with([self.domain])
def test_reinstall_success(self):
self.mock_auth.return_value = ('reinstall', mock.Mock())
self.mock_auth.return_value = mock.Mock()
self.mock_find_cert.return_value = False, mock.Mock()
self._call()
self.mock_success_installation.assert_called_once_with([self.domain])
def test_renewal_success(self):
self.mock_auth.return_value = ('renewal', mock.Mock())
self.mock_auth.return_value = mock.Mock()
self.mock_find_cert.return_value = True, mock.Mock()
self._call()
self.mock_success_renewal.assert_called_once_with([self.domain])
class ObtainCertTest(unittest.TestCase):
"""Tests for certbot.main.obtain_cert."""
class CertonlyTest(unittest.TestCase):
"""Tests for certbot.main.certonly."""
def setUp(self):
self.get_utility_patch = test_util.patch_get_utility()
@ -112,15 +121,20 @@ class ObtainCertTest(unittest.TestCase):
cli.prepare_and_parse_args(plugins, args))
with mock.patch('certbot.main._init_le_client') as mock_init:
main.obtain_cert(config, plugins)
with mock.patch('certbot.main._suggest_donation_if_appropriate'):
main.certonly(config, plugins)
return mock_init() # returns the client
@mock.patch('certbot.main._auth_from_available')
def test_no_reinstall_text_pause(self, mock_auth):
@mock.patch('certbot.main._find_cert')
@mock.patch('certbot.main._get_and_save_cert')
@mock.patch('certbot.main._report_new_cert')
def test_no_reinstall_text_pause(self, unused_report, mock_auth,
mock_find_cert):
mock_notification = self.mock_get_utility().notification
mock_notification.side_effect = self._assert_no_pause
mock_auth.return_value = ('reinstall', mock.ANY)
mock_auth.return_value = mock.Mock()
mock_find_cert.return_value = False, None
self._call('certonly --webroot -d example.com'.split())
def _assert_no_pause(self, message, pause=True):
@ -215,7 +229,7 @@ class RevokeTest(unittest.TestCase):
'cert.pem'))
self.patches = [
mock.patch('acme.client.Client'),
mock.patch('acme.client.Client', autospec=True),
mock.patch('certbot.client.Client'),
mock.patch('certbot.main._determine_account'),
mock.patch('certbot.main.display_ops.success_revocation')
@ -241,8 +255,9 @@ class RevokeTest(unittest.TestCase):
for patch in self.patches:
patch.stop()
def _call(self):
args = 'revoke --cert-path={0}'.format(self.tmp_cert_path).split()
def _call(self, extra_args=""):
args = 'revoke --cert-path={0} ' + extra_args
args = args.format(self.tmp_cert_path).split()
plugins = disco.PluginsRegistry.find_all()
config = configuration.NamespaceConfig(
cli.prepare_and_parse_args(plugins, args))
@ -250,6 +265,17 @@ class RevokeTest(unittest.TestCase):
from certbot.main import revoke
revoke(config, plugins)
@mock.patch('certbot.main.client.acme_client')
def test_revoke_with_reason(self, mock_acme_client):
mock_revoke = mock_acme_client.Client().revoke
expected = []
for reason, code in constants.REVOCATION_REASONS.items():
self._call("--reason " + reason)
expected.append(mock.call(mock.ANY, code))
self._call("--reason " + reason.upper())
expected.append(mock.call(mock.ANY, code))
self.assertEqual(expected, mock_revoke.call_args_list)
def test_revocation_success(self):
self._call()
self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path)
@ -349,6 +375,9 @@ class DetermineAccountTest(unittest.TestCase):
def setUp(self):
self.args = mock.MagicMock(account=None, email=None,
config_dir="unused_config",
logs_dir="unused_logs",
work_dir="unused_work",
register_unsafely_without_email=False)
self.config = configuration.NamespaceConfig(self.args)
self.accs = [mock.MagicMock(id='x'), mock.MagicMock(id='y')]
@ -422,10 +451,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
'--logs-dir', self.logs_dir, '--text']
def tearDown(self):
shutil.rmtree(self.tmp_dir)
# Reset globals in cli
# pylint: disable=protected-access
cli._parser = cli.set_by_cli.detector = None
reload_module(cli)
shutil.rmtree(self.tmp_dir)
def _call(self, args, stdout=None):
"Run the cli with output streams and actual client mocked out"
@ -479,22 +507,23 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._cli_missing_flag(args, "specify a plugin")
args.extend(['--standalone', '-d', 'eg.is'])
self._cli_missing_flag(args, "register before running")
with mock.patch('certbot.main._auth_from_available'):
with mock.patch('certbot.main._get_and_save_cert'):
with mock.patch('certbot.main.client.acme_from_config_key'):
args.extend(['--email', 'io@io.is'])
self._cli_missing_flag(args, "--agree-tos")
@mock.patch('certbot.main._report_new_cert')
@mock.patch('certbot.main.client.acme_client.Client')
@mock.patch('certbot.main._determine_account')
@mock.patch('certbot.main.client.Client.obtain_and_enroll_certificate')
@mock.patch('certbot.main._auth_from_available')
def test_user_agent(self, afa, _obt, det, _client):
@mock.patch('certbot.main._get_and_save_cert')
def test_user_agent(self, gsc, _obt, det, _client, unused_report):
# Normally the client is totally mocked out, but here we need more
# arguments to automate it...
args = ["--standalone", "certonly", "-m", "none@none.com",
"-d", "example.com", '--agree-tos'] + self.standard_args
det.return_value = mock.MagicMock(), None
afa.return_value = "newcert", mock.MagicMock()
gsc.return_value = mock.MagicMock()
with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net:
self._call_no_clientmock(args)
@ -519,8 +548,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
'--key-path', 'key', '--chain-path', 'chain'])
self.assertEqual(mock_pick_installer.call_count, 1)
@mock.patch('certbot.main._report_new_cert')
@mock.patch('certbot.util.exe_exists')
def test_configurator_selection(self, mock_exe_exists):
def test_configurator_selection(self, mock_exe_exists, unused_report):
mock_exe_exists.return_value = True
real_plugins = disco.PluginsRegistry.find_all()
args = ['--apache', '--authenticator', 'standalone']
@ -546,13 +576,13 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._cli_missing_flag(["--standalone"], "With the standalone plugin, you probably")
with mock.patch("certbot.main._init_le_client") as mock_init:
with mock.patch("certbot.main._auth_from_available") as mock_afa:
mock_afa.return_value = (mock.MagicMock(), mock.MagicMock())
with mock.patch("certbot.main._get_and_save_cert") as mock_gsc:
mock_gsc.return_value = mock.MagicMock()
self._call(["certonly", "--manual", "-d", "foo.bar"])
unused_config, auth, unused_installer = mock_init.call_args[0]
self.assertTrue(isinstance(auth, manual.Authenticator))
with mock.patch('certbot.main.obtain_cert') as mock_certonly:
with mock.patch('certbot.main.certonly') as mock_certonly:
self._call(["auth", "--standalone"])
self.assertEqual(1, mock_certonly.call_count)
@ -640,12 +670,12 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
chain = 'chain'
fullchain = 'fullchain'
with mock.patch('certbot.main.obtain_cert') as mock_obtaincert:
with mock.patch('certbot.main.certonly') as mock_certonly:
self._call(['certonly', '--cert-path', cert, '--key-path', 'key',
'--chain-path', 'chain',
'--fullchain-path', 'fullchain'])
config, unused_plugins = mock_obtaincert.call_args[0]
config, unused_plugins = mock_certonly.call_args[0]
self.assertEqual(config.cert_path, os.path.abspath(cert))
self.assertEqual(config.key_path, os.path.abspath(key))
self.assertEqual(config.chain_path, os.path.abspath(chain))
@ -731,7 +761,8 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
date = '1970-01-01'
mock_notAfter().date.return_value = date
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=cert_path)
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=cert_path,
fullchain_path=cert_path)
mock_client = mock.MagicMock()
mock_client.obtain_and_enroll_certificate.return_value = mock_lineage
self._certonly_new_request_common(mock_client)
@ -754,7 +785,8 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
# pylint: disable=too-many-locals,too-many-arguments
cert_path = test_util.vector_path('cert.pem')
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path,
cert_path=cert_path, fullchain_path=chain_path)
mock_lineage.should_autorenew.return_value = due_for_renewal
mock_lineage.has_pending_deployment.return_value = False
mock_lineage.names.return_value = ['isnot.org']
@ -805,7 +837,8 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
return mock_lineage, mock_get_utility, stdout
def test_certonly_renewal(self):
@mock.patch('certbot.crypto_util.notAfter')
def test_certonly_renewal(self, unused_notafter):
lineage, get_utility, _ = self._test_renewal_common(True, [])
self.assertEqual(lineage.save_successor.call_count, 1)
lineage.update_all_links_to.assert_called_once_with(
@ -814,7 +847,8 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue('fullchain.pem' in cert_msg)
self.assertTrue('donate' in get_utility().add_message.call_args[0][0])
def test_certonly_renewal_triggers(self):
@mock.patch('certbot.crypto_util.notAfter')
def test_certonly_renewal_triggers(self, unused_notafter):
# --dry-run should force renewal
_, get_utility, _ = self._test_renewal_common(False, ['--dry-run', '--keep'],
log_out="simulating renewal")
@ -862,14 +896,17 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
test_util.make_lineage(self, 'sample-renewal.conf')
args = ["renew", "--dry-run", "--post-hook=no-such-command",
"--disable-hook-validation"]
self._test_renewal_common(True, [], args=args, should_renew=True,
error_expected=False)
with mock.patch("certbot.hooks.post_hook"):
self._test_renewal_common(True, [], args=args, should_renew=True,
error_expected=False)
@mock.patch("certbot.cli.set_by_cli")
def test_ancient_webroot_renewal_conf(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
rc_path = test_util.make_lineage(self, 'sample-renewal-ancient.conf')
args = mock.MagicMock(account=None, email=None, webroot_path=None)
args = mock.MagicMock(account=None, config_dir=self.config_dir,
logs_dir=self.logs_dir, work_dir=self.work_dir,
email=None, webroot_path=None)
config = configuration.NamespaceConfig(args)
lineage = storage.RenewableCert(rc_path, config)
renewalparams = lineage.configuration["renewalparams"]
@ -913,15 +950,15 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
if names is not None:
mock_lineage.names.return_value = names
mock_rc.return_value = mock_lineage
with mock.patch('certbot.main.obtain_cert') as mock_obtain_cert:
with mock.patch('certbot.main.renew_cert') as mock_renew_cert:
kwargs.setdefault('args', ['renew'])
self._test_renewal_common(True, None, should_renew=False, **kwargs)
if assert_oc_called is not None:
if assert_oc_called:
self.assertTrue(mock_obtain_cert.called)
self.assertTrue(mock_renew_cert.called)
else:
self.assertFalse(mock_obtain_cert.called)
self.assertFalse(mock_renew_cert.called)
def test_renew_no_renewalparams(self):
self._test_renew_common(assert_oc_called=False, error_expected=True)
@ -981,8 +1018,8 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_rc.return_value = mock_lineage
mock_lineage.configuration = {
'renewalparams': {'authenticator': 'webroot'}}
with mock.patch('certbot.main.obtain_cert') as mock_obtain_cert:
mock_obtain_cert.side_effect = Exception
with mock.patch('certbot.main.renew_cert') as mock_renew_cert:
mock_renew_cert.side_effect = Exception
self._test_renewal_common(True, None, error_expected=True,
args=['renew'], should_renew=False)
@ -1016,12 +1053,12 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
mock_client = mock.MagicMock()
mock_client.obtain_certificate_from_csr.return_value = (certr, chain)
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
mock_client.save_certificate.return_value = cert_path, None, None
full_path = '/etc/letsencrypt/live/example.com/fullchain.pem'
mock_client.save_certificate.return_value = cert_path, None, full_path
with mock.patch('certbot.main._init_le_client') as mock_init:
mock_init.return_value = mock_client
with test_util.patch_get_utility() as mock_get_utility:
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
full_path = '/etc/letsencrypt/live/example.com/fullchain.pem'
args = ('-a standalone certonly --csr {0} --cert-path {1} '
'--chain-path {2} --fullchain-path {3}').format(
CSR, cert_path, chain_path, full_path).split()
@ -1041,7 +1078,7 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
def test_certonly_csr(self):
mock_get_utility = self._test_certonly_csr_common()
cert_msg = mock_get_utility().add_message.call_args_list[0][0][0]
self.assertTrue('cert.pem' in cert_msg)
self.assertTrue('fullchain.pem' in cert_msg)
self.assertTrue(
'donate' in mock_get_utility().add_message.call_args[0][0])
@ -1062,7 +1099,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
with open(CERT, 'rb') as f:
cert = crypto_util.pyopenssl_load_certificate(f.read())[0]
mock_revoke = mock_acme_client.Client().revoke
mock_revoke.assert_called_once_with(jose.ComparableX509(cert))
mock_revoke.assert_called_once_with(
jose.ComparableX509(cert),
mock.ANY)
@mock.patch('certbot.main._determine_account')
def test_revoke_without_key(self, mock_determine_account):
@ -1071,7 +1110,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
with open(CERT) as f:
cert = crypto_util.pyopenssl_load_certificate(f.read())[0]
mock_revoke = client.acme_from_config_key().revoke
mock_revoke.assert_called_once_with(jose.ComparableX509(cert))
mock_revoke.assert_called_once_with(
jose.ComparableX509(cert),
mock.ANY)
def test_agree_dev_preview_config(self):
with mock.patch('certbot.main.run') as mocked_run:
@ -1122,9 +1163,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
def test_update_registration_with_email(self, mock_utility, mock_email):
email = "user@example.com"
mock_email.return_value = email
with mock.patch('certbot.main.client') as mocked_client:
with mock.patch('certbot.main.account') as mocked_account:
with mock.patch('certbot.main._determine_account') as mocked_det:
with mock.patch('certbot.eff.handle_subscription') as mock_handle:
with mock.patch('certbot.main._determine_account') as mocked_det:
with mock.patch('certbot.main.account') as mocked_account:
with mock.patch('certbot.main.client') as mocked_client:
mocked_storage = mock.MagicMock()
mocked_account.AccountFileStorage.return_value = mocked_storage
@ -1145,6 +1186,69 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue(mocked_storage.save_regr.called)
self.assertTrue(
email in mock_utility().add_message.call_args[0][0])
self.assertTrue(mock_handle.called)
class UnregisterTest(unittest.TestCase):
def setUp(self):
self.patchers = {
'_determine_account': mock.patch('certbot.main._determine_account'),
'account': mock.patch('certbot.main.account'),
'client': mock.patch('certbot.main.client'),
'get_utility': test_util.patch_get_utility()}
self.mocks = dict((k, v.start()) for k, v in self.patchers.items())
def tearDown(self):
for patch in self.patchers.values():
patch.stop()
def test_abort_unregister(self):
self.mocks['account'].AccountFileStorage.return_value = mock.Mock()
util_mock = self.mocks['get_utility'].return_value
util_mock.yesno.return_value = False
config = mock.Mock()
unused_plugins = mock.Mock()
res = main.unregister(config, unused_plugins)
self.assertEqual(res, "Deactivation aborted.")
def test_unregister(self):
mocked_storage = mock.MagicMock()
mocked_storage.find_all.return_value = ["an account"]
self.mocks['account'].AccountFileStorage.return_value = mocked_storage
self.mocks['_determine_account'].return_value = (mock.MagicMock(), "foo")
acme_client = mock.MagicMock()
self.mocks['client'].Client.return_value = acme_client
config = mock.MagicMock()
unused_plugins = mock.MagicMock()
res = main.unregister(config, unused_plugins)
self.assertTrue(res is None)
self.assertTrue(acme_client.acme.deactivate_registration.called)
m = "Account deactivated."
self.assertTrue(m in self.mocks['get_utility']().add_message.call_args[0][0])
def test_unregister_no_account(self):
mocked_storage = mock.MagicMock()
mocked_storage.find_all.return_value = []
self.mocks['account'].AccountFileStorage.return_value = mocked_storage
acme_client = mock.MagicMock()
self.mocks['client'].Client.return_value = acme_client
config = mock.MagicMock()
unused_plugins = mock.MagicMock()
res = main.unregister(config, unused_plugins)
m = "Could not find existing account to deactivate."
self.assertEqual(res, m)
self.assertFalse(acme_client.acme.deactivate_registration.called)
class TestHandleException(unittest.TestCase):

View file

@ -28,7 +28,7 @@ class OCSPTest(unittest.TestCase):
def tearDown(self):
pass
@mock.patch('certbot.ocsp.logging.info')
@mock.patch('certbot.ocsp.logger.info')
@mock.patch('certbot.ocsp.Popen')
@mock.patch('certbot.util.exe_exists')
def test_init(self, mock_exists, mock_popen, mock_log):

View file

@ -2,8 +2,11 @@
import os
import mock
import unittest
import shutil
import tempfile
from acme import challenges
from certbot import configuration
from certbot import errors
from certbot import storage
@ -16,11 +19,16 @@ class RenewalTest(unittest.TestCase):
self.tmp_dir = tempfile.mkdtemp()
self.config_dir = os.path.join(self.tmp_dir, 'config')
def tearDown(self):
shutil.rmtree(self.tmp_dir)
@mock.patch('certbot.cli.set_by_cli')
def test_ancient_webroot_renewal_conf(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
rc_path = util.make_lineage(self, 'sample-renewal-ancient.conf')
args = mock.MagicMock(account=None, email=None, webroot_path=None)
args = mock.MagicMock(account=None, config_dir=self.config_dir,
logs_dir="logs", work_dir="work",
email=None, webroot_path=None)
config = configuration.NamespaceConfig(args)
lineage = storage.RenewableCert(rc_path, config)
renewalparams = lineage.configuration['renewalparams']
@ -53,6 +61,29 @@ class RestoreRequiredConfigElementsTest(unittest.TestCase):
self.assertRaises(
errors.Error, self._call, self.config, renewalparams)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_pref_challs_list(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')}
self._call(self.config, renewalparams)
expected = [challenges.TLSSNI01.typ,
challenges.HTTP01.typ, challenges.DNS01.typ]
self.assertEqual(self.config.namespace.pref_challs, expected)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_pref_challs_str(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
renewalparams = {'pref_challs': 'dns'}
self._call(self.config, renewalparams)
expected = [challenges.DNS01.typ]
self.assertEqual(self.config.namespace.pref_challs, expected)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_pref_challs_failure(self, mock_set_by_cli):
mock_set_by_cli.return_value = False
renewalparams = {'pref_challs': 'finding-a-shrubbery'}
self.assertRaises(errors.Error, self._call, self.config, renewalparams)
@mock.patch('certbot.renewal.cli.set_by_cli')
def test_must_staple_success(self, mock_set_by_cli):
mock_set_by_cli.return_value = False

View file

@ -394,7 +394,7 @@ class TestFullCheckpointsReverter(unittest.TestCase):
self.assertTrue(mock_logger.info.call_count > 0)
def test_view_config_changes_bad_backups_dir(self):
# There shouldn't be any "in progess directories when this is called
# There shouldn't be any "in progress directories when this is called
# It must just be clean checkpoints
os.makedirs(os.path.join(self.config.backup_dir, "in_progress"))

View file

@ -13,9 +13,7 @@ from cryptography.hazmat.primitives import serialization
import mock
import OpenSSL
from acme import errors
from acme import jose
from acme import util
from certbot import constants
from certbot import interfaces
@ -86,20 +84,6 @@ def load_pyopenssl_private_key(*names):
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
def requirement_available(requirement):
"""Checks if requirement can be imported.
:rtype: bool
:returns: ``True`` iff requirement can be imported
"""
try:
util.activate(requirement)
except errors.DependencyError: # pragma: no cover
return False
return True # pragma: no cover
def skip_unless(condition, reason): # pragma: no cover
"""Skip tests unless a condition holds.

View file

@ -219,6 +219,25 @@ def safely_remove(path):
raise
def get_filtered_names(all_names):
"""Removes names that aren't considered valid by Let's Encrypt.
:param set all_names: all names found in the configuration
:returns: all found names that are considered valid by LE
:rtype: set
"""
filtered_names = set()
for name in all_names:
try:
filtered_names.add(enforce_le_validity(name))
except errors.ConfigurationError as error:
logger.debug('Not suggesting name "%s"', name)
logger.debug(error)
return filtered_names
def get_os_info(filepath="/etc/os-release"):
"""
Get OS name and version

View file

@ -1,14 +1,18 @@
production:
build: .
ports:
- "443:443"
version: '2'
services:
production:
build: .
ports:
- "443:443"
# For development, mount git root to /opt/certbot/src in order to
# make the dev workflow more vagrant-like.
development:
build: .
ports:
- "443:443"
volumes:
- .:/opt/certbot/src
- /opt/certbot/venv
development:
build:
context: .
dockerfile: Dockerfile-dev
ports:
- "443:443"
volumes:
- .:/opt/certbot/src
- /opt/certbot/venv

View file

@ -206,7 +206,7 @@ or a family of enhancements, one per selectable ciphersuite configuration.
Feedback
========
We recieve lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some coallated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
Because of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions.
@ -255,7 +255,7 @@ I have access to an English-language summary of the recommendations.
Keylength.com
~~~~~~~~~~~~~
Damien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels. The keylength recommendations of the various sources are summarized in a chart. This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2015.
Damien Giry collects recommendations by academic researchers and standards organizations about keylengths for particular cryptoperiods, years, or security levels. The keylength recommendations of the various sources are summarized in a chart. This site has been updated over time and includes expert guidance from eight sources published between 2000 and 2017.
http://www.keylength.com/

View file

@ -1,39 +1,61 @@
usage:
certbot [SUBCOMMAND] [options] [-d domain] [-d domain] ...
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:
cert. The most common SUBCOMMANDS and flags 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
register Perform tasks related to registering with the CA
rollback Rollback server configuration changes made during install
config_changes Show changes made to server config during installation
plugins Display information about installed plugins
obtain, install, and renew certificates:
(default) run Obtain & install a cert in your current webserver
certonly Obtain or renew a cert, but do not install it
renew Renew all previously obtained certs that are near expiry
-d DOMAINS Comma-separated list of domains to obtain a cert for
--apache Use the Apache plugin for authentication & installation
--standalone Run a standalone webserver for authentication
--nginx Use the Nginx plugin for authentication & installation
--webroot Place files in a server's webroot folder for authentication
--manual Obtain certs interactively, or using shell script hooks
-n Run non-interactively
--test-cert Obtain a test cert from a staging server
--dry-run Test "renew" or "certonly" without saving any certs to disk
manage certificates:
certificates Display information about certs you have from Certbot
revoke Revoke a certificate (supply --cert-path)
delete Delete a certificate
manage your account with Let's Encrypt:
register Create a Let's Encrypt ACME account
--agree-tos Agree to the ACME server's Subscriber Agreement
-m EMAIL Email address for important account notifications
optional arguments:
-h, --help show this help message and exit
-c CONFIG_FILE, --config CONFIG_FILE
config file path (default: None)
path to config file (default: /etc/letsencrypt/cli.ini
and ~/.config/letsencrypt/cli.ini)
-v, --verbose This flag can be used multiple times to incrementally
increase the verbosity of output, e.g. -vvv. (default:
-2)
-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)
--dialog Run using interactive dialog menus (default: False)
--force-interactive Force Certbot to be interactive even if it detects
it's not being run in a terminal. This flag cannot be
used with the renew subcommand. (default: False)
-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: [])
of domains as a parameter. (default: Ask)
--cert-name CERTNAME Certificate name to apply. Only one certificate name
can be used per Certbot run. To see certificate names,
run 'certbot certificates'. When creating a new
certificate, specifies the new certificate's name.
(default: None)
--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'
@ -48,6 +70,221 @@ optional arguments:
because they may be necessary to accurately simulate
renewal. --renew-hook commands are not called.
(default: False)
--preferred-challenges PREF_CHALLS
A sorted, comma delimited list of the preferred
challenge to use during authorization with the most
preferred challenge listed first (Eg, "dns" or "tls-
sni-01,http,dns"). Not all plugins support all
challenges. See
https://certbot.eff.org/docs/using.html#plugins for
details. ACME Challenges are versioned, but if you
pick "http" rather than "http-01", Certbot will select
the latest version automatically. (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:
CertbotACMEClient/0.12.0 (Ubuntu 16.04.2 LTS)
Authenticator/XXX Installer/YYY)
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: Ask)
--expand If an existing cert covers some subset of the
requested names, always expand and replace it with the
additional names. (default: Ask)
--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)
--renew-with-new-domains
If a certificate already exists for the requested
certificate name but does not match the requested
domains, renew it now, regardless of whether it is
near expiry. (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: Ask)
--duplicate Allow making a certificate lineage that duplicates an
existing one (both can be renewed in parallel)
(default: False)
--os-packages-only (certbot-auto only) install OS package dependencies
and then stop (default: False)
--no-self-upgrade (certbot-auto only) prevent the certbot-auto script
from upgrading itself to newer released versions
(default: Upgrade automatically)
-q, --quiet Silence all output except errors. Useful for
automation via cron. Implies --non-interactive.
(default: False)
security:
Security parameters & server settings
--rsa-key-size N Size of the RSA key. (default: 2048)
--must-staple Adds the OCSP Must Staple extension to the
certificate. Autoconfigures OCSP Stapling for
supported setups (Apache version >= 2.3.3 ). (default:
False)
--redirect Automatically redirect all HTTP traffic to HTTPS for
the newly authenticated vhost. (default: Ask)
--no-redirect Do not automatically redirect all HTTP traffic to
HTTPS for the newly authenticated vhost. (default:
Ask)
--hsts Add the Strict-Transport-Security header to every HTTP
response. Forcing browser to always use SSL for the
domain. Defends against SSL Stripping. (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)
--staple-ocsp Enables OCSP Stapling. A valid OCSP response is
stapled to the certificate that the server offers
during TLS. (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)
testing:
The following flags are meant for testing and integration purposes only.
--test-cert, --staging
Use the staging server to obtain or revoke test
(invalid) certs; equivalent to --server https://acme-
staging.api.letsencrypt.org/directory (default: False)
--debug Show tracebacks in case of errors, and allow certbot-
auto execution on experimental platforms (default:
False)
--no-verify-ssl Disable verification of the ACME server's certificate.
(default: False)
--tls-sni-01-port TLS_SNI_01_PORT
Port used during tls-sni-01 challenge. This only
affects the port Certbot listens on. A conforming ACME
server will still attempt to connect on port 443.
(default: 443)
--http-01-port HTTP01_PORT
Port used in the http-01 challenge. This only affects
the port Certbot listens on. A conforming ACME server
will still attempt to connect on port 80. (default:
80)
--break-my-certs Be willing to replace or renew valid certs with
invalid (testing/staging) certs (default: False)
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)
--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)
manage:
Various subcommands and flags are available for managing your
certificates:
certificates List certificates managed by Certbot
delete Clean up all files related to a certificate
renew Renew all certificates (or one specified with --cert-
name)
revoke Revoke a certificate specified with --cert-path
update_symlinks Recreate symlinks in your /etc/letsencrypt/live/
directory
run:
Options for obtaining & installing certs
certonly:
Options for modifying how a cert is obtained
--csr CSR Path to a Certificate Signing Request (CSR) in DER or
PEM format. Currently --csr only works with the
'certonly' subcommand. (default: None)
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. When renewing several certificates
that have identical pre-hooks, only the first will be
executed. (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. This is only run if an
attempt was made to obtain/renew a certificate. If
multiple renewed certificates have identical post-
hooks, only one will be run. (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 the
config 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)
--disable-hook-validation
Ordinarily the commands specified for --pre-hook
/--post-hook/--renew-hook will be checked for
validity, to see if the programs being run are in the
$PATH, so that mistakes can be caught early, even when
the hooks aren't being run just yet. The validation is
rather simplistic and fails if you use more advanced
shell constructs, so you can use this switch to
disable it. (default: False)
certificates:
List certificates managed by Certbot
delete:
Options for deleting a certificate
revoke:
Options for revocation of certs
--reason {keycompromise,affiliationchanged,superseded,unspecified,cessationofoperation}
Specify reason for revoking certificate. (default: 0)
register:
Options for account registration & modification
--register-unsafely-without-email
Specifying this flag enables registering an account
with no email address. This is strongly discouraged,
@ -65,220 +302,46 @@ optional arguments:
registering a new account. (default: False)
-m EMAIL, --email EMAIL
Email used for registration and recovery contact.
(default: None)
--preferred-challenges PREF_CHALLS
A sorted, comma delimited list of the preferred
challenge to use during authorization with the most
preferred challenge listed first (Eg, "dns" or "tls-
sni-01,http,dns"). Not all plugins support all
challenges. See
https://certbot.eff.org/docs/using.html#plugins for
details. ACME Challenges are versioned, but if you
pick "http" rather than "http-01", Certbot will select
the latest version automatically. (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)
(default: Ask)
--eff-email Share your e-mail address with EFF (default: None)
--no-eff-email Don't share your e-mail address with EFF (default:
None)
automation:
Arguments for automating execution & other tweaks
unregister:
Options for account deactivation.
--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 (certbot-auto only) install OS package dependencies
and then stop (default: False)
--no-self-upgrade (certbot-auto only) prevent the certbot-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)
security:
Security parameters & server settings
--rsa-key-size N Size of the RSA key. (default: 2048)
--must-staple Adds the OCSP Must Staple extension to the
certificate. Autoconfigures OCSP Stapling for
supported setups (Apache version >= 2.3.3 ). (default:
False)
--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 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)
--staple-ocsp Enables OCSP Stapling. A valid OCSP response is
stapled to the certificate that the server offers
during TLS. (default: None)
--no-staple-ocsp Do not automatically enable OCSP Stapling. (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)
testing:
The following flags are meant for testing purposes only! Do NOT change
them, unless you really know what you're doing!
--test-cert, --staging
Use the staging server to obtain test (invalid) certs;
equivalent to --server https://acme-
staging.api.letsencrypt.org/directory (default: False)
--debug Show tracebacks in case of errors, and allow certbot-
auto execution on experimental platforms (default:
False)
--no-verify-ssl Disable verification of the ACME server's certificate.
(default: False)
--break-my-certs Be willing to replace or renew valid certs with
invalid (testing/staging) certs (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. This is only run if an
attempt was made to obtain/renew a certificate.
(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 the
config 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)
--disable-hook-validation
Ordinarily the commands specified for --pre-hook
/--post-hook/--renew-hook will be checked for
validity, to see if the programs being run are in the
$PATH, so that mistakes can be caught early, even when
the hooks aren't being run just yet. The validation is
rather simplistic and fails if you use more advanced
shell constructs, so you can use this switch to
disable it. (default: True)
certonly:
Options for modifying how a cert is obtained
--tls-sni-01-port TLS_SNI_01_PORT
Port used during tls-sni-01 challenge. This only
affects the port Certbot listens on. A conforming ACME
server will still attempt to connect on port 443.
(default: 443)
--http-01-port HTTP01_PORT
Port used in the http-01 challenge. This only affects
the port Certbot listens on. A conforming ACME server
will still attempt to connect on port 80. (default:
80)
--csr CSR Path to a Certificate Signing Request (CSR) in DER or
PEM format. 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
--fullchain-path FULLCHAIN_PATH
Accompanying path to a full certificate chain (cert
plus chain). (default: None)
config_changes:
Options for controlling which changes are displayed
--num NUM How many past revisions you want to be displayed
(default: None)
rollback:
Options for reverting config changes
Options for rolling back server configuration changes
--checkpoints N Revert configuration N number of checkpoints.
(default: 1)
plugins:
Options for the "plugins" subcommand
Options for for the "plugins" subcommand
--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)
update_symlinks:
Recreates cert and key symlinks in /etc/letsencrypt/live, if you changed
them by hand or edited a renewal configuration file
plugins:
Plugin Selection: Certbot client supports an extensible plugins
@ -287,15 +350,15 @@ plugins:
provided below. Running --help <plugin_name> will list flags specific to
that plugin.
--configurator CONFIGURATOR
Name of the plugin that is both an authenticator and
an installer. Should not be used together with
--authenticator or --installer. (default: Ask)
-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:
@ -318,13 +381,24 @@ standalone:
Spin up a temporary webserver
manual:
Manually configure an HTTP server
Authenticate through manual configuration or custom shell scripts. When
using shell scripts, an authenticator script must be provided. The
environment variables available to this script are $CERTBOT_DOMAIN which
contains the domain being authenticated, $CERTBOT_VALIDATION which is the
validation string, and $CERTBOT_TOKEN which is the filename of the
resource requested when performing an HTTP-01 challenge. An additional
cleanup script can also be provided and can use the additional variable
$CERTBOT_AUTH_OUTPUT which contains the stdout output from the auth
script.
--manual-test-mode Test mode. Executes the manual command in subprocess.
(default: False)
--manual-auth-hook MANUAL_AUTH_HOOK
Path or command to execute for the authentication
script (default: None)
--manual-cleanup-hook MANUAL_CLEANUP_HOOK
Path or command to execute for the cleanup script
(default: None)
--manual-public-ip-logging-ok
Automatically allows public IP logging. (default:
False)
Automatically allows public IP logging (default: Ask)
webroot:
Place files in webroot directory
@ -335,7 +409,7 @@ webroot:
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: [])
m.thing.net` (default: Ask)
--webroot-map WEBROOT_MAP
JSON dictionary mapping domains to webroot paths; this
implies -d for each entry. You may need to escape this

Some files were not shown because too many files have changed in this diff Show more