mirror of
https://github.com/certbot/certbot.git
synced 2026-05-28 04:34:11 -04:00
Merge branch 'master' into dockerfile++
This commit is contained in:
commit
3d5c0842cb
131 changed files with 3304 additions and 2708 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -33,3 +33,5 @@ tags
|
|||
tests/letstest/letest-*/
|
||||
tests/letstest/*.pem
|
||||
tests/letstest/venv/
|
||||
|
||||
.venv
|
||||
|
|
|
|||
4
.pep8
4
.pep8
|
|
@ -1,4 +0,0 @@
|
|||
[pep8]
|
||||
# E265 block comment should start with '# '
|
||||
# E501 line too long (X > 79 characters)
|
||||
ignore = E265,E501
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
[MASTER]
|
||||
|
||||
# use as many jobs as there are cores
|
||||
jobs=0
|
||||
|
||||
# Specify a configuration file.
|
||||
#rcfile=
|
||||
|
||||
|
|
|
|||
12
.travis.yml
12
.travis.yml
|
|
@ -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
336
CHANGELOG.md
Normal 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
|
||||
|
|
@ -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
14
ISSUE_TEMPLATE.md
Normal 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.
|
||||
|
|
@ -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
41
Vagrantfile
vendored
|
|
@ -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
|
||||
|
|
@ -1,4 +0,0 @@
|
|||
[pep8]
|
||||
# E265 block comment should start with '# '
|
||||
# E501 line too long (X > 79 characters)
|
||||
ignore = E265,E501
|
||||
377
acme/.pylintrc
377
acme/.pylintrc
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
@ -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
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
|
|
|
|||
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
451
certbot-auto
451
certbot-auto
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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...
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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'))
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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]))
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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 ##
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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``)::
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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."""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
|
|||
|
|
@ -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
95
certbot/eff.py
Normal 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)
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
249
certbot/main.py
249
certbot/main.py
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
135
certbot/tests/eff_test.py
Normal 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
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue