Compare commits

...

762 Commits

Author SHA1 Message Date
Brad Warren
a5eff30e07 Merge branch 'nginx-min-cb-version' into test-nginx-min-cb-version 2019-03-27 09:19:34 -07:00
Brad Warren
bf32193577 s/certbot/./g 2019-03-27 09:19:21 -07:00
Brad Warren
2b0006c234 test fast and quiet 2019-03-27 09:15:03 -07:00
Brad Warren
040c64f63f Bump the min Certbot version for nginx plugin. 2019-03-27 09:01:58 -07:00
Brad Warren
491d6c8f45 Revert "Configure jessie repos in LTS mode during Docker build (#6887)" (#6889)
This reverts commit a27bd28b39.
2019-03-27 07:27:06 +01:00
Adrien Ferrand
a03e7b95d3 Deprecate all tls-sni related objects in acme module (#6859)
This PR is a part of the tls-sni-01 removal plan described in #6849.

As `acme` is a library, we need to put some efforts to make a decent deprecation path before totally removing tls-sni in it. While initialization of `acme.challenges.TLSSNI01` was already creating deprecation warning, not all cases were covered.

For instance, and innocent call like this ...
```python
if not isinstance(challenge, acme.challenges.TLSSNI01):
    print('I am not using this TLS-SNI deprecated stuff, what could possibly go wrong?')
```
... would break if we suddenly remove all objects related to this challenge.

So, I use the _Deprecator Warning Machine, Let's Pacify this Technical Debt_ (Guido ®), to make `acme.challenges` and `acme.standalone` patch themselves, and display a deprecation warning on stderr for any access to the tls-sni challenge objects.

No dev should be able to avoid the deprecation warning. I set the deprecation warning in the idea to remove the code on `0.34.0`, but the exact deprecation window is open to discussion of course.

* Modules challenges and standalone patch themselves to generated deprecation warning when tls-sni related objects are accessed.

* Correct unit tests

* Correct lint

* Update challenges_test.py

* Correct lint

* Fix an error during tests

* Update coverage

* Use multiprocessing for coverage

* Add coverage

* Update test_util.py

* Factor the logic about global deprecation warning when accessing TLS-SNI-01 attributes

* Fix coverage

* Add comment for cryptography example.

* Use warnings.

* Add a changelog

* Fix deprecation during tests

* Reload

* Update acme/acme/__init__.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update CHANGELOG.md

* Pick a random free port.
2019-03-26 18:26:38 -07:00
Adrien Ferrand
821bec6997 Remove tls-sni related flags in cli. Add a deprecation warning instead. (#6853)
This PR is a part of the tls-sni-01 removal plan described in #6849.

This PR removes --tls-sni-01-port, --tls-sni-01-address and tls-sni-01/tls-sni options from --preferred-challenges. They are replace by deprecation warning, indicating that these options will be removed soon.

This deprecation, instead of complete removal, is done to avoid certbot instances to hard fail if some automated scripts still use these flags for some users.

Once this PR lands, we can remove completely theses flags in one or two release.

* Remove tls-sni related flags in cli. Add a deprecation warning instead.

* Adapt tests to cli and renewal towards tls-sni flags deprecation

* Add https_port option. Make tls_sni_01_port show a deprecation warning, but silently modify https_port if set

* Migrate last items

* Fix lint

* Update certbot/cli.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Ensure to remove all occurences of tls-sni-01

* Remove unused parameter

* Revert modifications on cli-help.txt

* Use logger.warning instead of sys.stderr

* Update the logger warning message

* Remove standalone_supported_challenges option.

* Fix order of preferred-challenges

* Remove supported_challenges property

* Fix some tests

* Fix lint

* Fix tests

* Add a changelog

* Clean code, fix test

* Update CI

* Reload

* No hard date for tls-sni removal

* Remove useless cast to list

* Update certbot/tests/renewal_test.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Add entry to the changelog

* Add entry to the changelog
2019-03-26 17:46:32 -07:00
Adrien Ferrand
a27bd28b39 Configure jessie repos in LTS mode during Docker build (#6887)
Currently, `tox -e le_auto_jessie` job fails. It breaks in particular the cron pipeline that test everything each night.

The failure occurs while setting up the Jessie Docker container to run the tests for certbot-auto, when `apt-get update` is invoked, with this error:
```
W: Failed to fetch http://deb.debian.org/debian/dists/jessie-updates/main/binary-amd64/Packages  404  Not Found
```

Indeed, if there are `stretch-updates`, `buster-updates` and so on in the repository, there is no `jessie-updates`. I do not know exactly the logic of Debian here, but as `*-updates` folders store stable updates, a distribution moving to LTS support like Jessie has no stable updates anymore. I suppose `jessie-updates have been decommissioned recently, and the official Docker has not been updated yet to use the LTS configuration for repositories.

This PR does that live in the Dockerfile, using official instructions from https://wiki.debian.org/LTS/Using, and fixes this specific job.

An example of a successful job with this modification can be found here: https://travis-ci.com/certbot/certbot/jobs/187864341
2019-03-26 11:35:43 -07:00
Brad Warren
50607eb0ff Document dropped tls-sni-01 support in plugins. (#6884) 2019-03-25 15:57:53 -07:00
Adrien Ferrand
97d269ceb5 Raise explicitly an error (#6883)
Explicit is better than implicit

When calling raise without an argument, Python will raise the last error occured from the caller except block. This makes my PyCharm very sad however. So this PR makes the function handling the error raising explicitly the error received as an argument.
2019-03-25 15:06:13 -07:00
Adrien Ferrand
20ed165699 Remove unused code in apache (#6882)
To fix one of the two uncovered lines in certbot-apache, given in #6880. Instead of adding a test to just increase the coverage, this fixes the uncovered line by removing the unused code.
2019-03-25 13:48:36 -07:00
Adrien Ferrand
4d2dfab4dd Simplify a branching that is not totally covered. (#6881) 2019-03-25 21:22:56 +01:00
Adrien Ferrand
537bffbc23 [Windows] Fix some unit tests (#6865)
This PR is a part of the effort to remove the last broken unit tests in certbot codebase for Windows, as described in #6850.

This PR fixes various unit tests on Windows, whose resolution was only to modify some logic in the tests, or minor changes in certbot codecase impacting Windows only (like handling correctly paths with DOS-style).

* Correct several tests

* Skip test definitively

* Test to be reactivated with #6497

* Mock log system to avoid errors due to multiple calls to main in main_test

* Simplify mock

* Update cli_test.py

* One test to be repaired when windows file permissions PR is merged
2019-03-25 12:56:28 -07:00
Adrien Ferrand
c1d2efec4e Construct the sanitized, pinned and hashed requirements file for certbot-auto (#6839)
* Setup an independant create_venv piece for certbot-auto

* Debug

* First implementation

* Some corrections, disable python 3

* Continue work

* Add hashin

* Polish CLI

* Fix logic

* Add executable permissions

* Assynchronous process

* Correction

* Add comments

* More controls

* Correct image name

* Fix image

* Test with 2

* Test timeout

* Remove parallelization for now. To much bugs.

* Add comments

* Correct installation

* Correct keys map view usage

* Improve filtering

* Correction

* Improve filtering, again

* Remove dependency on python 3

* Remove necessity to run from certbot root

* Add constraints. Clean code.

* Pure constraints

* More involved base test

* Update certbot-auto with calculated dependencies

* Update header

* Rebuild UI

* Correction

* Remove debug info

* Ensure docker exit when process finish

* Another try to stop docker

* Clean stdout/stderr

* Fix python-augeas

* Catch stderr

* Update dependencies with new constraints

* Update certbot-auto

* Corrections after review.

* Clean endline

* Silent execution

* Filter editable installation of local certbot packages, strict check on package names
2019-03-25 18:52:59 +01:00
Adrien Ferrand
d9880721b3 Remove tls sni in nginx plugin (#6857)
* Remove tls-sni from nginx config

* Add a dedicated configuration to define what is the HTTPS port for this certbot instance.

* Correct some tests

* Reestablish default vhost creation

* Clean tls references for nginx integration tests

* Associate https_port only to tests and nginx
2019-03-18 10:22:19 -07:00
Adrien Ferrand
b447b0a8e9 Remove tls sni in apache plugin (#6858)
* Add a dedicated configuration to define what is the HTTPS port for this certbot instance.

* Remove tls-sni in apache plugin

* Update constants.py

* Update interfaces.py

* Remove option

* Simplify a test
2019-03-15 16:39:43 -07:00
Adrien Ferrand
e909b0852c Remove tls-sni challenge in manual plugin (#6855)
* Remove tls-sni challenge in manual

* Remove unused logic
2019-03-14 17:56:56 -07:00
Adrien Ferrand
c2f2aa5ee0 Remove tls-sni in compatibility tests (#6854)
* Reconfigure compatibility tests to use http challenge

* Correct simple test

* Add a fake DNS resolution for HTTP simple_verify

* Debug

* More subtle approach: we monkey patch urllib3 to fake a dns resolution to the target IP, allowing every host header to be preserved.

* Private package

* Relaxed permissions on certbot temp working dir

* Move the fake DNS logic in compatibility test, to avoid degrading the acme coverage

* Fix lint

* Update certbot-compatibility-test/certbot_compatibility_test/configurators/common.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>
2019-03-15 01:07:49 +01:00
Adrien Ferrand
5e64349a4a Remove tls-sni challenge in standalone plugin (#6856) 2019-03-14 16:30:17 -07:00
Brad Warren
8386d08de2 Use Python3 to run tools/venv3.py. (#6860) 2019-03-14 19:07:19 +01:00
Adrien Ferrand
acc918eee7 Remove tls-sni integration tests (#6852)
This PR is a part of the tls-sni-01 removal plan described in #6849.

This PR removes the tls-sni-01 challenge tests during the integration tests. The approach I used here is not to remove completely the existing test code, but simply editing it to use a http-01 challenge. Indeed:
* the current integration tests are strongly coupled, and would require more modifications that it is worth, because ...
* the certbot-ci project, that has already no tls-sni tests, will soon replace completely the current integration tests code.
2019-03-13 15:42:07 -07:00
Adrien Ferrand
cf29e89366 Move coverage computation during certbot integration tests at the end of the script (#6842)
Currently coverage invocation during integration tests on certbot core is misplaced, just before the OCSP statuses tests.

This PR move back the coverage invocation at the end of the script.
2019-03-11 16:16:48 -07:00
Brad Warren
d34beb4149 This is now at the top level of their site (#6846) 2019-03-11 16:14:34 -07:00
Brad Warren
a7f2f24426 Mention OCSP UTC fix in changelog. (#6845) 2019-03-11 16:14:20 -07:00
Seth Schoen
e20adedb94 This is now at the top level of their site 2019-03-11 15:50:27 -07:00
Adrien Ferrand
81d9b5250e Clean stderr in case of /etc/os-release does not exist (#6835) 2019-03-11 15:42:32 -07:00
schoen
674ba896eb Merge pull request #6817 from obynio/master
Replace deprecated Gandi plugin link
2019-03-11 15:41:45 -07:00
Adrien Ferrand
cac4be7046 Calculate timedelta with thisUpdate/nextUpdate in UTC (#6838)
Fixes #6836.

OCSP responses contains a thisUpdate and nextUpdate that allow to calculate its validity. Certbot currently uses datetime.now() to get the current time when OCSP check is done through cryptography. But datetime.now() expresses the date in the machine local time, and comparison operators on datetime do not take into account the offset between two datetime objects expressed in difference timezones.

As a consequence, a given thisUpdate may be seen as a future date depending on the local time, failing the OCSP check process.

The error is not critical for certbot, as it will just make some valid OCSP responses giving an EXPIRED status been ignored.

This PR fixes this comparison by taking the current time in UTC using datetime.utctime().
2019-03-11 15:27:33 -07:00
Brad Warren
45229eebdf Drop expected Apache coverage to workaround #6813. (#6826)
* Drop expected Apache coverage to workaround #6813.

* add comment
2019-03-07 20:57:08 +01:00
Adrien Ferrand
34393f9bf4 Correct certbot-auto for Fedora 29+ (#6812)
Fixes #6698

Fedora maintainers engaged a deprecation path for Python 2.x with Fedora 29. As a first step, python2-virtualenv does not install the virtualenv binary anymore, in favor of python3-virtualenv, and so the installation of Python 3 virtual environments by default.

However, certbot-auto installs python2-virtualenv for all recent RPM distributions, and relies of the execution of virtualenv, and this is failing the process.

Since the plan in the future is to remove Python 2.x from Fedora, this PR follows this logic to fix certbot-auto: started to Fedora 29, certbot-auto will install and execute certbot on Python 3. This implies to detect that we are on Fedora 29+, install python3-virtualenv that will install also Python 3 dependencies and virtualenv binary, then instruct the process to use Python 3. This is in fact similar to EOL distributions shipping with Python 2.6, and for which Python 3.4 from EPEL is installed and used.

Older versions of Fedora continue to use Python 2.x, and their process is untouched. Four scenarios are covered here:

fresh Fedora 28: old process is used, nothing changes
fresh Fedora 29: new process is used, Python 3 is installed, certbot runs on it
update Fedora 29 from 28, already installed certbot-auto without rebootstrapping required: existing venv continue to be used, certbot runs on it
update Fedora 29 from 28, already installed certbot-auto with rebootstrapping required: new process is used, installing python3-virtualenv, python3-devel and python3-rpm-macros, Python 3 is installed, certbot runs on it

* Add a step to handle python3 on fedora29

* Update letsencrypt-auto-source/letsencrypt-auto.template

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update letsencrypt-auto-source/letsencrypt-auto.template

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update letsencrypt-auto-source/letsencrypt-auto.template

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update rpm_python3.sh

* Rebuild certbot-auto

* Empty commit to relaunch CI pipeline

* Add changelog

* Update CHANGELOG.md

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update CHANGELOG.md
2019-03-07 10:05:20 -08:00
Adrien Ferrand
f378536ffa Do not run the full CI pipeline on master (#6811)
* Configure appveyor

* Renaming in travis yml

* Update .travis.yml

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update .travis.yml

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>
2019-03-06 14:49:43 -08:00
Brad Warren
e593674930 Merge pull request #6820 from certbot/candidate-0.32.0
Release 0.32.0
2019-03-06 13:58:02 -08:00
Erica Portnoy
8dda6cc68f Bump version to 0.33.0 2019-03-06 12:47:29 -08:00
Erica Portnoy
0343f365ab Add contents to CHANGELOG.md for next version 2019-03-06 12:47:28 -08:00
Erica Portnoy
0492855166 Release 0.32.0 2019-03-06 12:47:27 -08:00
Erica Portnoy
a276523c09 Update changelog for 0.32.0 release 2019-03-06 12:18:08 -08:00
Erica Portnoy
be6df7de04 Remove Fixed section from changelog 2019-03-06 12:16:13 -08:00
Adrien Ferrand
670e9d89b7 Fix faulty test (#6816) 2019-03-05 18:30:33 -08:00
Brad Warren
198c447e77 Move the OCSP change to the right section. (#6818)
Looks like this got added to our changelog for the released 0.31.0 instead of the upcoming release. We want this change for the release tomorrow.
2019-03-05 14:01:08 -08:00
Yohann Leon
6a0f3248a8 Replace deprecated Gandi plugin link 2019-03-05 22:27:07 +01:00
Brad Warren
0c6d83dc39 Merge pull request #6786 from certbot/pin-deps_continued
This continues from the work of @sydneyli in PR #6671
I didn't do much here. Basically added support for reading data from sys.stdin to both tools/merge_requirements.py and tools/strip_hashes.py as well as support for reading files from paths passed as cli parameters to strip_hashes.py.

Reading the filepaths was not strictly required, but I thought would be a good thing to do in order to keep the tooling usage options consistent.

Fixes #6581

* Generate constraints file to pin deps in Docker images

Dockerfiles pin versions using constraints file

Pulling out strip_hashes and add --no-deps flag

* Add stdin option for merge_requirements

Add stdin and file path support to strip_hashes

* Address review comments
2019-03-04 09:02:53 -08:00
Joona Hoikkala
d8a3fa3904 Address review comments 2019-03-04 15:52:38 +02:00
Adrien Ferrand
6ff101dcbb Cover case of OpenSUSE Leap 15+ in certbot-auto (#6794)
Fixes #6228.

Since OpenSUSE Leap 15, python-virtualenv became a source package, breaking certbot-auto bootstrap on this version. Then python2-virtualenv must be used to create Python 2.x virtual environments.

This PR makes certbot-auto compatible to prior and after Leap 15, by testing the existence of python-virtualenv on current OpenSUSE system, and then use appropriate packages.

* Cover case of OpenSUSE Leap 15+ in certbot-auto

* Revert increment on bootstrap for OpenSUSE

* Fix configuration for Leap15+

* Add comment about explicit installation of python2-setuptools

* Update letsencrypt-auto-source/pieces/bootstrappers/suse_common.sh

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update letsencrypt-auto
2019-03-01 18:07:07 -08:00
Adrien Ferrand
bf1f83f47b Revert "Disable build for commits pushed on master (#6804)" (#6807)
By removing all the builds on push to master, done in #6804, we also removing the coveralls reports that are necessary to calculate the effect of a PR on code coverage, that is part of the quality gate process.

This PR is reverting #6804, and another implementation preserving the coveralls reports will be done soon.

This reverts commit a468a3b255.
2019-03-01 17:16:22 -08:00
Adrien Ferrand
3ed3787bd8 Implement Retry-After, and refactor authorization polling (#6766)
Fixes #5789

This PR is about allowing Certbot to respect the Retry-After HTTP header that an ACME CA server can return to a client POSTing to a challenge, to instruct him and retry the request later.

However, this feature was not easily implementable in the current code of certbot.auth_handler, because the code became really hard to read. In fact, @bmw was thinking that the code was really deceiving, and a lot of supposed functionalities declared in the comments were in fact not implemented or not functional.

So I took the time to understand what was going on, and effectively, most of the code is in fact not usable or not used. Then I did a refactoring against the bare ACME spec about what to do to prepare challenges, instruct the ACME CA server to perform them, then polling regularly the authorization resources until they are decided (valid or invalid).

And of course this implementation takes care of Retry-After ^^

I added a lot of comments in the new implementation, to explain what is going on for a future developer. The workflow I used is relying on the relationships between authorizations and challenges states as described in section 7.1.6 of the ACME spec draft: https://datatracker.ietf.org/doc/draft-ietf-acme-acme/

* Clean auth_handler a bit, and implement retry-after.

* Remove a debug logger

* Correct tests

* Fix mypy and lint. Setup max retries and default retry after accordingly.

* Ease a comparison in tests

* Update documentation

* Add tests

* Adapt windows coverage threshold to the global LOC reduction

* Update certbot/auth_handler.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Corrections under review

* Correction under review

* Update certbot/auth_handler.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Corrections under review

* Update auth_handler_test.py

* Reimplementing user readable report for failed authorizations

* Fixes two tests

* Fix another test + lint + mypy

* Update auth_handler.py

* Update auth_handler_test.py

* Fix tests

* Update certbot/auth_handler.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Raise directly the exception on polling timout

* Improve interface documentation

* Move the wait on top of the loop, to be used initially or after a new loop iteration. Do not wait for negative values.

* Always display the report about failed authorizations.

* Clarify an exception.

* Return, instead of break

* Use setdefault

* Remove useless assertion

* Adapt tests

* Improve a test about retry after value.

* Update certbot/auth_handler.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Add a complete test on best_effort

* Add entry to the changelog

* Gather all failed authzrs to be reported in one unique report in case of best_effort

* Build complete warn/report/raise process about failed authzrs
2019-03-01 17:03:33 -08:00
Brad Warren
c7f8f15e9b Fix spurious test_relevant_values* failures (#6799)
* Mock set_by_cli in _test_relevant_values_common.

* Empty commit

* Revert "Mock set_by_cli in _test_relevant_values_common."

This reverts commit 9dfec8dfa9.

* mock less

* Use plugin_common code instead of reimplementing.

* Revert 2nd implementation.

* Simplify certbot.storage.relevant_values() tests.

In addition to cleaning up the code a bit, it also removes the problems we've
seen in these tests with the global state used in cli.py.
2019-03-02 01:04:26 +01:00
Adrien Ferrand
a468a3b255 Disable build for commits pushed on master (#6804)
Fixes #6746.

Every commit on master is always the result of a merged PR, that has been tested by Travis. So retesting the merge commit on master is superfluous. This PR uses build conditions to avoid to launch a build for a commit push on master.

I also added the equivalent logic for AppVeyor. Builds cannot received conditions, so it needs to be done on init using Exit-AppVeyorBuild. This command does not fail the build, it finishes it prematurely with success.

* Disable build for commit pushed on master (PR are still tested of course)

* Equivalent exclusion code for AppVeyor
2019-03-01 14:21:07 -08:00
Adrien Ferrand
7161e792e8 Fix the Nginx configuration during integration tests (#6801)
If you execute `tests/lock_test.py` or `tox -e integration` on a fairly recent machine, you will get the following error during tests executing against a live Nginx instance:
```
no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking, client: x.x.x.x, server: y:y:y:y:z
```

Indeed, having no defined ssl certificate for a ssl port would inevitably lead to an error during the handshake SSL process between a client and this mis-configured nginx instance.

However it was not a problem one year before, because the handshake was not occurring in practice: the test just need to have a nginx started, and then immediately proceed to modify the configuration with a correct SSL setup. And nginx was able to start with a mis-configuration on SSL. 

But then this fix has been done: https://trac.nginx.org/nginx/ticket/178

Basically with this, validation of the configuration is done during nginx startup, that will refuse to start with invalid configuration on SSL. Consequently, all related tests are failing with a sufficiently up-to-date nginx. For now, it is not seen on Travis because Ubuntu Trusty is used, with an old Nginx.

The PR fixes that, by generating on the fly self-signed certificates in the two impacted tests, and pushing the right parameters in the Nginx configuration.

* Fix nginx configuration with self-signed certificates generated on the fly

* Fix lint/mypy

* Fix old cryptography

* Unattended openssl

* Update lock_test.py
2019-03-01 13:54:09 -08:00
Adrien Ferrand
841f8efd0a [Unix] Create a framework for certbot integration tests: PART 1 (#6578)
* First part

* Several optimizations about the docker env setup

* Documentation

* Various corrections and documentation. Add acme and certbot explicitly as dependencies of certbot-ci.

* Correct a variable misinterpreted as a pytest hook

* Correct strict parsing option on pebble

* Refactor acme setup to be executed from pytest hooks.

* Pass TRAVIS env variable to trigger specific xdist logic

* Retrigger build.

* Work in progress

* Config operational

* Propagate to xdist

* Corrections on acme and misc

* Correct subnet for pebble

* Remove gobetween, as tls-sni challenges are not tested anymore.

* Improve pebble setup. Reduce LOC.

* Update acme.py

* Optimize acme ca setup, with less temporary assets

* Silent setup

* Clean code

* Remove unused workspace

* Use default network driver

* Remove bridge

* Update package documentation

* Remove rerun capability for integration tests, not needed.

* Add documentation

* Variable for all ports and subnets used by the stack

* Update certbot-ci/certbot_integration_tests/conftest.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot-ci/certbot_integration_tests/utils/acme.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot-ci/certbot_integration_tests/utils/misc.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update tox.ini

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot-ci/certbot_integration_tests/utils/misc.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot-ci/certbot_integration_tests/utils/acme.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot-ci/certbot_integration_tests/utils/acme.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot-ci/certbot_integration_tests/conftest.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Rename to acme_server

* Add comment

* Refactor in a unique context fixture

* Remove the need of CERTBOT_ACME_XDIST environment variable

* Remove nonstrict/strict options in pebble

* Clean dependencies

* Clean tox

* Change function name

* Add comment about coveragerc specificities

* Change a comment.

* Update setup.py

* Update conftest.py

* Use the production-ready docker-compose.yml file for Pebble

* New style class

* Tune pebble to have a stable test environment

* Pin a dependency
2019-03-01 13:18:06 -08:00
Adrien Ferrand
efc8d49806 Disable rerun feature of Travis (#6800)
The rerun capability in a test campaign can be a nice feature. It can also a bad design.

It is right that with high level tests, like performance or end-to-end tests, a given test runtime can depend on an external component, outside the scope of the developers, that would spuriously fail. In theses situations, having the capacity to rerun several time a test can be a great benefit. Indeed, as these tests are inherently flaky, the rerun greatly reduces their failure because of external reasons, reducing also the Pierre et le Loup effect (from a well-known french book for children): because tests constantly fail for no reason, you stop to listen to them and to not see when they fail for a real reason.

However this not apply to unit tests, or operations about code quality. For theses executions, the flakiness should not exist: a unit test is supposed to have no external dependency. A rerun approach would just hide a situation that is not desirable for this kind of tests

Also effectively I see that our tests are usually not flaky: so the only effect of the rerun is to give the failure state about a test three times slower. If a test becomes flaky, this should be fixed, and so be visible immediately in our CI.

For these reasons, I remove the travis_retry in the script section of .travis-ci.yml, to call directly tox and let the pipeline fails on the first error.

* Disable rerun feature of Travis

* Update .travis.yml

* Remove completely the retry logic
2019-02-28 12:49:36 -08:00
Adrien Ferrand
5e849e03f6 [Windows] Fix pipstrap (#6775)
* Fix pipstrap on windows

* Pipstrap pin setuptools version, so explicit install it is not needed anymore.

* Rebuild letsencrypt-auto source

* Use sys.executable in pipstrap to allow straightforward execution in a venv by choosing the python interpreter from this venv.

* Update letsencrypt-auto

* Simulate test-everything

* Revert "Simulate test-everything"

This reverts commit b62c4d719a6e741cb11126c7490097a79c68cf4d.

* Clean pipstrap code
2019-02-28 20:35:58 +01:00
schoen
a809c3697d Warn sysadmins about privilege escalation risk (#6795) 2019-02-27 16:32:57 -08:00
Adrien Ferrand
9c405a3cd1 Fix cryptography OCSP support (#6751)
* Reenabling OCSP cryptography support

* Refactor the validation logic of OCSP response to match the OpenSSL one

* Prepare runtime for OCSP response test

* Move unrelated test to another relevant place

* Reimplement OCSP status checks in integration tests

* Clean script

* Protect OCSP check against connection errors

* Update tests/certbot-boulder-integration.sh

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Cleaning

* Add a specific script for letsencrypt-auto install+help

* Remove inconsistent assertion

* Add executable permissions

* Remove unused variable

* Move testdata

* Corrected cleanup code

* Empty commit
2019-02-28 00:16:52 +01:00
Adrien Ferrand
339d034d6a Remove keyAuthorization field from the challenge response JWS token (#6758)
Fixes #6755.

POSTing the `keyAuthorization` in a JWS token when answering an ACME challenge, has been deprecated for some time now. Indeed, this is superfluous as the request is already authentified by the JWS signature.

Boulder still accepts to see this field in the JWS token, and ignore it. Pebble in non strict mode also. But Pebble in strict mode refuses the request, to prepare complete removal of this field in ACME v2.

Certbot still sends the `keyAuthorization` field. This PR removes it, and makes Certbot compliant with current ACME v2 protocol, and so Pebble in strict mode.

See also [letsencrypt/pebble#192](https://github.com/letsencrypt/pebble/issues/192) for implementation details server side.

* New implementation, with a fallback.

* Add deprecation on changelog

* Update acme/acme/client.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Fix an instance parameter

* Update changelog, extend coverage

* Update comment

* Add unit tests on keyAuthorization dump

* Update acme/acme/client.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Restrict the magic of setting a variable in immutable object in one place. Make a soon to be removed method private.
2019-02-27 09:21:47 -08:00
schoen
f5b23361bd Merge pull request #6791 from certbot/no-manual-tests
Remove display.py.
2019-02-25 15:38:30 -08:00
Julie R
401045be89 Add acme library usage example (http-01) (#5494)
* Add acme library usage example

Create, edit and deactivate account.
Setup and perform http-01 challenge.
Issue, renew and revoke certificate.

* Adapt example to ACME-v2 and exclude data persistence

The code to persist/load data would length this example and distract from what is actually important.

* Fix domain names and e-mail addresses

* Remove unnecessary license header

This usage example is under the license for the acme package.

* Remove logging information

The code will be mostly read by developers, so simplify the logging info into comments.

* Revert abstraction of simple methods

All methods that are used only once in this example were expanded into the main code in order to make the process more explicit.

* Fix missing URL suffix

* Improve aesthetics and reorganize workflow

Also make words capitalization consistent and improve comments.
No complaints from pep8.
2019-02-22 18:02:43 -08:00
Brad Warren
f105aedc92 Remove display.py. 2019-02-22 16:55:50 -08:00
Brad Warren
31b4b8e57c Log the execution of manual hooks (#6788)
* Move logging to execute and fix tests.

* update changelog
2019-02-22 16:42:01 +02:00
Adrien Ferrand
b10ceb7d90 Fix test sdists with atexit handlers (#6769)
So merging the study from @bmw and me, here is what happened.

Each invocation of `certbot.logger.post_arg_parse_setup` create a file handler on `letsencrypt.log`. This function also set an atexit handler invoking `logger.shutdown()`, that have the effect to close all logger file handler not already closed at this point. This method is supposed to be called when a python process is close to exit, because it makes all logger unable to write new logs on any handler.

Before #6667 and this PR, for tests, the atexit handle would be triggered only at the end of the pytest process. It means that each test that launches `certbot.logger.post_arg_parse_setup` add a new file handler. These tests were typically connecting the file handler on a `letsencrypt.log` located in a temporary directory, and this directory and content was wipped out at each test tearDown. As a consequence, the file handles, not cleared from the logger, were accumulating in the logger, with all of them connected to a deleted file log, except the last one that was just created by the current test. Considering the number of tests concerned, there were ~300 file handler at the end of pytest execution.

One can see that, on prior #6667, by calling `print(logger.getLogger().handlers` on the `tearDown` of these tests, and see the array growing at each test execution.

Even if this represent a memory leak, this situation was not really a problem on Linux: because a file can be deleted before it is closed, it was only meaning that a given invocation of `logger.debug` for instance, during the tests, was written in 300 log files. The overhead is negligeable. On Windows however, the file handlers were failing because you cannot delete a file before it is closed.

It was one of the reason for #6667, that added a call to `logging.shutdown()` at each test tearDown, with the consequence to close all file handlers. At this point, Linux is not happy anymore. Any call to `logger.warn` will generate an error for each closed file handler. As a file handler is added for each test, the number of errors grows on each test, following an arithmetical suite divergence.

On `test_sdists.py`, that is using the bare setuptools test suite without output capturing, we can see the damages. The total output takes 216000 lines, and 23000 errors are generated. A decent machine can support this load, but a not a small AWS instance, that is crashing during the execution. Even with pytest, the captured output and the memory leak become so large that segfaults are generated.

On the current PR, the problem is solved, by resetting the file handlers array on the logging system on each test tearDown. So each fileHandler is properly closed, and removed from the stack. They do not participate anymore in the logging system, and can be garbage collected. Then we stay on always one file handler opened at any time, and tests can succeed on AWS instances.

For the record, here is all the places where the logging system is called and fail if there is still file handlers closed but not cleaned (extracted from the original huge output before correction):

```
Logged from file account.py, line 116
Logged from file account.py, line 178
Logged from file client.py, line 166
Logged from file client.py, line 295
Logged from file client.py, line 415
Logged from file client.py, line 422
Logged from file client.py, line 480
Logged from file client.py, line 503
Logged from file client.py, line 540
Logged from file client.py, line 601
Logged from file client.py, line 622
Logged from file client.py, line 750
Logged from file cli.py, line 220
Logged from file cli.py, line 226
Logged from file crypto_util.py, line 101
Logged from file crypto_util.py, line 127
Logged from file crypto_util.py, line 147
Logged from file crypto_util.py, line 261
Logged from file crypto_util.py, line 283
Logged from file crypto_util.py, line 307
Logged from file crypto_util.py, line 336
Logged from file disco.py, line 116
Logged from file disco.py, line 124
Logged from file disco.py, line 134
Logged from file disco.py, line 138
Logged from file disco.py, line 141
Logged from file dns_common_lexicon.py, line 45
Logged from file dns_common_lexicon.py, line 61
Logged from file dns_common_lexicon.py, line 67
Logged from file dns_common.py, line 316
Logged from file dns_common.py, line 64
Logged from file eff.py, line 60
Logged from file eff.py, line 73
Logged from file error_handler.py, line 105
Logged from file error_handler.py, line 110
Logged from file error_handler.py, line 87
Logged from file hooks.py, line 248
Logged from file main.py, line 1071
Logged from file main.py, line 1075
Logged from file main.py, line 1189
Logged from file ops.py, line 122
Logged from file ops.py, line 325
Logged from file ops.py, line 338
Logged from file reporter.py, line 55
Logged from file selection.py, line 110
Logged from file selection.py, line 118
Logged from file selection.py, line 123
Logged from file selection.py, line 176
Logged from file selection.py, line 231
Logged from file selection.py, line 310
Logged from file selection.py, line 66
Logged from file standalone.py, line 101
Logged from file standalone.py, line 88
Logged from file standalone.py, line 97
Logged from file standalone.py, line 98
Logged from file storage.py, line 52
Logged from file storage.py, line 59
Logged from file storage.py, line 75
Logged from file util.py, line 56
Logged from file webroot.py, line 165
Logged from file webroot.py, line 186
Logged from file webroot.py, line 187
Logged from file webroot.py, line 204
Logged from file webroot.py, line 223
Logged from file webroot.py, line 234
Logged from file webroot.py, line 235
Logged from file webroot.py, line 237
Logged from file webroot.py, line 91
```

* Reapply #6667

* Make setuptools delegates tests execution to pytest, like in acme module.

* Clean handlers at each tearDown to avoid memory leaks.

* Update changelog
2019-02-21 16:55:08 -08:00
Adrien Ferrand
eb5c4eca87 [Windows] Working unit tests for certbot-nginx (#6782)
This PR fixes certbot-nginx and relevant tests to make them succeed on Windows.

Next step will be to enable integration tests through certbot-ci in a future PR.

* Fix tests and incompabilities in certbot-nginx for Windows

* Fix lint, fix oldest local dependencies
2019-02-20 16:20:16 -08:00
ohemorange
eef4c47633 Add failure message if test farm tests do not run the correct number of tests. (#6771)
Fixes #6748.
2019-02-20 15:20:44 -08:00
Joona Hoikkala
8bda10541a Add stdin option for merge_requirements
Add stdin and file path support to strip_hashes
2019-02-20 21:34:05 +02:00
sydneyli
7c731599a0 Generate constraints file to pin deps in Docker images
Dockerfiles pin versions using constraints file

Pulling out strip_hashes and add --no-deps flag
2019-02-20 16:59:55 +02:00
sblondon
bda840b3ee add version parameter when the help message is displayed (#6780) 2019-02-20 01:08:20 +01:00
Adrien Ferrand
209a0c4d2c [Windows] Refactor lock_and_call using queues (#6778)
* Refactor lock_and_call using queues

* Update util.py

* Replace queue by event

* Add comments

* Update certbot/tests/util.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot/tests/util.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Add control on timeout
2019-02-19 15:15:06 -08:00
Adrien Ferrand
0489ca5888 [Windows] Fixes lock_and_call test method (#6772)
The method `lock_and_call`, in `certbot.tests.util` is designed to acquire a lock on a foreign process, then execute a callable in the current process. This is done to closely reproduce the lock mechanism involved between two certbot instances that are running in parallel.

This method uses the `multiprocessing` module. But its implementation in `lock_and_call` is broken for Windows: the two processes fail to communicate, leading to a deadlock.

In fact, `multiprocessing` module is using the fork mechanism on Linux, and the spawn mechanism on Windows, leading to behavior inconsistencies between the two platforms.

As this method is for tests, and not for production code, I did not try to make two implementations "by the book", one suitable for Windows, the other for Linux, like for the `certbot.lock` module.

Instead, I use a `subprocess` approach with a trigger file allowing to coordinate the current process and the subprocess. With this, `lock_and_call` is running from the same code both on Linux and Windows.

Relevant tests in the `certbot.tests.lock_test` test module are now enabled for Windows.

* Implement new lock_and_call method

* Reactivate tests for Windows
2019-02-15 18:51:22 -08:00
Adrien Ferrand
e40d929e80 [Windows|Unix] New platform independent locking mechanism - the revenge (#6663)
First PR about this issue, #6440, involved to much refactoring to ensure a correct behavior on Linux and installer plugins.

This PR proposes a new implementation of a lock mechanism for Linux and Windows, without implying a refactoring. It takes strictly the existing behavior for Linux, and add the appropriate logic for Windows. The `lock` module formalizes two independant mechanism dedicated to each platform, to improve maintainability.

Tests related to locking are re-activated for Windows, or definitively skipped because of irrelevancy for this platform. 6 more tests are enabled overall.

* Reimplement lock file on basic level

* Remove unused code

* Re-activate some tests

* Update doc

* Reactivate tests relevant to locks in Windows. Correct a test that was not testing what is supposed to test.

* Clean compat.

* Move close sooner in Windows lock implementation

* Add strong mypy types

* Use os.name

* Refactor lock mechanism logic

* Enable more tests

* Update lock.py

* Update lock_test.py
2019-02-14 16:55:27 -08:00
Brad Warren
583d40f5cf Pin pytest in test_sdists.sh. (#6764)
* pin pytest in test_sdists.sh.

* Use pip_install.py in test_tests.sh.
2019-02-14 15:26:44 -08:00
Adrien Ferrand
acc0b1e773 Fix the pebble fetch script (#6765)
This PR updates and fixes `pebble-fetch.sh` considering latest improvements done on Pebble, to start a working instance.

* Fix the pebble fetch script

* Update pebble-fetch.sh

* Update tox.ini
2019-02-14 10:43:27 -08:00
Joona Hoikkala
cff8769db7 Apache: respect CERTBOT_DOCS environment variable (#6598)
Apache plugin will now use command line default values from `ApacheConfingurator.OS_DEFAULTS` instead of respective distribution override when `CERTBOT_DOCS=1` environment variable is present.

Fixes: #6234

* Apache: respect CERTBOT_DOCS environment variable

* Move the tests to apache plugin
2019-02-13 08:37:01 -08:00
Brad Warren
f10f98fec5 More carefully check for certbot --version output. (#6762) 2019-02-12 16:54:04 -08:00
Adrien Ferrand
a0a8292ff2 Correct the Content-Type used in the POST-as-GET request to retrieve a cert (#6757) 2019-02-12 15:36:27 -08:00
Brad Warren
66c9767623 Fix #6501 (#6761) 2019-02-12 23:59:34 +01:00
Brad Warren
ec4c03fa6d Merge pull request #6754 from certbot/candidate-0.31.0
Release 0.31.0
2019-02-07 15:50:39 -08:00
Brad Warren
381d097895 Bump version to 0.32.0 2019-02-07 13:27:13 -08:00
Brad Warren
917dc16b30 Add contents to CHANGELOG.md for next version 2019-02-07 13:27:12 -08:00
Brad Warren
75499277be Release 0.31.0 2019-02-07 13:27:10 -08:00
Brad Warren
ee3c14cbab Update changelog for 0.31.0 release 2019-02-07 13:20:30 -08:00
Brad Warren
432e18d943 Revert "Call atexit handlers before test tearDown to remove errors on Windows (#6667)" (#6752)
This reverts commit ca25d1b66a.
2019-02-07 21:40:45 +01:00
J0WI
67828562a0 Upgrade to Alpine 3.9 (#6743)
Alpine 3.9 comes with OpenSSL 1.1.1.
2019-02-07 09:06:04 -08:00
Brad Warren
ab79d1d44a Revert "Use built-in support for OCSP in cryptography >= 2.5 (#6603) (#6747)
I think this is causing failures in some of our tests so this PR reverts the change until we can fix the problem.

The 2nd commit is to keep the change using more idiomatic wording in the changelog for another change that got included in this PR.

* Revert "Use built-in support for OCSP in cryptography >= 2.5 (#6603)"

This reverts commit 2ddaf3db04.

* keep changelog correction
2019-02-06 16:36:32 -08:00
ohemorange
c5baf035df Update CHANGELOG.md (#6745) 2019-02-06 14:51:52 -08:00
Brad Warren
2560ef0ffa Test all on push events to tested non-master branches. (#6741)
We always run a full set of CI tests before beginning the release process. The way this would work previously is we would either trigger tests on the `test-everything` branch to run through Travis' web UI or if it was a point release, create a new branch based on `test-everything` but modify `.travis.yml` so the branch that was pulled in to be tested was the point release branch instead of `master`.

This no longer works because the former `test-everything` tests are now only run when Travis automatically runs our tests nightly.

We could create and maintain a separate branch for the purpose of manually running all tests or remove the conditionals from the latest `.travis.yml` file every time before we want to run these tests, but there must be A Better Way™.

This PR makes the change that in addition to running all tests nightly, they would also run on pushes to tested branches other than master. These changes do not affect the tests run on PRs or on commits to `master`.

What is affected is commits to point release branches and branches named `test-*`. (See [.travis.yml](2ddaf3db04/.travis.yml (L177)) for what branches we run tests on.) Running all tests on point release branches automates the step of running our full test suite before doing a point release.

The changes to `test-*` could be a mixed bag, however, since we switched to travis-ci.com over 3 weeks ago, I'm the only one who has used this functionality and I personally prefer things this way. At the very least, since these branches don't seem to be widely used, I think we can make this change and reevaluate if it becomes a problem.

* Test all on push events to non-master branches.

* Move branches section up.

* expand comment
2019-02-06 12:47:56 -08:00
Joona Hoikkala
7e6a1f2488 Apache plugin: configure all matching domain names to be able to answer HTTP challenge. (#6729)
Attempts to configure all of the following VirtualHosts for answering the HTTP challenge:

* VirtualHosts that have the requested domain name in either `ServerName` or `ServerAlias` directive.
* VirtualHosts that have a wildcard name that would match the requested domain name.

This also applies to HTTPS VirtualHosts, making Apache plugin able to handle cases where HTTP redirection takes place in reverse proxy or similar, before reaching the Apache HTTPD.

Even though also HTTPS VirtualHosts are selected, Apache plugin tries to ensure that at least one of the selected VirtualHosts listens to HTTP-01 port (configured with `--http-01-port` CLI option). So in a case where only HTTPS VirtualHosts exist, but user wants to configure those, `--http-01-port` parameter needs to be set for the port configured to the HTTPS VirtualHost(s).

Fixes: #6730

* Select all matching VirtualHosts for HTTP-01 challenges instead of just one

* Finalize PR and add tests

* Changelog entry
2019-02-06 10:02:35 -08:00
Adrien Ferrand
2ddaf3db04 Use built-in support for OCSP in cryptography >= 2.5 (#6603)
In response to #6594. [Fixes #6594.]

To execute OCSP requests, certbot relies currently on a openssl binary execution. If openssl is not present in the PATH, the OCSP check will be silently ignored. Since version 2.4, cryptography has support for OCSP requests, without the need to have openssl binary available locally.

This PR takes advantage of it, and will use the built-in support of OCSP in cryptography for versions >= 2.4. Otherwise, fallback is done do a direct call to openssl binary, allowing oldest requirements to still work with legacy cryptography versions.

Update: requirement is now cryptography >= 2.5, to avoid to rely on a private method from cryptography.

* Implement logic using cryptography

* Working OSCP using pure cryptography

* Fix openssl usage in unit tests

* Reduce verbosity

* Add tests

* Improve naive skipIf

* Test resiliency

* Update ocsp.py

* Validate OCSP response. Unify OCSP URL get

* Improve resiliency checks, correct lint/mypy

* Improve hash selection

* Fix warnings when calling openssl bin

* Load OCSP tests assets as vectors.

* Update ocsp.py

* Protect against invalid ocsp response.

* Add checks to OCSP response

* Add more control on ocsp response

* Be lenient about assertion that next_update must be in the future, similarly to openssl.

* Construct a more advanced OCSP response mock to trigger more logic in ocsp module.

* Add test

* Refactor signature process to use crypto_util

* Fallback for cryptography 2.4

* Avoid a collision with a meteor.

* Correct method signature documentation

* Relax OCSP update interval

* Trigger built-in ocsp logic from cryptography with 2.5+

* Update pinned version of cryptography

* Update certbot/ocsp.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update ocsp.py

* Update ocsp_test.py

* Update CHANGELOG.md

* Update CHANGELOG.md
2019-02-05 10:45:15 -08:00
schoen
9671985885 Clarify what a "renewal attempt" is (#6735) 2019-02-04 22:11:52 +01:00
Daniel McCarney
30803f30ba acme: add TLSALPN01Response for initiating tls-alpn-01. (#6689)
The existing `acme.TLSALPN01` challenge class did not have
a `response_cls`, meaning it was not possible to use a tls-alpn-01
challenge with `client.answer_challenge`.

To support the above a simple `TLSALPN01Response` class is added that
doesn't provide the ability to solve a tls-alpn-01 challenge end to end
(e.g. generating and serving the correct response certificate) but that
does allow the challenge to be initiated. This is sufficient for users
that have set up the challenge response independent of the `acme`
module code.

Resolves #6676
2019-02-04 10:03:29 -08:00
Samuel Shifterovich
f547521a5b /var/logs/ -> /var/log/ (#6732) 2019-02-02 18:56:38 +01:00
Brad Warren
bb8222200a Remove IValidator (#6572)
* Remove unneeded validator usage.

* Remove IValidator
2019-02-01 12:22:11 -08:00
Brad Warren
a0d47a44c9 Remove spdy cruft (#6573) 2019-02-01 12:16:18 -08:00
Adrien Ferrand
8f7b280106 [Windows] Fix account paths on Windows when colons are involved (#6711)
The account path used to store user credentials is calculated from the domain used to contact the relevant ACME CA server.

For instance, if the directory URL is https://my.domain.net/directory, then the account path will be $CONFIG_DIR/accounts/my.domain.net.

However, if non standard HTTP/HTTPS port need to be used, colons will be involved. For instance, https://my.domain.net:14000/directory will give $CONFIG_DIR/accounts/my.domain.net:14000.

Colons in paths are supported on POSIX systems, but not on Windows (it is reserved for the root drive letter).

This PR replaces colons by underscores for account paths on Windows, and leaves them untouched on Linux.

* Fix account path on Windows when colons are involved

* Protect colon in drive letter

* Refactor compat platform specific logic
2019-01-31 14:53:32 -08:00
Brad Warren
0484b1554d Set --pyargs directly in the files where it is needed. (#6727)
It was pointed out to me that you can no longer run tox.cover.py directly to run coverage tests on a subset of the packages in this repo.

This happened after we did both of:

1. Factored out --pyargs from the different test files and put it in pytest.ini.
2. Moved the options we added to pytest.ini to tox.ini meaning that --pyargs is not set unless you run the file through tox.

I think the fact that we factored out --pyargs from the files that needed it was a mistake. --pytest is needed by tox.cover.py and install_and_test.py in order to work correctly.

I think CLI options like this which are needed for the file to function should be left in the file directly. Doing anything else in my opinion unnecessarily couples these scripts to other files making them more brittle and harder to maintain.

With that said, I also think CLI options which are not needed (such as --numprocesses) can be left to be optionally added through PYTEST_ADDOPTS.

* Add --pyargs to tox.cover.py.

* Add --pyargs to install_and_test.py.

* Remove --pyargs from tox.ini.
2019-01-31 12:57:49 -08:00
Brad Warren
4237d4a3ad Ignore color_scheme warning from IPython. (#6714)
This PR in combination with #6713 resolves issues with using ipdb with pytest.
2019-01-30 13:59:07 -08:00
Adrien Ferrand
ca25d1b66a Call atexit handlers before test tearDown to remove errors on Windows (#6667)
When certbot is executing, several resources are opened. It is typically file handles and locks on them. Of course, theses resources need to be cleanup. It is done in Certbot by registering cleanup functions through atexit module, that ensures theses functions will be called when Certbot exit. This allow to not care about resource cleanup everywhere in the code, as it is processed globally.

The problem with atexit is it cleanup functions are called when the Python program exit. If the program is Certbot itself when used, this is Pytest in unit test execution. So during a unit test execution, cleanup is not called after a test and before its tearDown, but when Pytest exit, so way after tests and their respective tearDown.

But many tearDown implies to delete folders where this kind of resources are hold.

This is never a problem on Linux, thanks to its non-blocking file handling. It is usually not a problem on Windows, despite its blocking approach. But if the tearDown requires folder cleanup, exceptions are raised, and currently hidden as warnings. There is currently 504 exceptions of this type in Certbot core tests on Windows.

This PR starts to correct this situation. To do so, some of the functions cleanup normally called through atexit, are explicitly called as part of the tearDown process of relevant test classes, before directory removal is done. Theses situations come all from the certbot.tests.util.TempDirTestCase, so the code is in this specific tearDown process.

As a consequence, exceptions drop from 504 to 64.

Then there are still a significant part of them, that will be handled in later mitigation.

* Call atexit handlers before test tearDown to reduce errors on Windows

* Clear locks dict after global releasing execution

* Remove last tearDown errors.

* Clean out mock on open.

* Remove a test

* Reenable some tests
2019-01-29 19:25:05 -08:00
Adrien Ferrand
d436259437 Forcibly reactivate tls-sni-01 challenges until complete removal. (#6683)
This PR reactivates tls-sni-01 challenges on recent Boulder versions checkout for integration tests. This allows to continue testing this challenge until it is officially dropped from server (Boulder) and client (Certbot).

Reverts #6679.
2019-01-29 19:23:08 -08:00
Brad Warren
3bb7dd8faf Update test farm targets (#6700)
Fixes #6106.

AMIs were taken from https://wiki.debian.org/Cloud/AmazonEC2Image/Stretch and https://cloud-images.ubuntu.com/locator/ec2/.

I didn't update the AMI for Fedora due to #6698.

These new AMIs pass on all test farm tests we run during the release process except Ubuntu 18.04 and 18.10 fail on test_apache2.sh. This is tracked at #6706. If this PR lands before this issue is resolved, we should list these systems as expected failures in the release notes.

Adding these AMIs slows down our tests significantly. I didn't measure it, but it feels 50-100% slower at least on my setup. I think it's worth it though.

* Update test farm targets.

* use different ubuntu ami

* Fix test_leauto_upgrades.sh on newer OSes.
2019-01-29 16:12:32 -08:00
Brad Warren
6ddb4e2999 No numprocesses in pytest.ini part 2 (#6715)
* Remove --numprocesses from pytest.ini.

* Add --numprocesses to PYTEST_ADDOPTS in tox.ini.

* complexity--
2019-01-29 10:08:29 +01:00
schoen
b288ef60d0 Merge pull request #6703 from messa/patch-1
Fix code formatting in docs/using.txt
2019-01-28 15:50:56 -08:00
Brad Warren
8e5b2ac5b5 Stop multitester.py from eating errors. (#6705) 2019-01-25 23:24:11 +01:00
ohemorange
b5921cde7c Merge pull request #6707 from certbot/candidate-0.30.2
Release 0.30.2
2019-01-25 14:13:51 -08:00
ohemorange
8c076692c1 Merge branch 'master' into candidate-0.30.2 2019-01-25 13:44:38 -08:00
Erica Portnoy
b326adc2be Bump version to 0.31.0 2019-01-25 12:36:28 -08:00
Erica Portnoy
17f322d51f Add contents to CHANGELOG.md for next version 2019-01-25 12:36:28 -08:00
Erica Portnoy
6cba691c19 Release 0.30.2 2019-01-25 12:36:19 -08:00
Erica Portnoy
33ea6c5d98 Update changelog for 0.30.2 release 2019-01-25 12:15:41 -08:00
Brad Warren
53d13ff3a3 Update setuptools pinned in pipstrap (#6699) (#6704)
Fixes #6697.

This PR updates the version of setuptools pinned in pipstrap which works around the problems we have seen on recent OSes.

Why did I pick this version of setuptools? Because it's the latest and greatest, [supports all versions of Python that we do](https://github.com/pypa/setuptools/blob/v40.6.3/setup.py#L173), [has been out for a month and a half without the need for a point release](https://setuptools.readthedocs.io/en/latest/history.html), and has no non-optional dependencies.

For the last point about dependencies, I don't think we have too much to worry about. setuptools did have a period between versions 34.0.0 and 36.0.0 where they tried to have normal dependencies on other packages, but reverted these changes. See their [changelog for 36.0.0](https://setuptools.readthedocs.io/en/latest/history.html#v36-0-0).

You can also compare their [current setup.py file](https://github.com/pypa/setuptools/blob/v40.6.3/setup.py) to the [setup.py file for the currently pinned version](https://github.com/pypa/setuptools/blob/v29.0.1/setup.py) and you'll see [not much has changed](https://pastebin.com/nQj6d7D8).

Not only that, but I have successfully tested these changes on:

* ubuntu18.10
* ubuntu18.04LTS
* ubuntu16.04LTS
* ubuntu14.04LTS
* ubuntu14.04LTS_32bit
* debian9
* debian8.1
* amazonlinux-2015.09.1
* amazonlinux-2015.03.1
* RHEL7
* fedora23
* fedora29
* centos7
* centos6
* freebsd11
* macOS

* Update setuptools to 40.6.3.

* Build letsencrypt-auto.

* update changelog

* Don't use pipstrap in Dockerfile.centos6.

(cherry picked from commit b7211c3f39)
2019-01-25 11:53:29 -08:00
Brad Warren
b7211c3f39 Update setuptools pinned in pipstrap (#6699)
Fixes #6697.

This PR updates the version of setuptools pinned in pipstrap which works around the problems we have seen on recent OSes.

Why did I pick this version of setuptools? Because it's the latest and greatest, [supports all versions of Python that we do](https://github.com/pypa/setuptools/blob/v40.6.3/setup.py#L173), [has been out for a month and a half without the need for a point release](https://setuptools.readthedocs.io/en/latest/history.html), and has no non-optional dependencies.

For the last point about dependencies, I don't think we have too much to worry about. setuptools did have a period between versions 34.0.0 and 36.0.0 where they tried to have normal dependencies on other packages, but reverted these changes. See their [changelog for 36.0.0](https://setuptools.readthedocs.io/en/latest/history.html#v36-0-0).

You can also compare their [current setup.py file](https://github.com/pypa/setuptools/blob/v40.6.3/setup.py) to the [setup.py file for the currently pinned version](https://github.com/pypa/setuptools/blob/v29.0.1/setup.py) and you'll see [not much has changed](https://pastebin.com/nQj6d7D8). 

Not only that, but I have successfully tested these changes on:

* ubuntu18.10
* ubuntu18.04LTS
* ubuntu16.04LTS
* ubuntu14.04LTS
* ubuntu14.04LTS_32bit
* debian9
* debian8.1
* amazonlinux-2015.09.1
* amazonlinux-2015.03.1
* RHEL7
* fedora23
* fedora29
* centos7
* centos6
* freebsd11
* macOS

* Update setuptools to 40.6.3.

* Build letsencrypt-auto.

* update changelog

* Don't use pipstrap in Dockerfile.centos6.
2019-01-25 11:21:34 -08:00
Petr Messner
01ed2409b9 Fix code formatting in docs/using.txt 2019-01-25 16:02:38 +01:00
ohemorange
078b1da1d2 Merge pull request #6696 from certbot/candidate-0.30.1-point-release
Candidate 0.30.1 point release
2019-01-24 17:09:37 -08:00
Brad Warren
2bc4eb8637 Merge pull request #6695 from certbot/candidate-0.30.1
Candidate 0.30.1
2019-01-24 16:06:35 -08:00
Brad Warren
cc581387a9 s/0.31.1/0.30.1 2019-01-24 15:24:11 -08:00
Brad Warren
1c2fc9af45 Revert "pin back boulder"
This reverts commit 5de41572e4.
2019-01-24 15:19:36 -08:00
Brad Warren
5e4e597ae3 Merge branch 'master' into candidate-0.30.1 2019-01-24 15:18:53 -08:00
Brad Warren
18281766df Bump version to 0.31.0 2019-01-24 14:13:08 -08:00
Brad Warren
f0f1a4838e Add contents to CHANGELOG.md for next version 2019-01-24 14:13:07 -08:00
Brad Warren
fc8f70097b Release 0.30.1 2019-01-24 14:13:06 -08:00
Brad Warren
73713d7871 Update changelog for 0.30.1 release 2019-01-24 14:07:15 -08:00
ohemorange
566a702b09 Merge pull request #6694 from certbot/prep-0.30.1
Prep 0.30.1
2019-01-24 13:04:16 -08:00
Brad Warren
5de41572e4 pin back boulder 2019-01-24 12:40:05 -08:00
ohemorange
4c4dcf4987 Always download the pinned version of pip in pipstrap (#6691)
This will immediately address the breakage reported in #6682 and tracked at #6685. Virtualenv downloads the latest pip, which causes issues, so after virtualenv upgrades pip, downgrade to the pinned version.

I've confirmed that this fixes the issue on a machine that fails with the version of certbot-auto currently in master: recent version of virtualenv, python 2.7.

* Always download the pinned version of pip in pipstrap

* Run build.py

* Update changelog

* Remove unused variable

* Run build.py

(cherry picked from commit 9746c310d8)
2019-01-24 12:04:35 -08:00
ohemorange
9746c310d8 Always download the pinned version of pip in pipstrap (#6691)
This will immediately address the breakage reported in #6682 and tracked at #6685. Virtualenv downloads the latest pip, which causes issues, so after virtualenv upgrades pip, downgrade to the pinned version.

I've confirmed that this fixes the issue on a machine that fails with the version of certbot-auto currently in master: recent version of virtualenv, python 2.7.

* Always download the pinned version of pip in pipstrap

* Run build.py

* Update changelog

* Remove unused variable

* Run build.py
2019-01-24 12:03:21 -08:00
ohemorange
8b3cea6714 Remove commas in filename (#6692)
This will immediately address the breakage reported in #6682 and tracked at #6685. Pip 19.0.0 and 19.0.1 don't allow commas in filenames, so don't use commas in filenames in certbot-apache test code.

I've confirmed that this fixes the issue on a machine that fails with the version of certbot-auto currently in master: recent version of virtualenv, python 2.7.

Steps to test:

push master to test box
run tools/venv.py
activate venv
pip --version: 19.0.1
pip install ./certbot-apache/: fails
push branch code to test box
confirm pip --version still 19.0.1
pip install ./certbot-apache/: success

* Rename old,default.conf to old-and-default.conf

* Update changelog

* sites-enabled should contain a symlink to sites-available

(cherry picked from commit 34d655151d)
2019-01-24 11:51:37 -08:00
ohemorange
aee847a6fb Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv (#6690)
This will immediately address the breakage reported in #6682 and tracked at #6685. Virtualenv downloads the latest pip, which causes issues, so tell virtualenv to not download the latest pip.

I added the flag preemptively to other files as well, they're in separate commits so it will be easy to revert any spots we don't want.

I've confirmed that this fixes the issue on a machine that fails with the version of certbot-auto currently in master: recent version of virtualenv, python 2.7.

* Update changelog

* Use an environment variable instead of a flag for compatibility with old versions

* Run build.py

(cherry picked from commit 658b7b9d47)
2019-01-24 11:50:41 -08:00
ohemorange
34d655151d Remove commas in filename (#6692)
This will immediately address the breakage reported in #6682 and tracked at #6685. Pip 19.0.0 and 19.0.1 don't allow commas in filenames, so don't use commas in filenames in certbot-apache test code.

I've confirmed that this fixes the issue on a machine that fails with the version of certbot-auto currently in master: recent version of virtualenv, python 2.7.

Steps to test:

push master to test box
run tools/venv.py
activate venv
pip --version: 19.0.1
pip install ./certbot-apache/: fails
push branch code to test box
confirm pip --version still 19.0.1
pip install ./certbot-apache/: success

* Rename old,default.conf to old-and-default.conf

* Update changelog

* sites-enabled should contain a symlink to sites-available
2019-01-24 11:37:36 -08:00
ohemorange
658b7b9d47 Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv (#6690)
This will immediately address the breakage reported in #6682 and tracked at #6685. Virtualenv downloads the latest pip, which causes issues, so tell virtualenv to not download the latest pip.

I added the flag preemptively to other files as well, they're in separate commits so it will be easy to revert any spots we don't want.

I've confirmed that this fixes the issue on a machine that fails with the version of certbot-auto currently in master: recent version of virtualenv, python 2.7.

* Update changelog

* Use an environment variable instead of a flag for compatibility with old versions

* Run build.py
2019-01-24 10:03:52 -08:00
Brad Warren
dc82179395 Remove ACMEv1 example. (#6668) 2019-01-22 10:39:31 +01:00
Brad Warren
04ac84c4c1 Update pinned version of pyOpenSSL (#6571)
* Update pinned version of pyOpenSSL.
* Run build to update letsencrypt-auto.
2019-01-20 19:24:43 +01:00
Rishabh Kumar
a1313267f0 Remove dead autodeployment code (#6678)
Fixes #4315
2019-01-20 19:17:03 +01:00
Adrien Ferrand
dde27e5aef Remove tls-sni-01 challenges in integration tests (#6679)
* Remove tls-sni-01 challenges in integration tests

* Remove the tls-sni test in the less invasive way

* Correct code coverage from tls-sni logic not been tested anymore.

* Update certbot-boulder-integration.sh
2019-01-20 17:53:18 +02:00
Adrien Ferrand
0f1d78e897 Do not ignore editable distributions already installed. (#6675)
This PR fix tests when run with unpinned dependencies. Option --ignore-installed is removed from pip command, allowing to use local dev modules (like acme) that are not still released.

Be warned that these tests will mostly work correctly if the relevant virtualenv in .tox does not exist yet. Otherwise, already installed distribution, potentially not up-to-date, will be reused without update from pip if they match the minimal requirements in relevant projects. This is unlikely to be an issue on CI systems, that will start from a fresh system.

We may build a dedicated tox environment for unpinned dependencies tests purpose, and ensure consistency of these tests on all situations.

Fixes #6674
2019-01-18 09:47:43 -08:00
Brad Warren
b58d0e722d Remove certbot-auto code for Wheezy and Precise (#6672)
* Remove lsb_release dependency.

* Remove more code.
2019-01-18 15:27:31 +02:00
sydneyli
39eb66a8ad pin dependency-requirements.txt in Dockerfile (#6670)
Fixes #6580
2019-01-17 16:00:39 -08:00
Adrien Ferrand
bb1242c047 Process isolated local oldest requirements for DNS plugins (#6653)
* Include local-oldest-requirements in the constraints when oldest is invoked

* Add oldest constraints for lexicon

* Skip editable requirements during merge

* Pin to lexicon 2.2.1 for oldest requirements. Override locally oldest if needed for specific providers.
2019-01-17 15:21:13 -08:00
Adrien Ferrand
835f4b9271 Allow to execute a tox target without pinned dependencies (#6590)
This PR passes the CERTBOT_NO_PIN environment variable to the unit tests tox envs. By setting CERTBOT_NO_PIN to 1 before executing a given tox env, certbot dependencies will be installed at their latest version instead of the usual pinned version.

I also moved the unpin logic one layer below to allow it to be used potentially more widely, and avoid unnecessary merging constraints operation in this case.

As warnings are errors now, latest versions of Python will break now the tests, because collections launch a warning when some classes are imported from collections instead of collections.abc. Certbot code is patched, and warning is ignored for now, because a lot of third party libraries still depend on this behavior.

* Allow to execute a tox target without pinned dependencies

* Correct lint

* Retrigger build.

* Remove debug code

* Added test against unpinned dependencies from test-everything-unpinned-dependencies branch

* Remove duplicated assertion to pass TRAVIS and APPVEYOR in default tox environment.
2019-01-17 13:02:35 -08:00
Brad Warren
6f388945cd Remove reference to latest draft implemented. (#6669) 2019-01-17 09:41:46 -08:00
Brad Warren
693bac4b49 Update outdated tests (#6515)
These tests were running on Ubuntu Precise and Debian Wheezy which have reached their end of life and are no longer maintained by the respective distros. This updates the tests to a newer version of Debian and Ubuntu.

* Remove tests on the deprecated precise.

* Add tests for Xenial.

* update Jessie tests to use Wheezy

* update .travis.yml
2019-01-16 13:17:37 -08:00
Adrien Ferrand
d3a8a54c95 Setup cron build with specific jobs (#6661)
Fixes #6659

It appears that the conditional statement if: cron = type works as expected:

on a push event, jobs marked with this statement are completely ignored
on a cron event, these jobs are executed
This PR takes advantages of it, by integrating the specific environments from test-everything branch to be merged into master, and add the conditional statement to execute them only during cron builds.

Once this PR is merged, a cron job can be set to build master at the appropriate time of the day, and test-everything will not be needed anymore.

For execution examples from this branch, see:

during a push event: https://travis-ci.org/adferrand/certbot/builds/480127828
during a cron event: https://travis-ci.org/adferrand/certbot/builds/480130236

* Use the conditional expression in travis.yml to execute jobs from test-everything only during cron executions.

* Update .travis.yml

* Update .travis.yml

* Remove before_install
2019-01-16 12:16:54 -08:00
Adrien Ferrand
36c8a45713 Merge pull request #6658 from certbot/clarify-revoke-deletion
Clarify behavior for deleting certs as part of revocation
2019-01-15 22:14:06 +01:00
Erica Portnoy
340d546845 Update changelog 2019-01-15 12:11:22 -08:00
Erica Portnoy
6f754f63b1 Update --help for --delete-after-revoke to clarify that the flag deletes the entire lineage 2019-01-15 12:05:00 -08:00
Erica Portnoy
f7b8e1f99e Modify delete after revoke question to clarify that the flag deletes the entire lineage 2019-01-15 12:03:49 -08:00
Brad Warren
e34e0d9f1a Change build status links to travis-ci.com. (#6656) 2019-01-14 14:06:52 -08:00
Adrien Ferrand
651de2dd2f Update Certbot dependency to Lexicon to 3.x (#6593)
This PR updates Lexicon dependency to the latest version available, 3.0.6, for every lexicon-based DNS plugins. It updates also the provider construction to use the new ConfigResolverobject, and to remove the legacy configuration process.
2019-01-10 11:36:15 -08:00
Adrien Ferrand
8cf3bcd3f3 [Windows|Unix] Avoid to re-execute challenges already validated (#6551)
In response to #5342.

Currently, certbot will execute the operations necessary to validate a challenge even if the challenge has already been validated before against the acme ca server. This can occur for instance if a certificate is asked and issue correctly, then deleted locally, then asked again.

It is a corner case, but it will lead to some heavy operations (like updating a DNS zone, or creating an HTTP server) that are not needed.

This PR corrects this behavior by not executing challenges already validated, and use them directly instead to issue the certificate.

Fixes #5342

* Avoid to execute a given challenge that have been already validated by acme ca server.

* Execute tls challenge on a separate dns name, to avoid reusing the existing valid http challenge.

* Align with master

* Improve log

* Simplify the implementation

* Update changelog

* Add a unit test to ensure that validated challenges are not rerun
2019-01-09 12:52:53 -08:00
Sebastiaan Lokhorst
130c29e333 Remove some irrelevant history in using.rst (#6639)
The documentation only applies to the current version of Certbot.
Move Apache plugin details out of the table.
2019-01-09 12:47:44 -08:00
Brad Warren
95557fa9b4 Stop using staging in apacheconftests (#6647)
Fixes #6585.

I wrote up three suggestions for fixing this at https://github.com/certbot/certbot/issues/6585#issuecomment-448054502. I took the middle approach of requiring the user to provide an ACME server to use. I like this better than the other approaches which were:

> Resolve #5938 instead of this issue.

There is value in these tests as is over the compatibility tests in that they don't use Docker and run on different OSes.

> Spin up a local Python server to return the directory object.

Trying to set up a dummy ACME server seemed hacky and error prone.

Other notes about this PR are:

* I put the Pebble setup in `tox.ini` rather than `.travis.yml` as this seems much cleaner and more natural.
* I created a new `tox` environment called `apacheconftest-with-pebble` that reuses the code from `testenv:apacheconftest` so `apacheconftest` can continue to be used with servers other than Pebble like is done in our test farm tests.
* I chose the environment variable `SERVER` for consistency with our integration tests. I chose to not give this environment variable a default but to fail fast when it is not set.
* I ran test farm tests on this PR and they passed.
2019-01-09 12:37:45 -08:00
Adrien Ferrand
b52cbc0fb7 Reduce build log verbosity on Travis (#6597)
PR #6568 removed the --quiet option in pip invocations, because this option deletes a lot of extremely useful logs when something goes wrong. However, when everything goes right, or at least when pip install is correctly executed, theses logs add hundreds of lines that are only noise, making hard to debug errors that can be in only one or two lines.

We can have best of both worlds. Travis allows to fold large blocks of logs, that can be expanded directly from the UI if needed. It only requires to print in the console some specific code, that this PR implements in the pip_install.py script when the build is run in Travis (known by the existence of TRAVIS environment variable).

I also take the occasion to clean up a little tox.ini.

Note that AppVeyor does not have this fold capability, but it can be emulated using a proper capture of stdout/stderr delivered only when an error is detected.

* Fold pip install log on travis

* Global test env

* Export env variable
2019-01-08 20:45:16 -08:00
Adrien Ferrand
5130e9ba1e Correct the oldest requirements environment (#6569)
I observed that the current set of oldest requirements do not correspond to any environment, except the specific Xenial image in Travis CI (and standard Xenial containers will also fail).

It is because the requirements make cryptography and requests fail against standard libraries available in the typical Linux distributions that are targeted by the oldest requirements approach (Centos 6, Centos 7, Xenial, Jessie).

This PR fixes that, by aligning the minimal version requirements of cryptography and requests to the maximal versions that are available on Centos 6. Centos 7, Jessie and Xenial stay unusable with oldest requirements for other reasons, but at least one old and supported Linux distribution is able to run the tests with oldest requirements out of the box.

A test is also corrected to match the expected error message that old versions of urllib3 will raise.
2019-01-08 19:44:25 -08:00
schoen
15109f653a Merge pull request #6643 from trinopoty/issue_6624
Added sorting to renewal_conf_files()
2019-01-07 15:07:15 -08:00
schoen
01689af1ef Merge pull request #6641 from certbot/semver-https
Use HTTPS link to semver.org.
2019-01-07 14:32:57 -08:00
Adrien Ferrand
efaaf48f90 [Windows|Unix] Use double quote compatible both for windows and linux (#6553)
File _venv_common.py uses single quotes to ask pip to install setuptools>=30.3. Using single quotes to enclose a string is not supported on Windows shell (Batch). This PR replaces theses single quotes by double quotes supported both on Windows and Linux.
2019-01-07 12:00:01 -08:00
Brad Warren
33090ab77a Fix oldest nginx integration tests (#6642)
#6636 broke [test-everything tests](https://travis-ci.org/certbot/certbot/builds/475173804) because `_common.sh` is a common file shared between Certbot and Nginx integration tests and `--no-random-sleep-on-renew` isn't defined for the version of Certbot used in the "oldest" integration tests.

This PR adds code to `_common.sh` to check the Certbot version and if it's new enough, add `--no-random-sleep-on-renew` to the command line. I repurposed `$store_flags` and stopped exporting it because it's not used anywhere outside of this file.

Other approaches I considered and decided against were:

1. Adding this flag in `certbot-boulder-integration.sh`. I decided against it because it's setting us up for the same problem in the future if the oldest version of Certbot is upgraded in the Nginx tests and we call `certbot renew`.
2. Just upgrading the oldest version of Certbot required by Nginx to avoid these issues. While this would work (with perhaps some unnecessary burden for our packagers), I think it's avoiding the real problem here which should now be able to addressed easily with the addition of `$other_flags` and `version_at_least`.

* Add version_at_least().

* Conditionally disable sleep.

* Consolidate store_flags and other_flags.

* update comments
2019-01-04 12:44:31 -08:00
Trinopoty Biswas
233cfe5207 Added sorting to renewal_conf_files() 2019-01-05 00:44:12 +05:30
Brad Warren
fd023cc3ea Use HTTPS link to semver. 2019-01-04 09:27:45 -08:00
schoen
3fa3ffea71 Merge pull request #6638 from certbot/mawkish
Compatibility with more traditional versions of awk
2019-01-03 18:14:03 -08:00
Seth Schoen
59bbda51ab Compatibility with more traditional versions of awk 2019-01-03 17:48:09 -08:00
Brad Warren
ec297ccf72 Add missing acme.jose attribute. (#6637)
Fixes the problem at https://github.com/certbot/certbot/pull/6592#discussion_r245106383.

The tests use `eval` which neither myself or `pylint` like very much. I started to change this by splitting the path we wanted to test and repeatedly calling `getattr`, but it didn't seem worth the effort to me.

* Add missing acme.jose attribute.

* update changelog
2019-01-03 12:46:25 -08:00
Brad Warren
3cb6d6c25b Don't sleep in integration tests (#6636)
Fixes #6635.

* Don't sleep in integration tests.

* add backslash
2019-01-03 11:26:15 -08:00
Brad Warren
bb0f356610 Tell people to update changed package list. (#6633) 2019-01-02 16:15:25 -08:00
Brad Warren
ecab26e2fd Merge pull request #6632 from certbot/candidate-0.30.0
Release 0.30.0
2019-01-02 14:29:09 -08:00
Erica Portnoy
c25e6a8adf Bump version to 0.31.0 2019-01-02 12:33:31 -08:00
Erica Portnoy
ab4942cb1a Add contents to CHANGELOG.md for next version 2019-01-02 12:33:31 -08:00
Erica Portnoy
3971573d7a Release 0.30.0 2019-01-02 12:33:19 -08:00
Erica Portnoy
ba358f9d07 Update changelog for 0.30.0 release 2019-01-02 12:01:39 -08:00
ohemorange
1cdcc15e64 Pin all dependency installation in the release script (#6584) (#6602)
Fixes #6584.

* Pin all dependency installation in the release script

* also use pip_install.py to install pytest
2019-01-02 10:08:08 -08:00
Adrien Ferrand
856bfe3544 Tag to disable random sleep upon a renew task (#6599)
* Extraction from #6541 to add flag to disable shuffle sleep on renew action

* Move the logic of random sleep to execute it only if there is effectively a certificate to renew.

* Add comments

* Correct lint

* Suspend lint rule

* Revert code cleaning

* Hide the flag

* Ignore lint

* Update cli.py
2018-12-18 16:17:54 -08:00
Brad Warren
f905610122 add mypy to envlist (#6610) 2018-12-17 16:22:03 -08:00
Brad Warren
62cb67ec67 default to any py3 test (#6609) 2018-12-17 15:38:28 -08:00
Brad Warren
e3cb782e59 Allow josepy to be accessed through acme.jose. (#6592)
When working on an update to our packages in Ubuntu Xenial, @NCommander noticed that importing code through acme.jose no longer works since josepy became a separate package and remembers having to fix up some code that was using acme.jose himself.

This PR should fix that problem by making all of josepy accessible through acme.jose. This is primarily beneficial to our OS package maintainers who want to avoid subtle API changes when updating packages in stable repositories. They will likely backport this change, but I figure we might as well add it ourselves to minimize divergences in our OS packages in the future and avoid problems on the off chance someone hasn't upgraded acme and was relying on this feature.
2018-12-17 13:23:57 -08:00
Brad Warren
346a424639 Update pinned urllib3 (#6601)
GitHub notified us about a security vulnerability in our pinned version of `urllib3` earlier this week. It doesn't affect us, but we might as well upgrade anyway. I checked:

* There are no backwards incompatible features we care about listed at https://github.com/urllib3/urllib3/blob/master/CHANGES.rst.
* urllib3's dependencies don't also need to be updated according to https://github.com/urllib3/urllib3/blob/1.24.1/setup.py.
* The hashes match when obtained from different network vantage points.
2018-12-13 15:54:38 -08:00
Adrien Ferrand
6b145a480e Correct boulder integration tests using the latest challtestsrv version (#6600) 2018-12-13 11:13:39 -08:00
dschlessman
f137d55b31 Issue 3816/revamp register subcommand (#6006)
* address issue #3816

* formatting update

* remove unused variable

* address pylint trailing whitespace error

* revert whitespace add

* update boulder ci test for new update_registration verb

* address code review comments

* Issue 3816: Revert renaming '...update_regristration...' tests to '...update_account...'. Fix removing update_registration default argument value.

* Issue 3816: Fix '--update-registration' not referring to 'update_registration' default as opposed to 'update_account'.

* Issue 3816: delint tox output.

* Issue 3816: Change @example.org domain to @domain.org in boulder test script

* Issue 3816: Update CHANGELOG.md for Issue 3816 and remove extraneous space in main.py

* Issue 3816: Remove extraneous default variable.
2018-12-11 18:57:33 -08:00
schoen
a8a1942ee2 Merge pull request #6574 from certbot/no-more-tls-sni-01
Remove references to TLS-SNI-01
2018-12-10 22:55:28 -08:00
Seth Schoen
64e570d63c Remove spurious comma 2018-12-10 17:49:24 -08:00
Seth Schoen
38ae7c8f99 An unspecified number of challenges exist 2018-12-10 17:48:59 -08:00
Seth Schoen
6c06a10d0a Remove motivation for combining plugins, add 2nd example 2018-12-10 16:28:28 -08:00
Seth Schoen
5a8bea4580 Consistent capitalization for list 2018-12-10 16:18:57 -08:00
Seth Schoen
9e1ee01547 "Four shalt thou not count, neither count thou two" 2018-12-10 16:17:30 -08:00
Seth Schoen
85f8f68263 Documentation fix-ups 2018-12-07 14:18:28 -08:00
Seth Schoen
ecc1c5ddb5 Merge remote-tracking branch 'georgio/TLS-SNI-01-Deprecation-1' into no-more-tls-sni-01 2018-12-07 13:36:49 -08:00
Seth Schoen
bc9865371a Merge remote-tracking branch 'georgio/TLS-SNI-01-Deprecation' into no-more-tls-sni-01 2018-12-07 13:36:47 -08:00
Brad Warren
353d092585 Remove -q/--quiet from pip invocations. (#6568)
While reducing noise in test output is valuable, this flag has made a couple aspects of Certbot's development difficult:

1. We test with different sets of dependencies and running pip in quiet mode removes all output about the packages being installed which has made reviewing changes to these tests more difficult.
2. When pip fails, it provides significantly less output about the failure in quiet mode than it does normally. The output is reduced so much that in the two times I've hit this issue in the last month, I was only able to see that installing package X failed rather than what the cause of that failure was which could be seen with `--quiet` removed.

Also, since running pip without `--quiet` is the tox default, I expect Python developers to be familiar with what they see here.
2018-12-06 16:02:16 -08:00
Adrien Ferrand
adedcc6416 Reduce to the minimal requirements to ensure Windows compatibility: execute tests only against lower and higher version of Python supported by Certbot on Windows. (#6565) 2018-12-06 09:33:46 -08:00
Brad Warren
bdfda3005e Merge pull request #6564 from certbot/candidate-0.29.1
Candidate 0.29.1
2018-12-05 17:45:42 -08:00
ohemorange
cb1226c938 Merge branch 'master' into candidate-0.29.1 2018-12-05 17:23:30 -08:00
Erica Portnoy
82cfff8d35 Bump version to 0.30.0 2018-12-05 16:31:09 -08:00
Erica Portnoy
5ff4fa91b0 Add contents to CHANGELOG.md for next version 2018-12-05 16:31:08 -08:00
Erica Portnoy
be8638dad0 Release 0.29.1 2018-12-05 16:31:07 -08:00
Erica Portnoy
87215c7faf Update changelog for 0.29.1 release 2018-12-05 15:47:59 -08:00
Brad Warren
1651bdd86b Fix default directories on Linux (#6560) (#6562)
* fix default directories

* update changelog

(cherry picked from commit 11e0fa52d0)
2018-12-05 15:37:06 -08:00
Brad Warren
11e0fa52d0 Fix default directories on Linux (#6560)
* fix default directories

* update changelog
2018-12-05 14:52:58 -08:00
Brad Warren
59a6d94b0a Merge pull request #6558 from certbot/candidate-0.29.0
Candidate 0.29.0
2018-12-05 14:12:16 -08:00
Brad Warren
3edfe92069 Bump version to 0.30.0 2018-12-05 10:57:46 -08:00
Brad Warren
245dbc7a95 Add contents to CHANGELOG.md for next version 2018-12-05 10:57:45 -08:00
Brad Warren
6476663516 Release 0.29.0 2018-12-05 10:57:43 -08:00
Brad Warren
d151481dea Update changelog for 0.29.0 release 2018-12-05 10:52:20 -08:00
Brad Warren
9b9ba9b5fe List packages that changed in CHANGELOG. 2018-12-05 10:52:02 -08:00
sydneyli
9c0b27de68 Preserve other-read bit on private keys too (#6544)
Not updating the guidelines in using.rst-- I don't want to encourage people to use this. now that Certbot preserves gid, the better way to set permissions is to change the group permisisons!

* Preserve other-read bit on private keys too

* fix integration test

* fix and rename permission routines in integration tests
2018-12-04 10:59:23 -08:00
sydneyli
f5aad1440f Conditionally depend on imgconverter for newer versions of Sphinx (#6536)
Fixes #6343.

* conditionally depend on imgconverter

* Pin docutils dependency for old Sphinx bug
2018-12-04 10:56:15 -08:00
ohemorange
daa77b5023 During the release, reset local-oldest-requirements.txts that might have been changed during development. (#6547)
* During the release, reset local-oldest-requirements.txts that might have been changed during development.

* only modify the file if it exists

* no need to specifically add certbot-compatibility-test because it doesn't have the file anyway

* Use double quotes instead of single

* escape dots and brackets

* Escape and quote correctly
2018-12-03 16:42:52 -08:00
Oshawk
d30afbea57 Remove duplicate route53 options (#6405)
* Remove duplicate route53 options

Duplicate route53 option (certbot-route53:auth) removed.

* Remove duplicate route53 options from help

Removed duplicate route53 options from help by adding a couple of if statements in cli.py.

* Fix formatting

* Use cleaner solution to remove duplicate route53 options from help

Used a cleaner method to remove the duplicate option from the help text

* Fix line too long

* Add regression test

Added a regression test and fixed a couple of minor issues.

* Fix trailing whitespace

* Add missing newline

* Put newline in correct place
2018-12-03 15:32:47 -08:00
Robert Kästel
1f4297d0ed WIP External Account Binding (#6059)
* Rough draft of External Account Binding.

* Remove parameter --eab and namespace kid and hmac. Also add parameters to "register" subcommand.

* Refactor as much as possible of the EAB functionality into ExternalAccountBinding class.

* Remove debug line.

* Added external account binding to Directory.Meta.

* Rename to account_public_key, hmac_key and make some non-optional.
Rename command line argument to --eab-hmac-key.

* Error out when the server requires External Account Binding and the user
has not supplied kid and hmac key.

* Remove whitespace.

* Refactor a bit to make it possible to set the url argument.

* Move from_data method into client.

* Revert "Move from_data method into client."

This reverts commit 8963fae

* Refactored to use json field on Registration.

* Inherit from object according to Google Python Style Guide.

* Move to two separate ifs.

* Get tests to pass after External Account Binding additions.

* messages.py back to 100% test coverage with some EAB tests.

* .encode() this JSON key.

* Set eab parameter default values to None.

* * Remove unnecessary public key mock on most of the test.
* Restructure the directory mock to be able to mock both True and False for externalAccountRequired easily.
* Add EAB client tests.

* Move external_account_required check into BackwardsCompatibleClientV2 to be able to mock it.

* Update versions.

* Try 0.29.0.

* Revert "Try 0.29.0."

This reverts commit 5779509

* Try 0.29.0 again.

* Try this.

* Fix pylint failures.

* Add tests for external_account_required method.

* Test not needed, avoid:
************* Module acme.client_test
C:  1, 0: Too many lines in module (1258/1250) (too-many-lines)

* Move real external_account_required method into ClientV2 and pass through to it in BackwardsCompatibleClientV2.

* Handle missing meta key in server ACME directory.

* Add docstring for BackwardsCompatibleClientV2.external_account_required().

* Add tests for BackwardsCompatibleClientV2.external_account_required().

* Fix coverage for ACMEv1 code in BackwardsCompatibleClientV2.

* Disable pylint too-many-lines check for client_test.py.

* Fix versions.

* Remove whitespace that accidently snuck into an earlier commit.

* Remove these two stray whitespaces also.

* And the last couple of whitespaces.

* Add External Account Binding to changelog.

* Add dev0 suffix to setup.py.

Co-Authored-By: robaman <robert@kastel.se>

* Set to "-e acme[dev]" again.

Co-Authored-By: robaman <robert@kastel.se>
2018-12-03 15:27:35 -08:00
Brad Warren
37d4b983c8 Don't use pytest.ini during the release. (#6550)
Currently the release script in master fails for a few reasons. First, it's trying to use --numprocesses which comes from a pytest plugin that we're not installing in the release script. Second, many new warnings are raised when we're not using pinned versions of our dependencies.

I'm not sure I agree, but one could argue that we should fix these issues and use the file during the release. I'm particularly hesitant for us to do this when it comes to warnings. We currently do not pin our dependencies in the release script. Do we really want to stop the release because a new package was released and is warning about something? One could argue we do because these warnings may be visible to the user, but they very rarely are and I think this makes the release process much too painful.

I especially do not think we should block the release on this now as we are not up to date on the warnings raised by the latest versions of our packages so there is a lot to work through.

* Don't use pytest.ini during the release.

* State that pytest.ini isn't used in release script.
2018-11-30 17:40:55 -08:00
Brad Warren
11dae13c2f Fix oldest Certbot version that works with Apache. (#6549) 2018-11-30 17:40:30 -08:00
hannay Mohanna
d461655ec6 removed unused sys imports (#6546) 2018-11-30 15:59:19 -08:00
Brad Warren
73458daac9 whitelist docker-compose (#6516) 2018-11-30 14:44:47 -08:00
Adrien Ferrand
0b5468e992 Implement POST-as-GET requests (#6522)
* Setup an integration tests env against Pebble, that enforce post-as-get

* Implement POST-as-GET requests, with fallback to GET.

* Fix unit tests

* Fix coverage.

* Fix or ignore lint errors

* Corrections after review

* Correct test

* Try a simple delegate approach

* Add a test

* Simplify test mocking

* Clean comment
2018-11-29 19:42:06 -05:00
Brad Warren
8b5ac9e257 Ask people not to rewrite commits. (#6538) 2018-11-29 18:42:08 -05:00
sydneyli
7d0ac47139 Change default privkey permissions while preserving group permissions (#6480)
Fixes #1473.

writes privkey.pem to 0600 by default for new lineages
on renewals where a new privkey is generated, preserves group mode and gid
Things this PR does not do:

we talked about forcing 0600 on privkeys when a Certbot upgrade is detected. Instead, this PR only creates new lineages with the more restrictive permission to prevent renewal breakages.
this doesn't solve many of the problems mentioned in #1473 that are not directly related to the title issue!

* safe_open on archive keyfiles

* keep group from current lineage

* clean up integration test

* safe_open can follow symlinks

* fix tests on windows, maybe

* Address Brad's comments

* Revert changes to safe_open
* Test chown is called when saving new key
* Reorder chown operation

* Changelog and documentation

* Fix documentation style
2018-11-29 09:33:05 -08:00
Jacob Hoffman-Andrews
7527a6c959 Remove "beta" label from Apache plugin. (#6537) 2018-11-28 19:01:05 -08:00
Brad Warren
6310d1e996 Merge pull request #6455 from certbot/warnings-are-errors
Fixes #6080
2018-11-28 09:05:10 -08:00
Erica Portnoy
1c23fea076 ignore erroneously no-member lint error 2018-11-27 17:24:34 -08:00
Erica Portnoy
3e155d443d Merge branch 'master' into warnings-are-errors 2018-11-27 17:18:55 -08:00
schoen
a6ac2269b6 Merge pull request #6488 from certbot/whats-a-certbot
Clarify letsencrypt-auto to certbot-auto message
2018-11-26 17:46:53 -08:00
schoen
32fb5b3c47 Merge pull request #6525 from b3n4kh/google-dns-typo-fix
Fix example filename to end with ".json"
2018-11-26 17:46:08 -08:00
schoen
a7f65eb5ac Merge pull request #6517 from certbot/whats_a_venv_common
Don't mention _venv_common.sh in certbot-auto.
2018-11-26 17:44:30 -08:00
ohemorange
f65cb070b3 Automation for changelog changes during release (#6489)
* Automation for changelog changes during release

* Update changelog during release before modifying version numbers

* don't link to the GitHub repo

* no need to sign the commit bumping version numbers

* simplify tail call
2018-11-26 17:48:59 -05:00
schoen
76bfe09393 Merge pull request #6514 from ye/issue-3190
Add clarification of what "$domain" means in the case of creating a SAN cert.
2018-11-26 12:13:23 -08:00
schoen
ff66b641e3 Re-adding period 2018-11-26 11:46:57 -08:00
Erica Portnoy
3edb36c4cc Revert acme/acme/client.py 2018-11-22 03:43:25 +00:00
Erica Portnoy
fe840d5d46 Merge branch 'master' into warnings-are-errors 2018-11-22 03:03:43 +00:00
Adrien Ferrand
41bf9c70f6 Update pinned version of cffi to 1.11.5 (#6512)
Current pinned version of cffi is 1.10.0. This version does not provide pre-compiled wheels for latest Python versions on Windows. This implies on this plateform, when certbot is installed, to compile cffi from sources.

But for that, the computer will need to have the Visual C compiler available locally. This environnement is really heavy to setup, and totally outside of the scope.

This PR updates cffi to version 1.11.5, that has the required wheels, and makes certbot installable without a full .NET dev profile.
2018-11-21 14:49:04 -08:00
schoen
e8e3534335 Add a random sleep for noninteractive renewals (#6393)
* WIP on adding a random sleep for noninteractive renewal

* Update changelog

* Log the fact that we're randomly sleeping

* stdin may better define interactivity than stdout

* Try mocking time.sleep for all tests

* Move mocked sleep elsewhere

* mock the right object

* Somewhat ugly synthetic PTY trick

* Move set -u down below self-exec

* Revert "Move set -u down below self-exec"

This reverts commit 6bde65a738.

* Revert "Somewhat ugly synthetic PTY trick"

This reverts commit 89c704a4be.

* Log specific duration of random sleep

* Test coverage for random sleep() logic in main.py
2018-11-20 23:55:51 -05:00
ohemorange
ca42945264 Fix test_sdists test farm test (#6524)
* Switch to using _venv_common.py in test_sdists.sh

* Upgrade setuptools in _venv_common.py

* Upgrade setuptools before running pip_install
2018-11-20 18:39:12 -05:00
Adrien Ferrand
a23d76beb0 [Windows] Change default paths for Certbot when run on Windows (#6416)
Defaults path of Certbot are the following:

config: /etc/letsencrypt
workdir: /var/letsencrypt/lib
logs: /var/letsencrypt/log
On Windows, this translate into:

config: C:\etc\letsencrypt
workdir: C:\var\letsencrypt\lib
logs: C:\var\letsencrypt\log
As Windows does not follow the standard POSIX filesystem layout, theses paths do not have a lot of sense in this case.

This PR sets the following default paths when Certbot is run on Windows:

config: C:\Certbot
workdir: C:\Certbot\lib
logs: C:\Certbot\log
Better to decide the default paths for Certbot before users start to run it on Windows, to avoid future migration procedures.
2018-11-20 14:06:09 -08:00
Benjamin Akhras
fce5af50fd Fixed Typo in the examples section since .ini files are not supported. 2018-11-20 21:48:20 +01:00
Adrien Ferrand
1dd7db12e0 Workaround for old pip versions that are not exposed as importable modules. (#6500)
Fallback to pipstrap 1.5.0. Manipulate PATH variable on higher level to activate the virtual environment before calling pipstrap.
2018-11-19 15:38:37 -08:00
Adrien Ferrand
78cf8ec4de Protect certbot-auto against automated downgrades (#6448)
With current code, the certbot-auto self-upgrade process can make it actually to downgrade itself, because the comparison done is an equality test between local certbot-auto version and the remote one. This is a flaw for attackers, that could make certbot-auto break itself by falsely advertising it about an old version as the latest one available.

A function is added to make a more advanced comparison between version. Certbot-auto will upgrade itself only if the local version is strictly inferior to the latest one available. For instance, a version 0.28.0 will not upgrade itself if the latest one available on internet is 0.27.1. Similarly, non-official versions like 0.28.0.dev0 will never trigger a self-upgrade, to help development workflows.

This implementation relies only on the Python distribution installed by certbot-auto (supporting 2.7+) and basic shell operations, to be compatible with any UNIX-based system.

* Check version with protection again downgrade

* Create a stable version of letsencrypt-auto to use correctly self-upgrade functionality

* Update letsencrypt-auto-source/letsencrypt-auto.template
2018-11-19 14:28:59 -08:00
Brad Warren
4e1c22779e Fix up environment variable use in venv creation scripts (#6518)
This PR has the value of VENV_NAME override any value set in the tools/venv* scripts.

I also removed the use of VENV_ARGS. This was used in _venv_common.sh as a means of passing arguments for virtualenv between the scripts, however, there is no other use of the variable in this repository and passing the arguments through a function call is much more natural in Python.

* Respect VENV_NAME in tools/venv*.

* Stop using VENV_ARGS

* Remove VENV_NAME_ENV_VAR and add docstrings.
2018-11-19 11:47:14 -08:00
Erica Portnoy
5700af594b Merge branch 'master' into warnings-are-errors 2018-11-17 02:29:20 +00:00
Brad Warren
ca12921a60 Don't mention _venv_common.sh in certbot-auto.
This wasn't always the case, but nowadays, _venv_common is a developer tool and
has nothing to do with certbot-auto.
2018-11-16 14:28:29 -08:00
Ye Wang
7fe64c3b9b Add clarification of what means in the case of creating a SAN cert. 2018-11-16 12:37:06 -05:00
Adrien Ferrand
5073090a20 Update tools/venv3.py to support py launcher on Windows (#6493)
Following some inconsistencies occurred during by developments, and in the light of #6508, it decided to wrote a PR that will take fully advantage of the conversion from bash to python to the development setup tools.

This PR adresses several issues when trying to use the development setup tools (`tools/venv.py` and `tools/venv3.py`:
* on Windows, `python` executable is not always in PATH (default behavior)
* even if the option is checked, the `python` executable is not associated to the usually symlink `python3` on Windows
* on Windows again, really powerful introspection of the available Python environments can be done with `py`, the Windows Python launcher
* in general for all systems, `tools/venv.py` and `tools/venv3.py` ensures that the respective Python major version will be used to setup the virtual environment if available.
* finally, the best and first candidate to test should be the Python executable used to launch the `tools/venv*.py` script. It was not relevant before because it was shell scripts, but do it is.

The logic is shared in `_venv_common.py`, and will be called appropriately for both scripts. In priority decreasing order, python executable will be search and tested:
* from the current Python executable, as exposed by `sys.executable`
* from any python or pythonX (X as a python version like 2, 3 or 2.7 or 3.4) executable available in PATH
* from the Windows Python launched `py` if available

Individual changes were:

* Update tools/venv3.py to support py launcher on Windows

* Fix typo in help message

* More explicit calls with space protection

* Complete refactoring to take advantage of the python runtime, and control of the compatible version to use.
2018-11-15 15:17:36 -08:00
Adrien Ferrand
b3d2ac5161 Fail-fast in test/cover/lint scripts (#6487)
After #6485 and #6435, it appears that there is no good reason to not fail fast when test, cover or linting scripts are executed.

This PR ensures to fail fast by invoking commands throught subprocess.check_call instead of subprocess.call, and by removing the handling of non-zero exit code at the end of theses scripts.

As now coverage on Windows is executed with thresholds, I added specific thresholds for this platform. Because some portions of code that are done for Unix platform will not be executed on Windows.

Note that coverage reports from Travis and AppVeyor are accumulated on Codecov. So if a file is covered up to 50 % on Linux, and all other parts are covered on Windows, then coverage is 100 % for Codecov.

Note: that PR also fixes the ability of coverage tests to fail if thresholds are exceeded.

* Use check_call to fail fast in all scripts related to tests/lint/coverage/deploy

* Make specific coverage threshold for windows
2018-11-14 13:57:40 -08:00
Adrien Ferrand
ad885afdb8 Correct venv3 detection on windows (#6490)
A little typo in the _venv_common.py block the script to finish correctly once the virtual environment has been setup on Windows.

This PR fixes that.
2018-11-09 16:17:17 -08:00
Brad Warren
ee6f20d93d Clarify letsencrypt-auto to certbot-auto message 2018-11-08 15:39:24 -08:00
Adrien Ferrand
7352727a65 [URGENT] Fix the CI system (#6485)
It is about the exit codes that are returned from the various scripts in tools during tox execution.
Indeed, tox relies on the non-zero exit code from a given script to know that something failed during the execution.

Previously, theses scripts were in bash, and a bash script returns an exit code that is the higher code returned from any of the command executed by the script. So if any command return a non-zero (in particular pylint or pytest), then the script return also non-zero.

Now that these scripts are converted into python, pylint and pytest are executed via subprocess, that returns the exit code as variables. But if theses codes are not handled explicitly, the python script itself will return zero if no python exception occured. As a consequence currently, Certbot CI system is unable to detect any test error or lint error, because there is no exception in this case, only exit codes from the binaries executed.

This PR fixes that, by handling correctly the exit code from the most critical scripts, install_and_test.py and tox.cover.py, but also all the scripts that I converted into Python and that could be executed in the context of a shell (via tox or directly for instance).
2018-11-08 08:35:07 -08:00
Adrien Ferrand
3d0e16ece3 [Windows|Unix] Rewrite bash scripts for tests into python (#6435)
Certbot relies heavily on bash scripts to deploy a development environment and to execute tests. This is fine for Linux systems, including Travis, but problematic for Windows machines.

This PR converts all theses scripts into Python, to make them platform independant.

As a consequence, tox-win.ini is not needed anymore, and tox can be run indifferently on Windows or on Linux using a common tox.ini. AppVeyor is updated accordingly to execute tests for acme, certbot and all dns plugins. Other tests are not executed as they are for Docker, unsupported Apache/Nginx/Postfix plugins (for now) or not relevant for Windows (explicit Linux distribution tests or pylint).

Another PR will be done on certbot website to update how a dev environment can be set up.

* Replace several shell scripts by python equivalent.

* Correction on tox coverage

* Extend usage of new python scripts

* Various corrections

* Replace venv construction bash scripts by python equivalents

* Update tox.ini

* Unicode lines to compare files

* Put modifications on letsencrypt-auto-source instead of generated scripts

* Add executable permissions for Linux.

* Merge tox win tests into main tox

* Skip lock_test on Windows

* Correct appveyor config

* Update appveyor.yml

* Explicit coverage py27 or py37

* Avoid to cover non supported certbot plugins on Windows

* Update tox.ini

* Remove specific warnings during CI

* No cover on a debug code for tests only.

* Update documentation and help script on venv/venv3.py

* Customize help message for Windows

* Quote correctly executable path with potential spaces in it.

* Copy pipstrap from upstream
2018-11-07 17:16:16 -08:00
Brad Warren
b17c322483 Merge pull request #6483 from certbot/candidate-0.28.0-2
Release 0.28.0 part 2
2018-11-07 16:30:04 -08:00
Brad Warren
63e0f56784 update changelog for 0.29.0 2018-11-07 15:56:29 -08:00
Brad Warren
22858c6025 Bump version to 0.29.0 2018-11-07 13:22:59 -08:00
Brad Warren
c1300a8e1b Release 0.28.0 2018-11-07 13:22:57 -08:00
Brad Warren
f3ff548a41 Update changelog for 0.28.0 release. 2018-11-07 13:02:25 -08:00
Adrien Ferrand
e6e323e3ff Update Lexicon to correct use of HTTP proxy on OVH provider (#6479)
This PR update requirement of Lexicon to 2.7.14 on OVH plugin, to allow HTTP proxy to be used correctly when underlying OVH provider is invoked.

* Update Lexicon to correct use of HTTP proxy on OVH provider

* Update dev_constraints.txt

* Update CHANGELOG.md
2018-11-07 07:49:13 -08:00
Adrien Ferrand
4edfb3ef65 [Windows] Handle file renaming when the destination path already exists (#6415)
On Linux, you can invoke os.rename(src, dst) even if dst already exists. In this case, destination file will be atomically replaced by the source file.

On Windows, this will lead to an OSError because changes are not atomic. This cause certbot renew to fail in particular, because the old certificate configuration needs to be replace by the new when a certificate is effectively renewed.

One could use the cross-platform function os.replace, but it is available only on Python >= 3.3.

This PR add a function in compat to handle correctly this case on Windows, and delegating everything else to os.rename.

* Cross platform compatible os.rename (we can use os.replace if its python 3)

* Use os.replace instead of custom non-atomic code.

* Avoid errors for lint and mypy. Add a test.
2018-11-06 15:35:09 -08:00
Erica Portnoy
92989956f9 no newline in py27 2018-11-05 17:47:38 -08:00
Erica Portnoy
39a008eb83 ignore our own TLS-SNI-01 warning 2018-11-05 17:42:19 -08:00
Erica Portnoy
91b3c5d61c remove pytest.mark, move to specific ignore in pytest.ini 2018-11-05 17:41:26 -08:00
Erica Portnoy
c3fa05ba74 remove unused six imports 2018-11-05 17:38:29 -08:00
Erica Portnoy
5dc9dd8dea Pin requests' dependencies in certbot-auto 2018-11-05 17:34:33 -08:00
Erica Portnoy
6c8652a0a6 add comment explaining about boto* in oldest_constraints.txt 2018-11-05 17:30:26 -08:00
Erica Portnoy
b7f4b33ffb Remove module-level ignore::ResourceWarnings 2018-11-05 17:28:26 -08:00
Erica Portnoy
79b2ea19fb no need for U flag because we won't support py2 on windows 2018-11-05 17:19:57 -08:00
Erica Portnoy
9cc5d18b97 Merge branch 'master' into warnings-are-errors 2018-11-05 17:17:51 -08:00
ohemorange
47062dbfbf update changelog (#6476) 2018-11-05 17:09:03 -08:00
Erica Portnoy
0e98904bec Merge branch 'master' into warnings-are-errors 2018-11-05 16:57:50 -08:00
ohemorange
cbdc2ee23b Log warning about TLS-SNI deprecation in Certbot (#6468)
For #6319.

* print warning in auth_handler

* add test
2018-11-05 15:01:16 -08:00
ohemorange
cb8dd8a428 Warn when using deprecated acme.challenges.TLSSNI01 (#6469)
* Warn when using deprecated acme.challenges.TLSSNI01

* Update changelog

* remove specific date from warning

* add a raw assert for mypy optional type checking
2018-11-05 14:50:20 -08:00
Adrien Ferrand
bc7763dd0f Lexicon v3 compatibility (#6474)
* Propagate correctly domain to lexicon providers

* Pass required parameter to ovh provider

* Fix all other lexicon-based dns plugins
2018-11-05 14:07:09 -08:00
ohemorange
9403c1641d Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins (#6461)
* flip challenge preference in Nginx

* Fix Nginx tests

* Flip challenge preference in Apache

* Flip challenge preference in standalone

* update changelog

* continue to run with tls-sni in integration tests for coverage
2018-11-05 13:58:56 -08:00
Georgio Nicolas
9fa0a58545 Remove mention of TLS-SNI-01 2018-11-04 01:29:38 -04:00
Georgio Nicolas
94f0a915c0 Remove mentioning of TLS-SNI-01 2018-11-04 01:23:47 -04:00
ohemorange
2c1964c639 Use the ACMEv2 newNonce endpoint when a new nonce is needed (#6442)
Also, add checking to the newNonce HEAD request, and check responses in general before attempting to save a nonce, for a better error message.

* check response before adding nonce to the pool

* fix tests so that they test what they're supposed to test, and also allow the order of _add_nonce and _check_response to be switched

* make _get_nonce take acme_version

* Send HEAD to newNonce endpoint when using ACMEv2

* check the HEAD newNonce response

* remove unnecessary try; get returns None if the item doesn't exist

* instead of setting new_nonce_url on ClientNetwork, use the saved directory in ClientBase and pass that into ClientNetwork.post

* no need to test acme_version in _get_nonce

* pop new_nonce_url out of kwargs before passing to _send_request
2018-11-02 17:32:33 -07:00
Erica Portnoy
eab7aa7bf1 Update changelog 2018-11-02 16:14:10 -07:00
Erica Portnoy
bbf89781c9 Merge branch 'master' into warnings-are-errors 2018-11-02 16:12:39 -07:00
Erica Portnoy
c98bf18a77 Use older version of boto in oldest tests, because new versions can't handle old versions of requests 2018-11-02 16:00:32 -07:00
Erica Portnoy
0caaf872fb bring requests back down to 2.4.1 in setup and oldest constraints 2018-11-02 15:53:44 -07:00
Erica Portnoy
ac8d6f58bb Update requests version in oldest-constraints.txt 2018-11-02 15:30:42 -07:00
Erica Portnoy
1228f5da99 update letsencrypt-auto 2018-11-02 14:59:29 -07:00
Erica Portnoy
64a61fa6d4 Use a newer version of botocore that doesn't vendor requests, which we need because newer versions of requests don't have the DeprecationWarning 2018-11-02 14:53:55 -07:00
Erica Portnoy
1b595f26d8 Requests no longer vendorizes urllib3 2018-11-02 13:31:48 -07:00
Erica Portnoy
a83afb6135 properly disable no-member 2018-11-02 13:31:04 -07:00
Erica Portnoy
e37a8fbded Use a newer version of requests because of the upcoming Callable import Deprecation in Python 3.8 that warns in Python 3.7 2018-11-02 13:01:14 -07:00
Erica Portnoy
d02ea812a5 Cover is run on 2.7, so mark 3-only lines as no cover 2018-11-02 12:36:42 -07:00
Erica Portnoy
28c117abe0 If ResourceWarning is specified in pytest.ini, tests fail on Python3. We should be catching all of them, and they usually fail to successfully error anyway, so this just means we get sligthly worse error messages when they do occur. 2018-11-02 12:31:55 -07:00
Joona Hoikkala
a1af42bc5f Dummy AWS credentials for Route53 tests to prevent outbound connections (#6456)
Boto3 / botocore library has a feature that tries to fetch AWS credentials from IAM if a set of credentials isn't available otherwise. This happens when boto loops through different credential providers in order to find the keys. See https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=912103

This PR simply adds dummy environmental variables for the tests that will be picked up by the credential provider iterator in order to prevent making outbound connections.

* Hardcode dummy AWS credentials to prevent boto3 making outgoing connections

* Remove the dummy credentials when tearing down test case
2018-11-02 09:59:27 -07:00
Erica Portnoy
36b6328acd Ignore ResourceWarnings in various modules in a 2-compatible way.
Sometimes tests are flaky about giving this warning, particularly in ACME. I recommend just ignoring them.
2018-11-01 17:56:01 -07:00
Erica Portnoy
c699a50983 Ignore all ResourceWarnings in log_test.py 2018-11-01 17:35:12 -07:00
Erica Portnoy
a5e26ca890 Ignore all ResourceWarnings in main_test.py, since they're flaky 2018-11-01 17:32:59 -07:00
Erica Portnoy
de1554f785 Ignore more warnings. These are the ones I'm less sure about 2018-11-01 17:28:40 -07:00
Erica Portnoy
6e7d42f298 ignore ResourceWarnings in acme tests 2018-11-01 17:17:19 -07:00
Erica Portnoy
36e8d748a1 Clean up many warnings 2018-11-01 16:39:54 -07:00
Erica Portnoy
49619fc0ab Error on warning, unless it's a ResourceWarning 2018-11-01 16:39:29 -07:00
Erica Portnoy
6f662fa489 ignore deprecation and resource warnings in certbot-dns-rfc2136, which are inherited from dnspython 2018-11-01 16:39:01 -07:00
Erica Portnoy
1eabb4bae3 warn-->warning 2018-10-31 18:11:43 -07:00
Erica Portnoy
35510bfc6c assertNotEquals --> assertNotEqual 2018-10-31 17:59:44 -07:00
Erica Portnoy
b837f218c9 U mode on open --> newline=None 2018-10-31 17:59:24 -07:00
Erica Portnoy
a5e0e801be correctly use getfullargspec 2018-10-31 17:49:50 -07:00
Erica Portnoy
594fbe3ae8 disable pylint no-member for x-version 2018-10-31 17:33:39 -07:00
Erica Portnoy
2ddf47e3cc assertEquals --> assertEqual 2018-10-31 17:28:47 -07:00
Erica Portnoy
090a1bd44d getargspec is deprecated in python 3 2018-10-31 17:20:31 -07:00
Joona Hoikkala
1d783fd4b9 Update Augeas lens to fix some Apache configuration parsing issues (#6438)
* Update Augeas lens to fix some Apache configuration parsing issues

* Added CHANGELOG entry
2018-10-31 09:34:14 -07:00
Erica Portnoy
b0b8710835 Move back to using a warning whitelist 2018-10-30 17:24:29 -07:00
Erica Portnoy
54b4758c27 Merge branch 'master' into warnings-are-errors 2018-10-30 17:18:00 -07:00
Erica Portnoy
aad2663695 Only error on DeprecationWarnings 2018-10-30 17:13:40 -07:00
Adrien Ferrand
9264561944 Check pattern for both old and new openssl (#6450) 2018-10-29 15:56:30 -07:00
ohemorange
36ebce4a5f Fix ranking of vhosts in Nginx so that all port-matching vhosts come first (#6412)
To more closely match how Nginx ranks things.
2018-10-19 19:16:54 -07:00
Adrien Ferrand
7b17c84dd9 Remove custom code for fail fast because rolling builds in AppVeyor are enabled. (#6431) 2018-10-19 17:37:22 -07:00
Adrien Ferrand
1e8c13ebf9 [Windows] Create the CI logic (#6374)
So here we are: after #6361 has been merged, time is to provide an environment to execute the automated testing on Windows.

Here are the assertions used to build the CI on Windows:

every test running on Linux should ultimately be runnable on Windows, in a cross-platform compatible manner (there is one or two exception, when a test does not have any meaning for Windows),
currently some tests are not runnable on Windows: theses tests are ignored by default when the environment is Windows using a custom decorator: @broken_on_windows,
test environment should have functionalities similar to Travis, in particular an execution test matrix against various versions of Python and Windows,
so test execution is done through AppVeyor, as it supports the requirements: it add a CI step along Travis and Codecov for each PR, all of this ensuring that Certbot is entirely functional on both Linux and Windows,
code in tests can be changed, but code in Certbot should be changed as little as possible, to avoid regression risks.
So far in this PR, I focused on the tests on Certbot core and ACME library. Concerning the plugins, it will be done later, for plugins which have an interest on Windows. Test are executed against Python 3.4, 3.5, 3.6 and 3.7, for Windows Server 2012 R2 and Windows Server 2016.

I succeeded at making 258/259 of acme tests to work, and 828/868 of certbot core tests to work. Most of the errors where not because of Certbot itself, but because of how the tests are written. After redesigning some test utilitaries, and things like file path handling, or CRLF/LF, a lot of the errors vanished.

I needed also to ignore a lot of IO errors typically occurring when a tearDown test process tries to delete a file before it has been closed: this kind of behavior is acceptable for Linux, but not for Windows. As a consequence, and until the tearDown process is improved, a lot of temporary files are not cleared on Windows after a test campaign.

Remaining broken tests requires a more subtile approach to solve the errors, I will correct them progressively in future PR.

Last words about tox. I did not used the existing tox.ini for now. It is just to far from what is supported on Windows: lot of bash scripts that should be rewritten completely, and that contain test logic not ready/relevant for Windows (plugin tests, Docker compilation/test, GNU distribution versatility handling and so on). So I use an independent file tox-win.ini for now, with the goal to merge it ultimately with the existing logic.

* Define a tox configuration for windows, to execute tests against Python 3.4, 3.5, 3.6 and 3.7 + code coverage on Codecov.io

* Correct windows compatibility on certbot codebase

* Correct windows compatibility on certbot display functionalities

* Correct windows compatibility on certbot plugins

* Correct test utils to run tests on windows. Add decorator to skip (permanently) or mark broken (temporarily) tests on windows

* Correct tests on certbot core to run them both on windows and linux. Mark some of them as broken on windows for now.

* Lock tests are completely skipped on windows. Planned to be replace in next PR.

* Correct tests on certbot display to run them both on windows and linux. Mark some of them as broken on windows for now.

* Correct test utils for acme on windows. Add decorator to skip (permanently) or mark broken (temporarily) tests on windows.

* Correct acme tests to run them both on windows and linux. Allow a reduction of code coverage of 1% on acme code base.

* Create AppVeyor CI for Certbot on Windows, to run the test matrix (py34,35,36,37+coverage) on Windows Server 2012 R2 and Windows Server 2016.

* Update changelog with Windows compatibility of Certbot.

* Corrections about tox, pyreadline and CI logic

* Correct english

* Some corrections for acme

* Newlines corrections

* Remove changelog

* Use os.devnull instead of /dev/null to be used on Windows

* Uid is a always a number now.

* Correct linting

* PR https://github.com/python/typeshed/pull/2136 has been merge to third-party upstream 6 months ago, so code patch can be removed.

* And so acme coverage should be 100% again.

* More compatible tests Windows+Linux

* Use stable line separator

* Remove unused import

* Do not rely on pytest in certbot tests

* Use json.dumps to another json embedding weird characters

* Change comment

* Add import

* Test rolling builds #1

* Test rolling builds #2

* Correction on json serialization

* It seems that rolling builds are not canceling jobs on PR. Revert back to fail fast code in the pipeline.
2018-10-19 14:53:15 -07:00
sydneyli
8dd68a6551 Add and test new nginx parsing abstractions (#6383)
* feat(nginx): add and test new parsing abstractions

* chore(nginx parser): fix mypy and address small comments

* chore(nginx parser): clean up by removing context object

* fix integration test and lint
2018-10-19 12:30:32 -07:00
Daniel McCarney
0dab41ee13 docs: remove mentions of #letsencrypt on Freenode. (#6419)
* docs: remove mentions of #letsencrypt on Freenode.

* docs: remove unused Freenode link
2018-10-18 13:12:47 -07:00
sydneyli
bfaf0296de Also write README file to /etc/letsencrypt/live (#6377)
We want to discourage people from moving things around in `/etc/letsencrypt/live`! So we dropped an extra README in the `/etc/` directory when it's first created.
2018-10-18 11:39:21 -07:00
Brad Warren
b9dd40b350 Merge pull request #6271 from certbot/prune_neworder
Do not send status or resource fields in newOrder payloads for ACMEv2
2018-10-18 11:17:59 -07:00
Erica Portnoy
6500b9095e Add test to confirm that status isn't set on neworder object 2018-10-18 10:37:56 -07:00
Erica Portnoy
ee02ed65af remove default status from Order so that the status field isn't filled in upon boulder deserialization 2018-10-18 10:26:37 -07:00
Erica Portnoy
a3a3840e91 replace status field 2018-10-18 10:19:57 -07:00
Erica Portnoy
ca155b48ae Merge branch 'master' into prune_neworder 2018-10-18 10:16:59 -07:00
schoen
3de3188dd6 Warn manual authenticator users not to remove/undo previous challenges (#6370)
* Warn users not to remove/undo previous challenges

* Even more specific DNS challenge message

* Fix spacing and variable names

* Create a second test DNS challenge for UI testing

* Changelog for subsequent manual challenge behavior
2018-10-18 14:44:45 +03:00
schoen
92501eaf8f Note about running on web server, not PC (#6422) 2018-10-17 14:08:59 -07:00
Ștefan Talpalaru
819f95c37d certbot_dns_linode: increase the default propagation interval (#6320)
Using the default value of 16 minutes (960 seconds) for
--dns-linode-propagation-seconds leads to DNS failures when the randomly
selected Linode DNS is not the first one out of six, due to an additional
delay before the other five are updated.

The problem can be easily solved by increasing the wait interval, so
this commit increases the default value to 20 minutes.

More details: https://community.letsencrypt.org/t/dns-servers-used-by-letsencrypt-for-challenges/32127/16
2018-10-17 13:48:49 -07:00
ohemorange
22da2447d5 Stop caching the results of ipv6_info in http01.py (#6411)
Stop caching the results of ipv6_info in http01.py. A call to choose_vhosts might change the ipv6 results of later calls. Add tests for this and default_listen_addresses more broadly.
2018-10-17 10:54:43 -07:00
ohemorange
139ef20650 Add debugging info for Nginx tls-sni and http integration tests purposes (#6414) 2018-10-15 10:41:04 -07:00
ohemorange
e0f760099c Merge pull request #6407 from fghzxm/patch-1
Fix typo in using.rst
2018-10-09 13:50:07 -04:00
fghzxm
19f74c3dc7 Fix typo in using.rst 2018-10-07 11:14:09 +08:00
ohemorange
06174bc113 Merge pull request #6388 from certbot/local-revoke-certname
revoke accepts --cert-name
2018-09-24 18:44:44 -07:00
schoen
15932dcafc Merge pull request #6389 from DigitalBrains1/quiet-sudo
Respect --quiet when reporting sudo invocation
2018-09-24 16:34:18 -07:00
Peter Lebbing
e8eeab3eab Respect --quiet when reporting sudo invocation 2018-09-23 14:22:20 +02:00
Erica Portnoy
eb7a10b5c0 use safe args 2018-09-20 20:12:51 -07:00
Erica Portnoy
faa44070f5 remove inappropriate logic and tests 2018-09-20 20:09:47 -07:00
Erica Portnoy
7ccd6ec98e update changelog 2018-09-20 20:00:13 -07:00
Erica Portnoy
b42283f0b3 update boulder integration test to check for new behavior 2018-09-20 19:57:48 -07:00
Erica Portnoy
f4d615371e Don't let users select both --cert-name and --cert-path when revoking 2018-09-20 19:52:52 -07:00
Erica Portnoy
cb4b1897c9 Make it clear that we don't need both --cert-path and --cert-name 2018-09-20 19:51:27 -07:00
Erica Portnoy
e6a9fa8695 Merge branch 'master' into local-revoke-certname 2018-09-20 19:48:20 -07:00
Adrien Ferrand
efd2ed1bdb Correct OVH integration tests on machines without internet access (#6380)
* Correct OVH integration tests on machines without internet.

* Update changelog
2018-09-18 17:35:28 -07:00
ohemorange
3ef43e4d88 Update parser to match new Nginx functionality (#6381)
Previously, Nginx did not allow `${` to start a variable name. Now it's allowed to. This means we'll be more permissible than Nginx when people are on older versions of Nginx, but it's unlikely anyone was relying on this to fail in the first place, so that's probably ok.
2018-09-18 12:52:11 -07:00
Adrien Ferrand
e501322ff1 Connect AppVeyor to the certbot git repository (#6361) 2018-09-13 15:20:22 -07:00
David Beitey
38b1d9d6ba More detailed error logging for nginx plugin (#6175)
This makes errors more useful when Nginx can't be found or when Nginx's
configuration can't be found.  Previously, a generic
`NoInstallationError` isn't descriptive enough to explain _what_ wasn't
installed or what failed without going digging into the source code.
2018-09-12 16:48:50 -07:00
Eli Young
b32ec6ed30 Remove CHANGES.rst (#6162)
The change log is now being tracked in CHANGELOG.md, so CHANGES.rst is
no longer necessary.
2018-09-12 16:40:10 -07:00
Sam Bull
a3b858db34 Exclude one-time use parameters. Fixes #6118 (#6223)
* Exclude one-time use parameters. Fixes #6118

* Fix error.

* Delete items inplace, rather than creating new list.

* Fix stupid mistake.

* Use .index() for stability.

* Try previous idea while resetting the index.

* Shorter comment for pylint.

* More readable approach

* Fix whitespace
2018-09-12 16:38:37 -07:00
Jacob Hoffman-Andrews
8f7209de14 Silence spammy integration test cases. (#5934) 2018-09-12 16:35:43 -07:00
Brad Warren
251355cade Add better error handling around release signatures (#6353)
* Better error handling around sig after offline-sig

* Add error handling around first sig with git.

* Don't fail if offline-sig fails.
2018-09-11 15:44:26 -07:00
Adrien Ferrand
85a859d63f Make Certbot runnable on Windows (#6296)
* Add and use a compatibility layer to allow certbot to be run on windows.

* Fix path comparison

* Corrections on compat and util for tests

* Less intrusive way to parse prefix in webroot plugin working for both linux and windows.

* Disable pylint import-error for some optional imports in compat.py

* Ensure path is normalized before prefixes are generated in webroot plugin

* Same prefixes in linux and windows, in fact root path is not needed in webroot plugin

* Check that user has administrative rights before continuing on windows (necessary for symlink creation)

* More straightforward way to test administrative rights on windows

* Try to resolve import error in travis ci

* OK. We go for full introspection to trick the ci.

* Move the administrative rights control to the certbot entrypoint

* Add comment for a really non trivial code.

* Allow some commands to be run on a shell without admin rights

* Avoid races conditions on windows for lock files

* Add sphinx doc to the compat functions.

* Remove irrelevant Windows error in the lock mechanism.

* Some corrections on compat
2018-09-08 07:34:27 -07:00
Brad Warren
5d1c6d28d5 Update DNS plugin docs. (#6358) 2018-09-07 12:18:59 -07:00
ohemorange
b50abddb5f Candidate 0.27.1 (#6351)
* fix(apache): s/handle_mods/handle_modules (#6347) (#6349)

fixes #6344

* fix(apache): s/handle_mods/handle_modules

* test(apache): ensure all keys defined in OS_DEFAULTS overrides

* changelog udpate

(cherry picked from commit 4e2faffe89)

* Release 0.27.1

* Bump version to 0.28.0
2018-09-06 17:49:24 -07:00
ohemorange
101eae4e05 Update CHANGELOG.md for 0.27.1 release (#6350) 2018-09-06 17:21:31 -07:00
sydneyli
4e2faffe89 fix(apache): s/handle_mods/handle_modules (#6347)
fixes #6344

* fix(apache): s/handle_mods/handle_modules

* test(apache): ensure all keys defined in OS_DEFAULTS overrides

* changelog udpate
2018-09-06 15:00:20 -07:00
ohemorange
d39a354a65 Create master section for incremental changes (#6342) 2018-09-06 10:17:51 -07:00
ohemorange
05ad539255 git ignore pytest cache (#6340) 2018-09-05 18:05:48 -07:00
ohemorange
0c66de47cf Remind people to modify changelog when submitting PRs (#6341)
* Remind people to modify changelog when submitting PRs

* Update pull_request_template.md
2018-09-05 18:05:42 -07:00
Brad Warren
a6f5189593 Merge pull request #6339 from certbot/candidate-0.27.0
Candidate 0.27.0
2018-09-05 17:31:58 -07:00
Brad Warren
2708d28157 Update changelog for 0.27.0 (#6338) 2018-09-05 17:13:30 -07:00
Erica Portnoy
e28f3da974 Bump version to 0.28.0 2018-09-05 15:42:01 -07:00
Erica Portnoy
19149a0d57 Release 0.27.0 2018-09-05 15:41:59 -07:00
Brad Warren
e178bbfdf5 Release script improvements (#6337)
* Add error checking and automatic logging.

* Ignore release dir and logs

* Don't always require PGP card and fix script cmd.

* keep track of default GPG key

* Add PGP card sanity check after offline signature

* fix typo

* I'm tired of pressing y.

* Automate running tools/offline-sigrequest.sh.

* Update comment and make output more readable.
2018-09-05 14:10:05 -07:00
Brad Warren
cd2edeff1b Fix test farm tests (#6335)
* update CentOS AMI ids

* Remove assumption of usable default subnet
2018-09-05 13:12:05 -07:00
Brad Warren
405a8b4264 Pin the real oldest requirement for nginx tests. (#6327) 2018-08-29 15:15:57 -07:00
Brad Warren
6e23b81dba Separate integration (#5814)
Main piece of #5810.

* Rename Certbot integration tests

* Remove nginx from certbot tests

* allow for running individual integration tests

* fail under 65

* Add set -e

* Track Nginx coverage and omit it from report later.

* Use INTEGRATION_TEST in script

* add INTEGRATION_TEST=all

* update min certbot percentage
2018-08-29 14:11:13 -07:00
Brad Warren
0e9dd5e3d2 Remove visible warnings about missing apachectl. (#6307) 2018-08-21 08:59:56 -07:00
Adrien Ferrand
b1003b7250 Fail fast during tests if python executable is not in the PATH (#6306) 2018-08-16 12:28:25 -07:00
Brad Warren
d8057f0e17 Fix Sphinx (#6070)
Fixes #4686.

In Sphinx 1.6, they changed how they handle images in latex and PDF files. You can learn more about this by reading the linked issue (or I can answer any questions), but the shortish version is we now need to use the extension sphinx.ext.imgconverter. This is only available in Sphinx 1.6+.

I also updated our pinned versions to use the latest Sphinx and a new dependency it pulled in called sphinxcontrib-websupport. To build the latex and PDF docs, you must first run:

apt-get install imagemagick latexmk texlive texlive-latex-extra

Afterwards, if you create the normal Certbot dev environment using this branch, activate the virtual environment, and from the root of the repo run make -C docs clean latex latexpdf, you'll successfully build the PDF docs.

* fix #4686

* bump minimum Sphinx req
2018-08-06 09:45:56 -07:00
Josh Soref
4989668e0a Clarify that configurations can be invalid (#6277) 2018-08-03 14:55:12 -07:00
Joona Hoikkala
7bff0a02e4 Make Apache control script and binary paths configurable on command line (#6238)
This PR adds two new command line parameters, --apache-ctlpath and --apache-binpath both of which are used to construct commands that we shell out for.

The way that we previously fetched values either from Certbot configuration object or the dictionary of distribution based constants is now also unified, and the active options are parsed in prepare() to make it easier to override needed values for the distributions needing this behavior.

Fixes: #5338

* Added the command line options and parsing

* Refactor existing code

* Distro override updates

* Handle vhost_root from cli

* Fix compatibility tests

* Add comment about changes to command line arguments

* Check None properly

* Made help texts consistent

* Keep the old defaults

* Move to shorter CLI parameter names

* No need for specific bin path, nor apache_cmd anymore

* Make sure that we use user provided vhost-root value

* Fix alt restart commands in overrides

* Fix version_cmd defaults in overrides

* Fix comparison

* Remove cruft, and use configuration object for parser parameter
2018-08-02 08:17:38 -07:00
Joona Hoikkala
f6219ddf19 Enable Apache VirtualHost for HTTP challenge validation if not enabled already (#6268)
If user provides a custom --apache-vhost-root path that's not parsed by Apache per default, Certbot fails the challenge validation. While the VirtualHost on custom path is correctly found, and edited, it's still not seen by Apache. This PR adds a temporary Include directive to the root Apache configuration when writing the challenge tokens to the VirtualHost.
2018-08-01 12:00:47 -07:00
Joona Hoikkala
8943dffe0d Removed status and resource fields from NewOrder object 2018-08-01 12:17:23 +03:00
Joona Hoikkala
b1b4650804 Revert "Do not send status or resource fields in newOrder payloads for ACMEv2"
This reverts commit d19698251d.
2018-08-01 12:10:55 +03:00
Joona Hoikkala
c131f4211d Revert "Fix tests"
This reverts commit 8b3629ebd4.
2018-08-01 12:10:01 +03:00
Brad Warren
f2bc876b6e switch to codecov (#6220) 2018-07-31 15:56:21 -07:00
Yoan Blanc
6262921315 bump pytest-xdist to 1.22.5 (#6253)
Signed-off-by: Yoan Blanc <yoan.blanc@exoscale.ch>
2018-07-31 10:31:36 -07:00
Brad Warren
68f9a1b300 Files with multiple vhosts are fine. (#6273) 2018-07-31 09:56:03 -07:00
Joona Hoikkala
8b3629ebd4 Fix tests 2018-07-31 19:55:19 +03:00
Joona Hoikkala
d19698251d Do not send status or resource fields in newOrder payloads for ACMEv2 2018-07-31 17:08:39 +03:00
ohemorange
ee7d5052fd Update changelog for 0.26.1 release (#6237)
* Update changelog for 0.26.1 release
2018-07-30 16:32:31 -07:00
dovreshef
dc91357418 Raise ConflictError on attempts to create an existing account (#6251)
* Raise ConflictError on attempts to create an existing account in ACME V2.

Fixes issue #6246

* Allow querying an account without calling new_account in ACMEv2

Fixed issue #6258
2018-07-27 08:50:53 -07:00
Brad Warren
c129ab2965 Bump the acme version needed for account reuse (#6250)
* Bump the acme version needed for account reuse.

Fixes https://github.com/certbot/certbot/issues/6155#issuecomment-407122742.

* Update nginx oldest requirements.

* bump min acme version

* update min acme version
2018-07-23 11:46:22 -07:00
Eli Young
daee6e8eb3 Fix test naming for certbot-dns-sakuracloud (#6231) 2018-07-19 10:29:34 -07:00
R3DDY97
cdc333491b Gpg2 doc (#5981) 2018-07-18 08:34:09 -07:00
sydneyli
94cadd33eb test(postfix): env for testing on oldest deps (#6230)
Fixes #6124.
2018-07-17 20:00:12 -07:00
Brad Warren
783b6e4746 Automate EBS cleanup (#6160)
* ensure volume cleanup

* remove volume cleanup

* cleanup function and output
2018-07-17 17:19:04 -07:00
Brad Warren
20f8e69593 Merge pull request #6232 from certbot/candidate-0.26.1
Candidate 0.26.1
2018-07-17 15:34:14 -07:00
Eli Young
6fd312833f Remove setuptools min version for OVH DNS plugin (#6229)
This simplifies packaging for EPEL7. See also #5617.
2018-07-17 15:29:08 -07:00
ohemorange
b3b2a65569 Merge branch 'master' into candidate-0.26.1 2018-07-17 15:08:40 -07:00
Erica Portnoy
e9af000b5b Bump version to 0.27.0 2018-07-16 16:37:25 -07:00
Erica Portnoy
a0d68338a2 Release 0.26.1 2018-07-16 16:36:59 -07:00
schoen
0f833c6c40 Merge pull request #6211 from greut/num-processes
travis: container env are bad at reading cpu count
2018-07-16 16:01:11 -07:00
hal869
704101c75b update venv3.sh to include dns-rfc2136 plugin (#6226) 2018-07-16 10:20:53 -07:00
Trinopoty Biswas
595e77eccb Fixed linode API settings page URL (#6208) 2018-07-16 08:40:16 -07:00
Brad Warren
72daec4346 fix account tests (#6216) 2018-07-16 08:38:50 -07:00
Brad Warren
043db62a29 Merge pull request #6206 from certbot/candidate-0.26.0
Update autos, version numbers, and docs
2018-07-16 08:05:56 -07:00
Brad Warren
9974963887 Handle existing ACMEv1 and ACMEv2 accounts (#6214) (#6215)
Fixes #6207.

As noted by Erica:

- we no longer need to check if it exists before linking to it, because we delete properly.
- the previously excisting check on if server is in `LE_REUSE_SERVERS` before unlinking is nice, but probably not necessary, especially since we don't officially support people doing weird things with symlinks in our directories, and because we rmdir which will fail if it's not empty anyway.

* Create single account symlink.

* refactor _delete_accounts_dir_for_server_path

* add symlinked account dir deletion

* add tests

(cherry picked from commit 9b0d2714c1)
2018-07-16 07:54:36 -07:00
Brad Warren
fa7cb38e97 Add 0.26.0 changelog (#6205) 2018-07-16 07:33:38 -07:00
Brad Warren
8428713032 Remove linode and ovh links which aren't valid yet. (#6198)
* Remove linode links which aren't valid yet.

* remove ovh references

* keep links which are now valid

* keep pypi links
2018-07-13 14:21:23 -07:00
Brad Warren
9b0d2714c1 Handle existing ACMEv1 and ACMEv2 accounts (#6214)
Fixes #6207.

As noted by Erica:

- we no longer need to check if it exists before linking to it, because we delete properly.
- the previously excisting check on if server is in `LE_REUSE_SERVERS` before unlinking is nice, but probably not necessary, especially since we don't officially support people doing weird things with symlinks in our directories, and because we rmdir which will fail if it's not empty anyway.

* Create single account symlink.

* refactor _delete_accounts_dir_for_server_path

* add symlinked account dir deletion

* add tests
2018-07-12 16:21:24 -07:00
Yoan Blanc
72cf844ecf travis: container env are bad at reading cpu count
Signed-off-by: Yoan Blanc <yoan.blanc@exoscale.ch>
2018-07-12 14:30:53 +02:00
Brad Warren
fdb3c8df4b s/assertEquals/assertEqual 2018-07-11 17:33:04 -07:00
Brad Warren
32676f02c3 warnings are errors 2018-07-11 17:32:48 -07:00
Brad Warren
7383fc6bf0 move values to pytest.ini 2018-07-11 17:25:48 -07:00
Brad Warren
0a6d520d26 Bump version to 0.27.0 2018-07-11 14:18:44 -07:00
Brad Warren
95e271bfcd Release 0.26.0 2018-07-11 14:18:26 -07:00
ohemorange
c326c02108 Update default to ACMEv2 server (#6152) 2018-07-11 11:20:36 -07:00
Nicolas Bachschmidt
a2222d5bdf OVH DNS Authenticator (#5423)
Implement an Authenticator which can fulfill a dns-01 challenge using the OVH DNS API. Applicable only for domains using OVH DNS.

Testing Done:
 * `tox -e py27`
 * `tox -e lint`
 * Manual testing:
    * Used `certbot certonly --dns-ovh -d`, specifying a credentials file as a command line argument. Verified that a certificate was successfully obtained without user interaction.
    * Used `certbot certonly --dns-ovh -d`, without specifying a credentials file as a command line argument. Verified that the user was prompted and that a certificate was successfully obtained.
    * Used `certbot certonly -d`. Verified that the user was prompted for a credentials file after selecting dnsimple interactively and that a certificate was successfully obtained.
    * Used `certbot renew --force-renewal`. Verified that certificates
      were renewed without user interaction.
 * Negative testing:
    * Path to non-existent credentials file.
    * Credentials file with unsafe permissions (644).
    * Path to credentials file with an invalid application key.
    * Path to credentials file with an invalid application secret.
    * Path to credentials file with an invalid consumer key.
    * Path to credentials file with missing properties.
    * Domain name not registered to OVH account.
2018-07-10 20:52:32 -07:00
Brad Warren
148d68b99a Fix broken fedora links (#6196)
* fix broken fedora links

* Add packaged plugin links
2018-07-10 18:48:49 -07:00
ohemorange
b7113a35eb Delete empty directories after deleting an account, including symlinks up and down the chain, as appropriate (#6176) 2018-07-10 18:48:09 -07:00
chibiegg
3f6a908821 Gehirn Infrastracture Service DNS Authenticator (#5702)
Implement an Authenticator which can fulfill a dns-01 challenge using
the Gehirn DNS (Gehirn Infrastructure Service) API.
Applicable only for domains using Gehirn DNS for DNS.

Testing Done:
 * `tox -e py27`
 * `tox -e lint`
 * Manual testing:
    * Used `certbot certonly --dns-gehirn -d`, specifying a
      credentials file as a command line argument. Verified that a
      certificate was successfully obtained without user interaction.
 * Negative testing:
    * Path to non-existent credentials file.
    * Credentials file with unsafe permissions (644).
    * Domain name not registered to Gehirn DNS account.
2018-07-10 17:36:20 -07:00
Brad Warren
3b39266813 Don't use quoted None for plugins in the config (#6195)
This stops us from printing messages like:

"Could not choose appropriate plugin for updaters: Could not select or initialize the requested installer None."

when certbot renew --force-renewal is run with a lineage that doesn't have an installer.

* unquote None

* Test None values aren't saved in config file.
2018-07-10 14:42:27 -07:00
chibiegg
9314911135 Sakura Cloud DNS Authenticator (#5701)
Implement an Authenticator which can fulfill a dns-01 challenge using
the Sakura Cloud DNS API.
Applicable only for domains using Sakura Cloud for DNS.

Testing Done:
 * `tox -e py27`
 * `tox -e lint`
 * Manual testing:
    * Used `certbot certonly --dns-sakuracloud -d`, specifying a
      credentials file as a command line argument. Verified that a
      certificate was successfully obtained without user interaction.
 * Negative testing:
    * Path to non-existent credentials file.
    * Credentials file with unsafe permissions (644).
    * Domain name not registered to Sakura Cloud account.
2018-07-10 14:30:37 -07:00
Brad Warren
8a5abb6203 Always save server value in renewal conf file (#6189) 2018-07-10 14:06:45 -07:00
Jacob Hoffman-Andrews
0672e63176 Remove main components from Alpha. (#6187)
acme, certbot, and the Nginx and Apache plugins should no longer be considered alpha-quality.
2018-07-10 13:52:58 -07:00
Trinopoty Biswas
3855cfc08d Linode DNS Authenticator (#5302)
* Added DNS based authenticator plugin for Linode

* Added linode plugin to docs

* Added Dockerfile

* Added .gitignore and readthedocs.org.requirements.txt

* Updated default_propagation_seconds

* Updated according to changes requested

* Bump version to 0.26.0

* Advertise our packages work on Python 3.7.
2018-07-10 13:51:03 -07:00
ohemorange
83f7e72fef Update and delete registration with account reuse (#6174)
* find the correct url when deactivating an acmev1 account on the acmev2 endpoint

* set regr in ClientNetwork.account after deactivating on the server

* update self.net.account

* move logic into update_registration

* return methods to their original order to please git

* factor out common code

* update test_fowarding to use a method that still gets forwarded

* add acme module test coverage

* pragma no cover on correct line

* use previous regr uri

* strip unnecessary items from regr before saving

* add explanation to main.py

* add extra check to client_test.py

* use empty dict instead of empty string to indicate lack of body that we save to disk
2018-07-10 13:03:25 -07:00
Brad Warren
43f2bfd6f1 Advertise our packages work on Python 3.7. (#6183) 2018-07-09 09:17:03 -07:00
Brad Warren
cdf93de338 Full Python 3.7 support (#6182)
Now that yaml/pyyaml#126 is resolved, #6170 can be reverted by bumping the pinned version of PyYAML.

You can see this code passing with full macOS and integration tests at https://travis-ci.org/certbot/certbot/builds/400957729.

* Revert "Allow py37 testing (#6170)"

This reverts commit cad95466b0.

* Bump pyyaml pinning to work on Python 3.7.
2018-07-09 09:16:44 -07:00
Brad Warren
dd600db436 Upgrade pinned josepy version (#6184)
We released josepy 1.1.0 a while ago to work around newer versions of cryptography deprecating some of the functionality we were using. We haven't yet upgraded our pinned josepy version though and since #6169 has landed, we're now seeing these deprecation warnings in our tests. This would be shown to certbot-auto users as well.

This PR removes these warnings by upgrading our pinned version of josepy.

* update pinned josepy version

* build leauto

* update pinned dev version of josepy
2018-07-09 09:16:08 -07:00
Joona Hoikkala
2564566e1c Do not call IPlugin.prepare() for updaters when running renew (#6167)
interfaces.GenericUpdater and new enhancement interface updater functions get run on every invocation of Certbot with "renew" verb for every lineage. This causes performance problems for users with large configurations, because of plugin plumbing and preparsing happening in prepare() method of installer plugins. This PR moves the responsibility to call prepare() to the plugin (possibly) implementing a new style enhancement interface.

Fixes: #6153

* Do not call IPlugin.prepare() for updaters when running renew

* Check prepare called in tests

* Refine pydoc and make the function name more informative

* Verify the plugin type
2018-07-06 13:19:29 -07:00
Brad Warren
08378203df Add Python 3.7 tests (#6179)
* Remove apacheconftest packages.

The apacheconftests handle installing Apache dependencies, so let's remove it from the general case.

* We don't need to run dpkg -s in before_install.

* Remove augeas sources.

We only needed it for Ubuntu Precise which is dead and it doesn't work in Ubuntu Xenial.

* Upgrade Python 3.6 tests to 3.7.

Let's continue the approach of testing on the oldest and newest versions of Python 3. We will continue testing on Python 3.6 in the nightly tests.

* Revert "We don't need to run dpkg -s in before_install."

This reverts commit e5d35099a7.

* let apacheconftest handle deps
2018-07-06 19:08:40 +03:00
Brad Warren
cb076539ec Remove .dev0 from version numbers during releases. (#6116)
This allows us to depend on packages like acme>=0.26.0.dev0 during development
and automatically change it to acme>=0.26.0 during the release. We use `git add
-p` to be safe, but if .dev0 is used at all in our released setup.py files,
we're probably doing something wrong.
2018-07-05 08:26:42 -07:00
Brad Warren
cad95466b0 Allow py37 testing (#6170)
* Reorganize packages in tox to allow for py37 tests

certbot-dns-cloudflare doesn't currently work in Python 3.7 because it transitively depends on pyYAML which doesn't yet support Python 3.7. See https://github.com/yaml/pyyaml/issues/126 for more info.

* add py37 tox environment
2018-07-05 12:11:04 +03:00
Brad Warren
ab9851d97e Upgrade to the latest cryptography version (#6169)
This allows certbot-auto and our development setup to work with Python 3.7.
2018-07-03 06:57:58 -07:00
Brad Warren
552e60a126 Don't use hardcoded port in tests (#6145)
* Don't use port 1234 in standalone tests.

* rename unused variable

* add back failure case

* Add back probe connection error test.

* fix lint

* remove unused import

* fix test file coverage

* prevent future heisenbug
2018-06-29 15:27:58 +03:00
Brad Warren
6e13c2ccc7 Add --disable=locally-enabled to .pylintrc. (#6159) 2018-06-28 15:06:52 -07:00
ohemorange
a816cc8979 Use account reuse symlink logic when loading an account (#6156)
Fixes #6154.

* add symlinking to load flow

* test account reuse on load
2018-06-28 13:34:16 -07:00
Brad Warren
64e06d4201 Use greater than or equal to in requirements. (#6117)
* Use greater than or equal to in requirements.

This changes the existing requirements using strictly greater than to greater
than or equal to so that they're more conventional.

* Use >= for certbot-postfix.

Despite it previously saying 'certbot>0.23.0', certbot-postfix/local-oldest-requirements.txt was pinned to 0.23.0 so let's just use certbot>=0.23.0.
2018-06-28 10:55:21 -07:00
Bahram Aghaei
d00a31622d run Isort on imported packages (#6138) 2018-06-28 10:43:52 -07:00
Brad Warren
742a57722b fix server_root default tests on macOS (#6149) 2018-06-27 17:35:43 -07:00
Brad Warren
ad3c547e1f Update cli-help.txt to use generic values (#6143) 2018-06-27 14:29:21 -07:00
Joona Hoikkala
f169e7374b Interactive certificate selection with install verb (#6097)
If either --cert-name or both --key-path and --cert-path (in which case the user requests installation for a certificate not managed by Certbot) are not provided, prompt the user with managed certificates and let them choose.

Fixes: #5824
2018-06-27 11:35:01 -07:00
Joona Hoikkala
2304f7fcda Remove unnecessary dotfiles (#6151) 2018-06-27 09:32:53 -07:00
Brad Warren
a4760cfe56 Partially revert "Implement TLS-ALPN-01 challenge and standalone TLS-ALPN server (#5894)" (#6144)
This partially reverts commit 15f1405fff.

A basic tls-alpn-01 implementation is left so we can successfully parse the
challenge so it can be used in boulder's tests.
2018-06-26 15:33:41 -07:00
ohemorange
87e1912bf9 Show both possible Nginx default server root values in docs (#6137)
See https://github.com/certbot/website/pull/348#issuecomment-399257703.

```
$ certbot --help all | grep -C 3  nginx-server-root
nginx:
  Nginx Web Server plugin - Alpha

  --nginx-server-root NGINX_SERVER_ROOT
                        Nginx server root directory. (default: /etc/nginx)
  --nginx-ctl NGINX_CTL
                        Path to the 'nginx' binary, used for 'configtest' and
 ```

```
$ CERTBOT_DOCS=1 certbot --help all | grep -C 3  nginx-server-root
nginx:
  Nginx Web Server plugin - Alpha

  --nginx-server-root NGINX_SERVER_ROOT
                        Nginx server root directory. (default: /etc/nginx or
                        /usr/local/etc/nginx)
  --nginx-ctl NGINX_CTL
```

* Show both possible Nginx default server root values in docs

* add test

* check that exactly one server root is in the default

* use default magic
2018-06-25 18:09:30 -07:00
r5d
80cd134847 certbot.cli: Remove debug-challenges option for renew subcommand. (#6141)
Addresses issue #5005.
2018-06-25 18:02:07 -07:00
sydneyli
7890de62ec doc(postfix): install instructions (#6136)
fixes #6131

* doc(postfix): install instructions

* address brad's comments
2018-06-21 16:11:02 -07:00
Brad Warren
1e1e7d8e97 Improve UA default in docs (#6120)
* Use less informative UA values in docs.

* set CERTBOT_DOCS during release
2018-06-21 15:40:42 -07:00
ohemorange
2ac0b55208 Reuse ACMEv1 accounts for ACMEv2 in production (#6134)
* Reuse accounts made with ACMEv1 when using an ACMEv2 Let's Encrypt server. This commit turns the feature on for the production server; the bulk of the work was done in 8e4303a.

* add upgrade test for production server
2018-06-21 13:23:09 -07:00
Harlan Lieberman-Berg
6771b8e05b docs: move warning about distro provided renewal (#6133)
Currently, you must read ten paragraphs about writing renewal hooks
before you find that most distributions will automatically renew certs
for you.  This is burying the lede in a major way; moving it up to the
header seems a better choice.
2018-06-21 12:52:08 -07:00
Joona Hoikkala
3877af6619 Gradually increasing HSTS max-age (#5912)
This PR adds the functionality to enhance Apache configuration to include HTTP Strict Transport Security header with a low initial max-age value.

The max-age value will get increased on every (scheduled) run of certbot renew regardless of the certificate actually getting renewed, if the last increase took place longer than ten hours ago. The increase steps are visible in constants.AUTOHSTS_STEPS.

Upon the first actual renewal after reaching the maximum increase step, the max-age value will be made "permanent" and will get value of one year.

To achieve accurate VirtualHost discovery on subsequent runs, a comment with unique id string will be added to each enhanced VirtualHost.

* AutoHSTS code rebased on master

* Fixes to match the changes in master

* Make linter happy with metaclass registration

* Address small review comments

* Use new enhancement interfaces

* New style enhancement changes

* Do not allow --hsts and --auto-hsts simultaneuously

* MyPy annotation fixes and added test

* Change oldest requrements to point to local certbot core version

* Enable new style enhancements for run and install verbs

* Test refactor

* New test class for main.install tests

* Move a test to a correct test class
2018-06-21 07:27:19 -07:00
Brad Warren
a875246a4b Merge pull request #6121 from certbot/squashed-postfix
Postfix plugin
2018-06-15 17:29:34 -07:00
sydneyli
40c50231ed Merge branch 'master' into squashed-postfix 2018-06-15 16:21:15 -07:00
Sydney Li
4ba153949d Fixing up postfix plugin
- Finishing refactor of postconf/postfix command-line utilities
 - Plugin uses starttls_policy plugin to specify per-domain policies

Cleaning up TLS policy code.

Print warning when setting configuration parameter that is overridden by master.

Update client to use new policy API

Cleanup and test fixes

Documentation fix

smaller fixes

Policy is now an enhancement and reverting works

Added a README, and small documentation fixes throughout

Moving testing infra from starttls repo to certbot-postfix

fixing tests and lint

Changes against new policy API

starttls-everywhere => starttls-policy

testing(postfix): Added more varieties of certificates to test against.

Moar fixes against policy API.

Address comments on README and setup.py

Address small comments on postconf and util

Address comments in installer

Python 3 fixes and Postconf tester extends TempDir test class

Mock out postconf calls from tests and test coverage for master overrides

More various fixes. Everything minus testing done

Remove STARTTLS policy enhancement from this branch.

sphinx quickstart

99% test coverage

some cleanup and testfixing

cleanup leftover files

Remove print statement

testfix for python 3.4

Revert dockerfile change

mypy fix

fix(postfix): brad's comments

test(postfix): coverage to 100

test(postfix): mypy

import mypy types

fix(postfix docs): add .rst files and fix build

fix(postfix): tls_only and server_only params behave nicely together

some cleanup

lint

fix more comments

bump version number
2018-06-15 15:46:55 -07:00
Brad Warren
5025b4ea96 Add certbot-postfix to tools
pep8ify

Delint

cover++

test more_info()

Refactor get_config_var

Don't duplicate changes to Postfix config

document instance variables

Always clear save_notes on save

Test deploy_cert and save and add MockPostfix.

Move mock and call to InstallerTest

Add getters and setters

Use postfix getters and setters

protect get_config_var

bump cover to 100%

bump required coverage to 100

s/config_dir/config_utility

Decrease minimum version to Postfix 2.6.

This is the minimum version that allows us to set ciphers to be used with
opportunistic TLS and is the oldest version packaged in any major distro.

Use tls_security_level instead of use_tls.

smtpd_tls_security_level should be used instead according to Postfix documentation.

Test smtpd_tls_security_level conditional

make dunder method an under method

refactor postconf usage

add check_all_output

test check_all_output

Add and test verify_exe_exists

Add PostfixUtilBase

Add ReadOnlyMainMap

Use _get_output instead of _call

Fix split strip typo
2018-06-15 15:46:48 -07:00
sydneyli
adc07ef933 fix(display): alternate spaces and dashes (#6119)
* fix(display): alternate spaces and dashes

* add comment
2018-06-15 15:03:58 -07:00
Brad Warren
3316eac178 Separate integration coverage (#6113)
* check coverage separately

* Add coverage minimums for integration tests.
2018-06-15 09:55:16 -07:00
Brad Warren
8b16a56de8 remove comment about renewer (#6115) 2018-06-15 11:43:48 +03:00
Brad Warren
453eafb11e Used packaged acme in oldest tests. (#6112) 2018-06-15 11:41:38 +03:00
r5d
c4ae376279 Add autorenew option to renew subcommand (#5911)
* Add autorenew option to `renew` subcommand.

* Change default value for 'autorenew' cli option.

* Update certbot.cli.prepare_and_parse_args (autorenew)

Set `default` for --autorenew and --no-autorenew.

* Update certbot.storage.RenewableCert.should_autorenew.

- Remove `interactive` argument in RenewableCert.should_autorenew.
- Update certbot.renewal.should_renew.

* Move autorenew enable/disable check to certbot.storage.

- Remove autorenew enable/disable check in
  `certbot.renewal.handle_renewal_request`.
- Fix RenewableCert.autorenewal_is_enabled; autorenew is stored in
  'renewalparams'.
- Add autorenew enable/disable check in
  `RenewableCert.should_autorenew`.
- Update tests test_time_interval_judgments,
  test_autorenewal_is_enabled, test_should_autorenew tests in
  storage_test.py

* certbot: Update RenewableCert.should_autorenew

Remove block that sets autorenew option in the renewal configuration
file.

* certbot: Update prepare_and_parse_args.

Remove --autorenew option.

* certbot: Update CLI_DEFAULTS.

Set default of `autorenew` to True.

* Remove unused imports in certbot.storage.
2018-06-13 15:24:51 -07:00
Brad Warren
fccfbd14b1 add 0.25.1 changelog (#6111) 2018-06-13 14:20:43 -07:00
Brad Warren
c9ae365f66 0.25.1 update for master (#6110)
* Release 0.25.1

(cherry picked from commit 21b5e4eadb)

* Bump version to 0.26.0
2018-06-13 14:20:15 -07:00
Brad Warren
9f20fa0ef9 Fixes #6085. (#6091)
The value of norecusedirs is the default in newer versions of pytest which is
listed at
https://docs.pytest.org/en/3.0.0/customize.html#confval-norecursedirs.
2018-06-12 17:31:22 -07:00
Brad Warren
95892cd4ab Require acme>=0.25.0 for nginx (#6099) 2018-06-12 17:24:19 -07:00
Roland Bracewell Shoemaker
da028ca9c2 Wrap TLS-ALPN extension with ASN.1 (#6089)
* Wrap TLS-ALPN extension with ASN.1

* Fix test
2018-06-11 11:59:57 -07:00
Brad Warren
5bf1c51de7 Merge pull request #6075 from certbot/candidate-0.25.0
Update certbot-auto and version numbers
2018-06-11 10:41:57 -07:00
Brad Warren
afa7e3fb82 Unrevert #6000 and silence deprecation warnings (#6082)
* Revert "Revert "switch signature verification to use pure cryptography (#6000)" (#6074)"

This reverts commit 3cffe1449c.

* Fixes #6073.

This silences the deprecation warnings from cryptography. I looked into only
silencing the cryptography warning specifically in the function, however,
CryptographyDeprecationWarning doesn't seem to be publicly documented, so we
probably shouldn't depend on it.
2018-06-08 00:45:23 +03:00
Brad Warren
3a8de6d172 Upgrade pinned twine version. (#6078)
For the past couple of releases, twine has errored while trying to upload
packages and this is fixed by upgrading to a newer version of twine. This
commit updates our pinned version installed when using tools/venv.sh to the
latest available version. pkginfo had to be upgraded as well to support the
latest version of twine.
2018-06-07 07:50:36 -07:00
Brad Warren
780a1b3a26 Don't require festival during signing. (#6079)
Festival isn't available via Homebrew and is only needed to read the hash
aloud, so let's not make it a strict requirement that it's installed. You can
simply read the hash from the terminal instead.
2018-06-07 11:43:45 +03:00
Brad Warren
da6320f4d1 Stop testing against Debian 7. (#6077)
Debian Wheezy is no longer supported (see https://wiki.debian.org/LTS) and
Amazon shut down their Debian 7 mirrors so let's stop trying to use Debian 7
during testing.
2018-06-07 11:11:06 +03:00
Brad Warren
eec37f65a8 Update changelog for 0.25.0 (#6076) 2018-06-06 19:01:55 -07:00
Brad Warren
4b11fe1fda Bump version to 0.26.0 2018-06-06 13:50:46 -07:00
Brad Warren
4ae2390c44 Release 0.25.0 2018-06-06 13:50:30 -07:00
Brad Warren
3cffe1449c Revert "switch signature verification to use pure cryptography (#6000)" (#6074)
This reverts commit 366c50e28e.
2018-06-06 07:58:50 -07:00
Brad Warren
868e5b831b Make python setup.py test use pytest for acme (#6072) 2018-06-05 17:59:11 -07:00
ohemorange
d905886f4c Automatically select among default vhosts if we have a port preference in nginx (#5944)
* automatically select among default vhosts if we have a port preference

* ports should be strings in the nginx plugin

* clarify port vs preferred_port behavior by adding allow_port_mismatch flag

* update all instances of default_vhosts to all_default_vhosts

* require port

* port should never be None in _get_default_vhost
2018-06-05 13:40:48 -07:00
sydneyli
09a28c7a27 Allow multiple add_headers directives (#6068)
* fix(nginx-hsts): allow multiple add_headers

* test(nginx): fix nginx tests
2018-06-04 17:44:51 -07:00
ohemorange
8e4303af9f Reuse ACMEv1 accounts for ACMEv2 (#5902)
* Reuse ACMEv1 accounts for ACMEv2

* Correct behavior

* add unit tests

* add _find_all_inner to comply with interface

* acme-staging-v01 --> acme-staging

* only create symlink to previous account if there is one there

* recurse on server path

* update tests and change internal use of load to use server_path

* fail gracefully on corrupted account file by returning [] when rmdir fails

* only reuse accounts in staging for now
2018-06-04 16:04:47 -07:00
Brad Warren
236f9630e0 Remove unneeded sys import (#5873)
* Remove unneeded sys import.

Once upon a time we needed this in some of these setup.py files because we were
using sys in the file, but we aren't anymore so let's remove the import.

* use setuptools instead of distutils
2018-06-04 15:04:56 -07:00
Maciej Dębski
15f1405fff Implement TLS-ALPN-01 challenge and standalone TLS-ALPN server (#5894)
The new challenge is described in https://github.com/rolandshoemaker/acme-tls-alpn.

* TLS-ALPN tests

* Implement TLS-ALPN challenge

* Skip TLS-ALPN tests on old pyopenssl

* make _selection methods private.
2018-06-04 14:54:17 -07:00
Brad Warren
4151737e17 Read in bytes to fix --reuse-key on Python 3 (#6069)
* Read bytes for now for compatibility

* add clarifying comment
2018-06-04 13:13:23 -07:00
Joona Hoikkala
f19ebab441 Make sure the pluginstorage file gets truncated when writing to it (#6062) 2018-06-04 11:08:40 -07:00
schoen
e2d6faa8a9 Add --reuse-key feature (#5901)
* Initial work on new version of --reuse-key

* Test for reuse_key

* Make lint happier

* Also test a non-dry-run reuse_key renewal

* Test --reuse-key in boulder integration test

* Better reuse-key integration testing

* Log fact that key was reused

* Test that the certificates themselves are different

* Change "oldkeypath" to "old_keypath"

* Simply appearance of new-key generation logic

* Reorganize new-key logic

* Move awk logic into TotalAndDistinctLines function

* After refactor, there's now explicit None rather than missing param

* Indicate for MyPy that key can be None

* Actually import the Optional type

* magic_typing is too magical for pylint

* Remove --no-reuse-key option

* Correct pylint test disable
2018-06-01 15:21:02 -07:00
Josh Soref
fb0d2ec3d6 Include missing space (#6061) 2018-06-01 15:09:02 -07:00
ohemorange
d53ef1f7c2 Add mypy info to Certbot docs (#6033)
* Add mypy info to Certbot docs

* break up lines

* link to mypy docs and links to https

* Expand on import wording

* be consistent about mypy styling
2018-05-31 13:57:23 -07:00
Joona Hoikkala
9f6b147d6f Do not call updaters and deployers when run with --dry-run (#6038)
When Certbot is run with --dry-run, skip running GenericUpdater and RenewDeployer interface methods.

This PR also makes the parameter order of updater.run_generic_updaters and updater.run_renewal_deployer consistent.

Fixes #5927

* Do not call updaters and deployers when run with --dry-run

* Use ConfigTestCase instead of mocking config objects manually
2018-05-26 08:31:23 -07:00
Joona Hoikkala
e48c653245 Change GenericUpdater parameter to lineage (#6030)
In order to give more flexibility for plugins using interfaces.GenericUpdater interface, lineage needs to be passed to the updater method instead of individual domains. All of the (present and potential) installers do not work on per domain basis, while the lineage does contain a list of them for installers which do.

This also means that we don't unnecessarily run the updater method multiple times, potentially invoking expensive tooling up to $max_san_amount times.

* Make GenericUpdater use lineage as parameter and get invoked only once per lineage
2018-05-25 11:00:37 -07:00
Jacob Hoffman-Andrews
a03c68fc83 Clean up boulder-fetch a bit. (#6032)
The value for FAKE_DNS is now always the same because Boulder's
docker-compose hardcodes it, so skip some sed.

Set a time limit on how long we'll wait for boulder to come up.
2018-05-24 10:53:21 -07:00
Jeremy Gillula
b1bcccb04b Changing opt-in ask for emails (#6035) 2018-05-23 20:40:34 -07:00
ohemorange
a1f5dc27f2 Add domain to error message when no matching server block found (#6034) 2018-05-23 14:03:30 -07:00
Brad Warren
0b215366b1 turn off cancel notifications (#5918) 2018-05-23 13:57:22 -07:00
Jacob Hoffman-Andrews
4304ff0d62 Bring up just the boulder container. (#6031)
Boulder recently added a "netaccess" container which may conflict.
2018-05-23 11:33:21 -07:00
Kevin Le
deb5b072d9 Log cases when standalone fails to bind a port. (#5985)
* Log cases when standalone fails to bind a port.

* Fix linter + changed log message

* Changed multiline string format

* Fixed indentation in standalone.py
2018-05-23 19:59:49 +03:00
pdamodaran
8440d0814d fixed dependency-requirements.txt (#6023) 2018-05-22 15:35:12 -07:00
Quang Vu
cfd4b8f363 #4242 Support multi emails register (#5994)
This change will allow registering/updating account with multi emails.
Detail is enclosed in #4242

* support multi emails register

* add more test cases

* update test to unregister before register

* update create path to support multi emaill

* refactor payload updating

* fix typo

* move command line doc to another place

* revert the change for updating account registration info, added unit test

* rearrange text for consistency
2018-05-22 15:32:44 -07:00
Brad Warren
c9a206ca89 Get mypy passing with check_untyped_defs everywhere (#6021)
* unchecked_typed_defs everywhere

* fix mypy for lock_test

* add magic_typing

* fix mypy in letshelp

* fix validator errors in compat test

* fix mypy for test_driver.py

* fix mypy in util.py

* delint
2018-05-21 20:23:21 -07:00
ohemorange
0d3a157525 Merge pull request #6020 from certbot/mabayhan-patch
Set correct Nginx server root on FreeBSD and Darwin
2018-05-21 16:53:11 -07:00
schoen
5d1df1cb4c Merge pull request #6022 from certbot/revert-pycon
Revert "Add link to pycon issues (#5959)"
2018-05-21 15:23:58 -07:00
Brad Warren
dec97fc126 Revert "Add link to pycon issues (#5959)"
This reverts commit 68359086ff.
2018-05-18 17:48:30 -07:00
Paul Kehrer
366c50e28e switch signature verification to use pure cryptography (#6000)
* switch signature verification to use pure cryptography

On systems that prevent write/execute pages this prevents a segfault
that is caused by pyopenssl creating a dynamic callback in the
verification helper.

* switch to using a verifier for older cryptography releases

also add ec support, test vectors, and a test
2018-05-18 09:10:41 -07:00
Dmitry Figol
36dfd06503 Prepare certbot module for mypy check untyped defs (#6005)
* Prepare certbot module for mypy check untyped defs

* Fix #5952

* Bump mypy to version 0.600 and fix associated bugs

* Fix pylint bugs after introducing mypy

* Implement Brad's suggestions

* Reenabling pylint and adding nginx mypy back
2018-05-18 06:28:17 -07:00
Brad Warren
250c0d6691 cd before running tests (#6017)
When importing a module, Python first searches the current directory. See
https://docs.python.org/3/tutorial/modules.html#the-module-search-path. This
means that running something like `import certbot` from the root of the Certbot
repo will use the local Certbot files regardless of the version installed on
the system or virtual environment.

Normally this behavior is fine because the local files are what we want to
test, however, during our "oldest" tests, we test against older versions of our
packages to make sure we're keeping compatibility. To make sure our tests use
the correct versions, this commit has our tests cd to an empty temporary
directory before running tests.

We also had to change the package names given to pytest to be the names used in
Python to import the package rather than the name of the files locally to
accommodate this.
2018-05-18 06:05:26 -07:00
Brad Warren
94bf97b812 Add remaining DNS plugins to mypy.ini and sort (#6018) 2018-05-18 12:26:10 +03:00
Erica Portnoy
1239d7a881 check platform with correct python 2018-05-17 20:02:27 -07:00
TyrannosourceExe
9b2862ebb0 3692 --dry-run expiration emails (#6015)
* If --dry-run is used and there exists no staging account, create account with no email

* added unit testing of dry-run to ensure certbot does not ask the user to create an email, and that certbot creates an account with no email
2018-05-17 16:03:01 -07:00
Brad Warren
1be1bd9211 remove PYTHONPATH (#6016) 2018-05-17 09:23:05 -07:00
pdamodaran
20418cdd68 Fixed #5859 (#6011) 2018-05-17 06:52:11 -07:00
Brad Warren
41e1976c17 Fix noisy tests (#6004)
* Fixes #5570.

The issue is calls to atexit aren't mocked out. During the tests there are many
repeated calls registering functions to be called when the process exits so
when the tests finishes, it prints a ton of output from running those
registered functions. This suppresses that by mocking out atexit.

* Mock at a lower level.

This ensures we don't mess with any other mocks in this test class by mocking
at the lowest level we can. Other tests shouldn't be mocking out specific
internals of functions in other modules, so this should work just fine.
2018-05-16 06:24:14 -07:00
Douglas Anger
722dac86d5 Fix crash when email submission endpoint unavailable (#6002)
* Fix crash when email submission endpoint unavailable

Handle KeyError and ValueError so that if the email submission endpoint
goes down, Certbot can still run.

Add tests to eff_test.py:
 - simulate non-JSON response as described in issue #5858
 - simulate JSON response without 'status' element

Non-JSON response throws an uncaught ValueError when attempting to
decode as JSON. A JSON response missing the 'status' element throws an
uncaught KeyError when checking whether status is True or False.

Teach _check_response to handle ValueError and KeyError and report an
issue to the user.

Rewrite if statement as assertion with try-except block to make error
handling consistent within the function. Update test_not_ok to make
mocked raise_for_status function raise a requests.exceptions.HTTPError.

Resolves #5858

* Update PR with requested changes

- Use `if` instead of `assert` to check `status` element of response JSON
- Handle KeyError and ValueError in the same way
- Import requests at the beginning of eff_test.py
- Clear JSON in test case in a more idiomatic way
2018-05-15 12:50:47 -07:00
GmH
751f9843b4 fixed issue #5974 for certbot-dns-route53 (#5984)
* fixed issue #5974 for certbot-dns-route53

* fixed issue #5967 for certbot-dns-digitalocean

* update to use acme.magic_typing and DefaultDict class

* added no-name-in-module identifier, for issue #5974

* added unused-import identifier to disable option, for issue #5974
2018-05-15 11:22:09 -07:00
dschlessman
9bd5b3dda2 Issue 5951/check untyped defs apache (#5989)
* resolved mypy untyped defs in parser.py

* resolved mypy untyped defs in obj.py

* removed unused imports

* resolved mypy untyped defs in http_01.py

* resolved mypy untyped defs in tls_sni_01.py

* resolved mypy untyped defs in configurator.py

* address mypy too-many-arguments error in override_centos.py

* resolved mypy untyped defs in http_01_test.py

* removed unused 'conf' argument that was causing mypy method assignment error

* address mypy error where same variable reassigned to different type

* address pylint and coverage issues

* one character space change for formatting

* fix required acme version for certbot-apache
2018-05-15 10:40:32 -07:00
James Hiebert
307f45f88f Enable checking of type annotation in Nginx plugin (#5997)
* Adds type checking for certbot-nginx

* First pass at type annotation in certbot-nginx

* Ensure linting is disabled for timing imports

* Makes container types specific per PR comments

* Removes unnecessary lint option
2018-05-15 09:36:47 -07:00
signop
802fcc99ee Add requests-toolbelt hashes to requirements. (#6001)
Fixes certbot/certbot#5993
2018-05-15 08:50:09 -07:00
Douglas Anger
2d68c9b81e Display (None) instead of a bullet for empty lists (#5999)
Include a line break before "(None)" to maintain consistency with output
for lists that are not empty.

Previous result as expected for non-empty lists:

    >>> _format_list('+', ['one', 'two', 'three'])
    '\n+ one\n+ two\n+ three'

Previous unexpected result for empty lists:

    >>> _format_list('+', [])
    '\n+ '

New result as expected (unchanged) for non-empty lists:

    >>> _format_list('+', ['one', 'two', 'three'])
    '\n+ one\n+ two\n+ three'

New behavior more explicit for empty lists:

    >>> _format_list('+', [])
    '\n(None)'

Resolves #5886
2018-05-15 08:46:36 -07:00
James Hiebert
42ef252043 Adds a note about Python3 in the Developer Guide (#5998) 2018-05-15 07:58:33 -07:00
Douglas Anger
99d94cc7e8 Make request logs pretty in Python 3 (#5992)
Decode response data as UTF-8 to eliminate ugly bytes repr in Python 3.

Resolves #5932
2018-05-14 14:45:25 -07:00
signop
02b128a128 Add support for specifying source_address to ClientNetwork. (#5990)
For certbot/certbot#3489
2018-05-14 14:43:43 -07:00
speter
372d4a046c docs/using.rst: fix typo (#5962) 2018-05-14 14:40:06 -07:00
Sarah Braden
a724dc659b correct error message text now prompts user to run $certbot certificates (#5988) 2018-05-14 14:21:09 -07:00
Douglas Anger
907ee79715 Check_untyped_defs in mypy with clean output for certbot-dns-nsone (#5987)
* check_untyped_defs in mypy with clean output for certbot-dns-nsone

Resolves #5972
2018-05-14 13:18:49 -07:00
Kevin Le
c1471fe873 Document IPv6/IPv4 binding on standalone. (#5983) 2018-05-14 13:15:02 -07:00
Brad Warren
94829e35be Merge pull request #5982 from certbot/handle-mypy-ini-conflicts
Check unchecked defs in various DNS plugins
2018-05-14 11:43:33 -07:00
Brad Warren
19c6a5e6ee Merge branch 'issue_5968' into handle-mypy-ini-conflicts 2018-05-14 11:12:31 -07:00
Brad Warren
875c8d4c01 Merge branch 'feature/5971' into handle-mypy-ini-conflicts 2018-05-14 11:11:08 -07:00
Brad Warren
8d994ae30d Merge branch 'issue_5970' into handle-mypy-ini-conflicts 2018-05-14 11:10:30 -07:00
Sarah Braden
be03a976d5 fixed issue #5969 for certbot-dns-dnsmadeeasy (#5976) 2018-05-14 10:54:26 -07:00
Kevin Le
74448e9344 Set pause=False to fix view_config_changes (#5977) 2018-05-14 10:45:12 -07:00
Sarah Braden
33583792fa blank line at eof 2018-05-14 13:43:15 -04:00
Sarah Braden
430f9414a9 fix for issue #5968 for certbot-dns-dnsimple 2018-05-14 13:42:12 -04:00
Douglas Anger
5636b55507 Check_untyped_defs in mypy with clean output for certbot-dns-luadns
* check_untyped_defs in mypy with clean output for certbot-dns-luadns

Resolves #5971
2018-05-14 13:40:27 -04:00
Sarah Braden
4bd9f4dac4 fix for issue #5970 regarding certbot-dns-google 2018-05-14 13:30:38 -04:00
Douglas Anger
2d45b0b07a Check_untyped_defs in mypy with clean output for certbot_dns_rfc2136 (#5975)
* check_untyped_defs in mypy with clean output for certbot_dns_rfc2136

Resolves #5973
2018-05-14 10:26:33 -07:00
Sarah Braden
a0775f42ba fixed issue #5969 for certbot-dns-dnsmadeeasy 2018-05-14 12:34:27 -04:00
Brad Warren
cce23c86c7 partially revert #5953 (#5964) 2018-05-14 08:08:24 -07:00
Brad Warren
68359086ff Add link to pycon issues (#5959)
* add link to pycon issues

* add especially
2018-05-13 08:06:19 -07:00
ohemorange
5940ee92ab add ready status type (#5941) 2018-05-11 14:25:02 -07:00
Brad Warren
9568f9d5b0 Add instructions on how to ask for help (#5957)
* Add instructions on how to ask for help

* s/setup/set up
2018-05-11 10:52:45 -07:00
Brad Warren
2f89a10f50 Small dev docs improvements (#5953)
* Clarify UNIX only

* Have people develop natively.

Some systems like Arch Linux and macOS require --debug in order to install
dependencies.

Our bootstrapping script for macOS works, so let's let people who want to
develop natively.

* briefly mention docker as dev option

* remove bad _common.sh info

* update OS dep section

* Remove sudo from certbot-auto usage

When sudo isn't available, Certbot is able to fall back to su. Removing it from
the instructions here allows the command to work when its run in minimal
systems like Docker where sudo may not be installed.

* copy advice about missing interpreters

* Improve integration tests docs

Explain what a boulder is and tell people they probably should just let the
tests run in Travis.

* Don't tell people to run integration tests.

I don't think any paid Certbot devs run integration tests locally and instead
rely on Travis. Let's not make others do it.

* fix spacing

* you wouldn't download a CA

* address review comments
2018-05-11 07:07:02 -07:00
Brad Warren
8851141dcf Revert "disable apacheconftest (#5937)" (#5954)
This reverts commit 83ea820525.
2018-05-11 06:12:10 -07:00
ohemorange
832941279b Specify that every domain name needs to be under a server_name directive (#5949) 2018-05-10 16:46:57 -07:00
Brad Warren
83ea820525 disable apacheconftest (#5937) 2018-05-09 12:11:36 -07:00
ohemorange
3eaf35f1e2 Check_untyped_defs in mypy with clean output for acme (#5874)
* check_untyped_defs in mypy with clean output for acme

* test entire acme module

* Add typing as a dependency because it's only in the stdlib for 3.5+

* Add str_utils, modified for python2.7 compatibility

* make mypy happy in acme

* typing is needed in prod

* we actually only need typing in acme so far

* add tests and more docs for str_utils

* pragma no cover

* add magic_typing

* s/from typing/from magic_typing/g

* move typing to dev_extras

* correctly set up imports

* remove str_utils

* only type: ignore for OpenSSL.SSL, not crypto

* Since we only run mypy with python3 anyway and we're fine importing it when it's not actually there, there's no actual need for typing to be present as a dependency

* comment magic_typing.py

* disable wildcard-import im magic_typing

* disable pylint errors

* add magic_typing_test

* make magic_typing tests work alongside other tests

* make sure temp_typing is set

* add typing as a dev dependency for python3.4

* run mypy with python3.4 on travis to get a little more testing with different environments

* don't stick typing into sys.modules

* reorder imports
2018-05-03 13:10:33 -07:00
Brad Warren
32e85e9a23 correct metaclass usage everywhere (#5919) 2018-05-03 10:59:25 +03:00
Joona Hoikkala
95c0c4a708 Py2 and Py3 compatibility for metaclass interfaces (#5913) 2018-05-02 15:56:37 -07:00
Brad Warren
03b20d972c Merge pull request #5914 from certbot/candidate-0.24.0
Update autos and version number for 0.24.0 release
2018-05-02 12:19:09 -07:00
Brad Warren
7fa3455dc6 Update changelog for 0.24.0 (#5915) 2018-05-02 12:18:29 -07:00
Brad Warren
0ec0d79c35 Bump version to 0.25.0 2018-05-01 16:59:48 -07:00
Brad Warren
4b870ef940 Release 0.24.0 2018-05-01 16:59:32 -07:00
mabayhan
b39507c5af Update constants.py
Fixed comma.
2018-04-17 09:09:27 -07:00
mabayhan
c443db0618 Update constants.py
On FreeBSD or MacOS, "certbot --nginx" fails. The reason is, at these op. systems, nginx directory is different than linux.
2018-04-12 16:33:10 -07:00
ynasser
5ca1b49e2e revoke accepts --cert-name too now; from the livestram 2017-11-02 06:23:38 +00:00
Brad Warren
66953435c9 Merge remote-tracking branch 'starttls-everywhere/certbotify' into postfix 2017-08-29 10:44:07 -07:00
Brad Warren
dde0bf0821 Remove non-plugin files 2017-08-29 10:42:31 -07:00
Brad Warren
142bc33545 Remove dead code 2017-08-29 10:38:53 -07:00
Brad Warren
a339de80f4 Add save() 2017-08-28 17:17:29 -07:00
Brad Warren
d663f7981a Add _write_config_changes 2017-08-28 15:24:51 -07:00
Brad Warren
72637b2cf6 Add util.check_call 2017-08-28 15:20:22 -07:00
Brad Warren
d0ea5958f9 Protect _set_config_var 2017-08-28 14:54:53 -07:00
Brad Warren
4805fb4b88 Add deploy_cert 2017-08-28 14:52:05 -07:00
Brad Warren
11b820c0e4 add set_config_var 2017-08-28 14:41:07 -07:00
Brad Warren
b21b66c0c0 Test restart command. 2017-08-25 14:29:50 -07:00
Brad Warren
218e15c9d4 Make restart() more robust. 2017-08-25 14:25:22 -07:00
Brad Warren
a6c08a2e25 Update prepare docstring. 2017-08-25 14:03:29 -07:00
Brad Warren
68dc678eed Call config_test in prepare 2017-08-25 14:02:57 -07:00
Brad Warren
5f3be9b1cf test config_test failure 2017-08-25 13:58:45 -07:00
Brad Warren
2ae187b1d6 Update config_test method 2017-08-25 13:55:21 -07:00
Brad Warren
b4b5c44750 Check postfix executable is found. 2017-08-25 13:38:07 -07:00
Brad Warren
a29a99fb6f Add --postfix-ctl flag. 2017-08-25 13:34:03 -07:00
Brad Warren
0f4c5c2305 Use common installer base 2017-08-25 11:41:30 -07:00
Brad Warren
60c6cc5f2a Write enhance() 2017-08-24 15:26:52 -07:00
Brad Warren
00e28592b6 Add supported_enhancements 2017-08-24 15:18:40 -07:00
Brad Warren
b92df1b71c Remove unused find_postfix_cf 2017-08-24 15:15:41 -07:00
Brad Warren
baf0d3343a Remove postfix version notes 2017-08-24 15:13:02 -07:00
Brad Warren
cc3896d5d4 Add test_lock_error 2017-08-24 15:12:11 -07:00
Brad Warren
b94e268f83 Remove unused certs_only_config 2017-08-24 15:05:39 -07:00
Brad Warren
b9177948d3 Remove _write_config 2017-08-24 15:05:24 -07:00
Brad Warren
3b2e9e49be Remove unneeded instance variables 2017-08-24 15:04:36 -07:00
Brad Warren
0efc02d6ee Lock the Postfix config dir 2017-08-24 15:03:11 -07:00
Brad Warren
967a1830e6 Rewrite get_all_names 2017-08-24 14:59:25 -07:00
Brad Warren
90ffe2aac0 Remove legacy get_all_certs_and_keys() method 2017-08-24 09:04:57 -07:00
Brad Warren
b342f40c2b remove old comments 2017-08-23 15:16:36 -07:00
Brad Warren
83e37acc8b group IPlugin methods 2017-08-23 15:16:07 -07:00
Brad Warren
d1f3a2deef move _get_version 2017-08-23 15:13:06 -07:00
Brad Warren
c9813a44d7 protect get_version() 2017-08-10 16:42:48 -07:00
Brad Warren
b98f541b91 clean up prepare() 2017-08-10 16:41:59 -07:00
Brad Warren
50a1f6340f Add _check_version. 2017-08-10 16:38:27 -07:00
Brad Warren
8c4ff5cb63 Use context manager to read conf file. 2017-08-10 16:36:08 -07:00
Brad Warren
290f5b8ce7 add test_set_config_dir 2017-08-10 16:35:21 -07:00
Brad Warren
48c5731a6b Write out temp config instead of mocking. 2017-08-10 16:29:08 -07:00
Brad Warren
749f758adb use a temporary directory 2017-08-10 16:25:30 -07:00
Brad Warren
2e8a8dfed5 add _set_config_dir 2017-08-10 16:17:46 -07:00
Brad Warren
25d1f6ec75 Test all branches of test_get_config_var 2017-08-10 16:11:09 -07:00
Brad Warren
4c4b63437f Test building of get_config_var command 2017-08-10 15:28:17 -07:00
Brad Warren
02c7eca6da Rename test classes and methods. 2017-08-10 15:08:41 -07:00
Brad Warren
b72dfc0c08 Add get_config_var 2017-08-10 15:07:32 -07:00
Brad Warren
7334fc3066 Rename postfix_dir to config_dir 2017-08-10 14:29:56 -07:00
Brad Warren
4e5740615c Use util.check_output in Postfix installer 2017-08-10 14:25:08 -07:00
Brad Warren
5a1d031f07 Rename util to certbot_util 2017-08-10 14:19:44 -07:00
Brad Warren
4715b2b12c Further document check_output 2017-08-10 14:18:27 -07:00
Brad Warren
5beaae3b65 Add check_output function and tests. 2017-08-10 14:17:13 -07:00
Brad Warren
dfd1cceb9b Test prepare() failure due to missing postconf 2017-08-10 12:26:47 -07:00
Brad Warren
192f0f60da test add_parser_arguments 2017-08-10 12:19:59 -07:00
Brad Warren
b395b72d1b Don't hardcode postconf path. 2017-08-10 11:51:01 -07:00
Brad Warren
a2dbf2fe4c Fix spacing 2017-08-10 11:20:21 -07:00
Brad Warren
86fe5ad362 Move calls to postconf to prepare(). 2017-08-10 11:16:46 -07:00
Brad Warren
89ae874f89 (temporarily) remove ca-certificates logic 2017-08-10 11:03:47 -07:00
Brad Warren
2a217189a6 (temporarily) remove policy_file 2017-08-10 10:58:27 -07:00
Brad Warren
481fb8413b Fix Postfix Installer __init__() 2017-08-10 08:50:08 -07:00
Brad Warren
d97a15861b Add --postfix-config-dir argument 2017-08-09 16:06:06 -07:00
Brad Warren
a15fe57225 remove policy config param 2017-08-09 15:57:19 -07:00
Brad Warren
6c5a8423b8 Remove unused logger from tests 2017-08-04 10:28:52 -07:00
Brad Warren
a66500ea38 Remove unused version argument. 2017-08-04 10:25:09 -07:00
Brad Warren
49cdfcec06 add six dependency 2017-08-04 10:20:36 -07:00
Brad Warren
b50a71ff4e Remove fopen argument in favor of mock.
This simplifies the actual production code and is a more standard approach in
Python.
2017-08-04 10:19:46 -07:00
Brad Warren
b37be61807 Import installer module directly in tests. 2017-08-04 10:12:54 -07:00
Brad Warren
4a3fd19c93 Move parse_line to the end of installer.py 2017-08-04 10:00:04 -07:00
Brad Warren
66ba0b5276 Remove invalid permissions exception.
Once things like locks are added, this error shouldn't be possible as it will
have occurred earlier.
2017-08-04 09:57:44 -07:00
Brad Warren
61c2209110 Use Certbot error types in the Postfix Installer 2017-08-04 09:56:39 -07:00
Brad Warren
694746409f s/ExistingConfigError/MisconfigurationError 2017-08-04 09:42:26 -07:00
Brad Warren
1c258c0a2c Add basic docstrings to Installer 2017-08-04 09:31:10 -07:00
Brad Warren
6c4b3c08a7 Clean up installer imports 2017-08-04 09:30:11 -07:00
Brad Warren
c2a8ce59ae Remove code to run the installer as on its own. 2017-08-04 09:28:22 -07:00
Brad Warren
5bf4ad1f52 Rename PostfixConfigGenerator to simply Installer 2017-08-04 09:25:12 -07:00
Brad Warren
ae08dc6bea Fix Postfix installer tests 2017-08-04 09:24:04 -07:00
Brad Warren
f89051cc2a Completely implement the Certbot plugin interfaces 2017-08-04 09:18:51 -07:00
Brad Warren
74b22a596e Ignore egg-info dirs 2017-08-04 09:03:30 -07:00
Brad Warren
e2d95b3719 Create packaging around PostfixConfigGenerator. 2017-08-04 09:02:56 -07:00
Aaron Zauner
dca274085d Merge pull request #38 from ekohl/patch-1
Correct markdown link syntax
2017-05-13 07:59:15 +02:00
Ewoud Kohl van Wijngaarden
619e273ae5 Correct markdown link syntax 2017-05-10 15:44:55 +02:00
Peter Eckersley
baa563f359 Merge pull request #30 from EFForg/azet/readme-fixup
README fixup
2016-05-12 19:12:50 -07:00
Aaron Zauner
64f2ddfa80 Merge pull request #31 from EFForg/dmwilcox/start-le-api
Dmwilcox/start le api
2016-05-03 13:45:42 +07:00
Daniel Wilcox
a5f23b5314 Configure logger to be a touch louder... than silent 2016-04-28 17:10:21 -07:00
Daniel Wilcox
af38c30c9c Fix path to postfix config variable. 2016-04-28 17:02:29 -07:00
Daniel Wilcox
887871833d Fix typo in changing quotes. 2016-04-28 16:44:25 -07:00
Daniel Wilcox
5d07b70269 Change over to using logging module from print statements. 2016-04-28 16:40:06 -07:00
Daniel Wilcox
c43602c908 Add simple config_test implementation. 2016-04-28 16:30:08 -07:00
Daniel Wilcox
4d24eb83a8 Move version fetching into get_version and implement more_info method. 2016-04-28 16:11:37 -07:00
Daniel Wilcox
7edceec8ac Add test case and fix to properly handle configs with no smtpd_tls_* vars. 2016-04-28 15:27:11 -07:00
Daniel Wilcox
c6baa82ee4 Implement basic get_all_certs_keys, tests pass. 2016-04-28 15:14:06 -07:00
Daniel Wilcox
e75bafa439 Add basic test for get_all_certs_keys IInstaller interface method. 2016-04-28 12:27:49 -07:00
Daniel Wilcox
cc83e9ba52 Wrap some lines, new style exceptions, return check for restart. 2016-04-28 12:26:56 -07:00
Peter Eckersley
5d355044c4 Merge pull request #28 from EFForg/azet/disable-v2-v3-fixup
set _all_ client&server options to exclude v2 and v3 #24
2016-04-25 23:18:05 +10:00
Aaron Zauner
1f95ac9640 README fixup pt. 1 2016-04-20 12:43:51 +07:00
Daniel Wilcox
5928fae89e Merge branch 'master' of github.com:dmwilcox/starttls-everywhere into hackathon 2016-03-29 15:04:23 -07:00
Daniel Wilcox
0bf2537a55 Add initial gitignore. 2016-03-29 14:32:53 -07:00
Daniel Wilcox
fd1cef3fa0 Implement get_all_names. 2016-03-29 14:31:27 -07:00
Daniel Wilcox
fee9c86233 Add failing test for get_all_names. 2016-03-29 14:20:33 -07:00
Daniel Wilcox
5cc317408c Move attributes into init and allow for injecting file contents for testing. 2016-03-29 14:19:13 -07:00
Aaron Zauner
2900d5122c set _all_ client&server options to exclude v2 and v3 #24 2016-03-29 19:31:00 +02:00
Peter Eckersley
c7a8d1cb7a Merge pull request #27 from EFForg/azet/disable-v2-v3
disable SSLv2,3 client-side too #24
2016-03-29 10:12:48 -07:00
Aaron Zauner
0ba508ee2d disable SSLv2,3 client-side too #24 2016-03-29 19:02:00 +02:00
Aaron Zauner
1d37e94e17 disable SSLv3 and v3 by default #24 2016-03-29 18:43:08 +02:00
Peter Eckersley
843e156a51 Merge pull request #26 from EFForg/log-summary
Improve log summary code
2016-03-29 09:23:43 -07:00
Peter Eckersley
ce88098ba6 Merge remote-tracking branch 'origin/master' 2016-03-29 09:21:15 -07:00
Aaron Zauner
1cde7f9b54 added doc. on postfix version dependent features 2016-03-29 16:22:19 +02:00
Aaron Zauner
e42a222c5d preliminary Postfix version check 2016-03-29 15:44:51 +02:00
Peter Eckersley
87022782fb Catch stray missing line 2016-03-09 10:59:10 -08:00
Peter Eckersley
a58c652443 ConfigParser is gone! 2016-03-09 10:54:15 -08:00
Peter Eckersley
0ce1684ba6 Changes to MTAConfigGenerator needed to be moved by hand 2016-03-09 10:50:15 -08:00
Peter Eckersley
af1e94be5a Merge branch 'log-summary'
Including adding dmwilcox's mandatory policy argument to jsha's argparse code
2016-03-09 10:05:43 -08:00
Peter Eckersley
e88eac65da [documentation] Add links to LEPC interface sources 2016-03-02 10:47:59 -08:00
Peter Eckersley
874c59012a Update README 2016-03-02 10:47:52 -08:00
Peter Eckersley
b2977ad6a9 Documentation details 2016-03-01 19:06:02 -08:00
Peter Eckersley
a435036a1e Document MVP objectives 2016-03-01 19:03:47 -08:00
Peter Eckersley
3a8e1d7a70 Further status update 2016-03-01 16:40:03 -08:00
Peter Eckersley
1210c04f14 tweak README 2016-03-01 16:38:55 -08:00
Peter Eckersley
6e2b6a0817 Update README 2016-03-01 16:37:56 -08:00
Peter Eckersley
9a122626b9 Merge pull request #23 from EFForg/delimiters
Fix postfix TLS policy map delimeters
2016-03-01 16:23:10 -08:00
Peter Eckersley
d0629e62ea Merge remote-tracking branch 'origin/master' into delimiters 2016-02-24 19:08:37 -08:00
Peter Eckersley
b8ce13f96a Merge pull request #21 from EFForg/master
Restructured project directory, unified scripts, moved to subdirs
2016-02-24 19:06:06 -08:00
Peter Eckersley
6db2858825 Correct policy map delimitation 2016-02-24 18:19:47 -08:00
Aaron Zauner
4d14423a21 re-structured project folder..
* Removed `ConfigParser.py` (ACK by Daniel).
* Removed MTAConfigGenerator-stub and renamed to `PostfixConfigGenerator.py`
* Moved all text/csv processing scripts to `tools/`.
* Moved all configuration files into dedicated `examples/` directory.
* unified all shebangs to `#!/usr/bin/env python` (default system python).
* Moved domain CSV and text-files to `share/`.
2016-02-24 23:35:52 +01:00
Peter Eckersley
959e943de8 Merge remote-tracking branch 'github/master' 2016-02-18 18:58:45 -08:00
Peter Eckersley
9e42f6ed08 Clarify project status 2016-02-18 18:58:34 -08:00
dmwilcox
bd20b50879 Merge pull request #19 from EFForg/hackathon
We now install certs!
2016-02-17 12:34:23 -08:00
Peter Eckersley
28bb0eb6ac Obtain acceptable_mxs the right way 2016-02-17 12:20:26 -08:00
Peter Eckersley
074fef773b Make up a domain 2016-02-17 12:14:24 -08:00
Peter Eckersley
3aeb62cf7e bugfixes, cleanups 2016-02-17 12:13:28 -08:00
Peter Eckersley
8f5b8558d2 Actually deploy a cert?
- Also add missing selves to interface methods
2016-02-17 12:09:25 -08:00
Peter Eckersley
47a5b7e3ba Start implementing cert installation 2016-02-17 11:56:41 -08:00
Peter Eckersley
cdc8b94823 Merge remote-tracking branch 'dmwilcox/master' into hackathon 2016-02-17 11:38:06 -08:00
dmwilcox
fedf970284 Fix bad import to be import *as*... as it should be. 2016-02-17 11:37:28 -08:00
Peter Eckersley
965027ce52 Start metamorphising to use LE's IInstaller interface 2016-02-17 11:36:38 -08:00
dmwilcox
1cde095dc2 Merge pull request #18 from EFForg/hackathon
hackathon work
2016-02-17 11:22:18 -08:00
Peter Eckersley
39a01190d5 Use 4 space indents
For greater compatibility with LE codebases
2016-02-17 11:19:02 -08:00
Peter Eckersley
9539f21390 Merge pull request #16 from dmwilcox/master
New Config container -- drop-in replacement
2016-02-17 10:34:14 -08:00
dmwilcox
9abef4c0bd Log MX records that will not be configured. 2016-02-17 10:20:56 -08:00
dmwilcox
146fce3878 Add comment about magic hat trick with class properties. 2016-02-17 09:51:37 -08:00
dmwilcox
904dc11b03 Add docstrings for Config objects update/merge methods. 2016-02-17 09:45:37 -08:00
dmwilcox
7c6c3efb0f Confirmed Postfix log parsing is working again. 2016-01-21 01:46:30 -08:00
dmwilcox
c87b5d6a78 Hook the MTA config generation into the new config container. 2016-01-21 00:56:29 -08:00
pypoet
9a71b18b85 Fix updates and merges, add testing to make sure they stay fixed. 2015-10-23 18:26:26 -07:00
pypoet
6da5de6b19 Beginnings of generic config composibility in place. 2015-10-16 00:57:42 -04:00
pypoet
147f58bdbc Rounds out missing features and is now on par with ConfigParser.py.
Still missing logging, composibility and a couple of attributes.
2015-10-14 02:49:34 -04:00
pypoet
fe17c873c0 Initial re-vamp of the Config object to centralize validation and
lay the basis for making compositions of configs and overrides.
Lots of TODOs, be warned.
2015-10-13 17:09:03 -04:00
Jacob Hoffman-Andrews
613a8f5e88 Improve cronjob operation to only process diffs. 2014-10-20 16:15:13 -04:00
Jacob Hoffman-Andrews
f04e8259a9 Protocols separated by colons, not commas. 2014-10-20 09:29:22 -04:00
Jacob Hoffman-Andrews
828c00b758 Find deferred lines in log summary.
Also add a cron mode that only emits output if there's something wrong.
2014-10-17 15:28:49 -04:00
Jacob Hoffman-Andrews
1d47acddfd Remove extraneous print of config. 2014-10-17 15:28:44 -04:00
Jacob Hoffman-Andrews
726afb8b95 Install CAfile on config generation. 2014-10-17 15:28:32 -04:00
Jacob Hoffman-Andrews
a1d016d031 Add motivating examples to README 2014-10-13 15:57:59 -04:00
jsha
91f6be6b3b Merge pull request #6 from jsha/move-collection
Move collection
2014-10-08 16:36:19 -04:00
Peter Eckersley
97cce82e5a Merge pull request #7 from jsha/min-tls-version
Treat min-tls-version as a minimum.
2014-10-08 11:32:54 -07:00
Jacob Hoffman-Andrews
622fc72dc1 Treat min-tls-version as a minimum.
Fixes #5.
2014-09-10 17:36:46 -04:00
Jacob Hoffman-Andrews
42c63cb6dd More informative message for RDNS fail. 2014-09-08 16:25:40 -04:00
Jacob Hoffman-Andrews
31e320d0a7 Collect certs in a subdir. 2014-08-13 15:59:50 -04:00
Jacob Hoffman-Andrews
7f9dadd681 Merge branch 'master' of github.com:EFForg/starttls-everywhere 2014-08-13 10:50:18 -04:00
Jacob Hoffman-Andrews
ad40618897 Respond to pde's comments 2014-08-13 10:49:50 -04:00
jsha
e0edc1b7ec Add link to mailing list. 2014-08-12 12:18:32 -04:00
Peter Eckersley
78a55c3823 Question about postfix logparsing output 2014-08-08 13:16:40 -07:00
Peter Eckersley
9cafcf1caf Comment regexp 2014-08-08 13:15:15 -07:00
Peter Eckersley
8c6d28ce95 Comment with some sample postfix log lines
(both those we support already, and those we may want to in the future...)
2014-08-08 13:01:33 -07:00
Peter Eckersley
30ba7e9305 Deduplicate stray comment line 2014-08-08 13:01:04 -07:00
Peter Eckersley
a6700e3172 Merge branch 'misc' of ssh://github.com/EFForg/starttls-everywhere into misc 2014-08-08 11:58:26 -07:00
Peter Eckersley
0bd8134e5f Comments
(and code review in comment form)
2014-08-08 11:57:01 -07:00
Peter Eckersley
ff5810d78f Don't accept files on the command line that don't do anything 2014-08-08 11:36:04 -07:00
Peter Eckersley
21ff3acf93 Set/list comprehensions are a bit more readable than lambdas 2014-08-08 11:26:59 -07:00
Jacob Hoffman-Andrews
cebc6f9a20 ProcessGoogleSTARTTLSDomains -> latest CSV format.
Also output in a more useful format, require >= 99% encrypted output in the CSV,
hande .{...} domains, and manually add gmail.com.
2014-08-08 13:28:01 -04:00
Jacob Hoffman-Andrews
749c4e39e0 Update meta-config with latest domains. 2014-08-07 17:34:40 -04:00
Jacob Hoffman-Andrews
6a1aa8e6b6 Update to latest config format. 2014-08-07 17:34:21 -04:00
Jacob Hoffman-Andrews
bdd4d01dc7 Update and sort golden-domains.txt, commit google-starttls-domains.csv 2014-08-07 16:57:51 -04:00
Jacob Hoffman-Andrews
6a40e1964b Notice if there are no "Trusted" entries. 2014-08-07 15:05:10 -04:00
Jacob Hoffman-Andrews
82ef8b185a Merge branch 'master' of github.com:EFForg/starttls-everywhere into misc
Conflicts:
	MTAConfigGenerator.py
	starttls-everywhere.json
2014-08-06 18:26:31 -04:00
Jacob Hoffman-Andrews
dd4f9d35ae Improve checker and starttls-everywhere.json.
Now we alphabetize keys on output for more useful diffs.
2014-08-06 18:23:13 -04:00
Jacob Hoffman-Andrews
127d49e837 Manually add a couple known-good domains.
These were skipped because in the Google data they are represented as,
e.g. 'gmail.{..}'.
2014-08-06 18:22:32 -04:00
Jacob Hoffman-Andrews
a85fad98c0 Restore default config 2014-08-06 18:22:08 -04:00
Jacob Hoffman-Andrews
1c3c69aaad Allow parameters to MTAConfigGenerator. 2014-08-06 17:08:49 -04:00
Jacob Hoffman-Andrews
3de8c2a651 Do a better job finding address domain from mx doman. 2014-08-06 17:08:30 -04:00
Peter Eckersley
803c39e585 Merge remote-tracking branch 'github/master'
Conflicts:
	ConfigParser.py
2014-06-19 09:11:15 -07:00
Jacob Hoffman-Andrews
51980e212f First pass at logs analysis 2014-06-18 17:50:41 -04:00
Peter Eckersley
3df343495e Various fixups 2014-06-18 10:58:53 -07:00
Peter Eckersley
45aeb5b003 Merge remote-tracking branch 'github/master'
Conflicts:
	ConfigParser.py
	starttls-everywhere.json
2014-06-18 10:56:48 -07:00
Jacob Hoffman-Andrews
67ee3b0488 Config format change - don't use * as it's misleading. 2014-06-18 12:32:17 -04:00
Peter Eckersley
2eba47a716 Verify more of the policy language 2014-06-16 14:23:51 -07:00
Jacob Hoffman-Andrews
51f90ffafb Write policies based on address domain, not stripped mx-domain 2014-06-16 18:26:56 +00:00
Peter Eckersley
8d6b6c358a Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere
Conflicts:
	MTAConfigGenerator.py
2014-06-16 02:47:15 -07:00
Peter Eckersley
9da4f93ae5 Changes for live demo 2014-06-16 02:46:46 -07:00
Jacob Hoffman-Andrews
3cf61a54b7 Add alternatives section 2014-06-13 13:57:19 -04:00
Jacob Hoffman-Andrews
43d457aa77 Typo cleanup in MTAConfigGenerator 2014-06-12 13:18:20 -04:00
Jacob Hoffman-Andrews
499f6c2fad Add comment to ProcessgoogleSTARTTLSDomains.py 2014-06-12 11:53:02 -04:00
Jacob Hoffman-Andrews
9cd71642fb Fix italicization boundaries 2014-06-12 11:50:39 -04:00
Jacob Hoffman-Andrews
7a7329fa19 Merge remote-tracking branch 'pde/master' 2014-06-12 11:49:09 -04:00
Jacob Hoffman-Andrews
9ce047980a Add .gitignore 2014-06-12 11:40:17 -04:00
Peter Eckersley
02abaf57bd Remove stray conf entry from README 2014-06-12 05:24:51 -07:00
Peter Eckersley
3d7b53daf1 Tune verbosity; reload postfix conf if we can 2014-06-12 05:24:05 -07:00
Peter Eckersley
e99abfacfd Include the demo example with the real stuff, temporarily 2014-06-12 05:22:30 -07:00
Peter Eckersley
a2ee328bc0 Paramaterise "/etc/postfix" 2014-06-11 10:32:52 -07:00
Peter Eckersley
34cba3accf Now successfully parsing the larger policy set 2014-06-11 09:51:56 -07:00
Peter Eckersley
3712a45399 Further (and different, and better) standardisation 2014-06-11 09:48:43 -07:00
Peter Eckersley
182e9b29e4 Trying to standardize JSON terms 2014-06-11 09:42:17 -07:00
Peter Eckersley
2540f1f1e8 Writing to the domain-wise policy file actually works now. 2014-06-11 09:31:41 -07:00
Peter Eckersley
eea1b0d8c5 Switch naming conventions so that modules are importable :)
It turns out that python won't import modules with hyphens in their names.
It seems that CamelCase is most consistent with our Class naming.  However
feel free to do something different instead :)
2014-06-11 09:18:56 -07:00
Peter Eckersley
6e1bcfdb2a WIP implementing domain-wise TLS policies 2014-06-11 09:17:50 -07:00
Peter Eckersley
d7e4d93190 Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-11 09:13:01 -07:00
Jacob Hoffman-Andrews
0c4e332811 Set up test CA and valid signed cert by that CA.
Also require valid cert for host 'valid'.
2014-06-11 11:45:28 -04:00
Peter Eckersley
46ce09d36d MTA config wrangling seems to work 2014-06-11 05:01:46 -07:00
Peter Eckersley
269f15f9ee Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-11 00:38:40 -07:00
Jacob Hoffman-Andrews
21e841fd13 Move synced folders into a common one.
Also, create certificates.
2014-06-10 15:08:52 -04:00
Peter Eckersley
a03db04ff4 WIP implementing deletion of existing cf lines 2014-06-10 08:08:40 -07:00
Peter Eckersley
d0bcc13059 Break ground on an postfix config wrangling engine 2014-06-10 08:08:17 -07:00
Jacob Hoffman-Andrews
17425e7337 Merge branch 'master' of github.com:jsha/starttls-everywhere
Conflicts:
	README.md
2014-06-09 14:56:34 -07:00
jsha
7bdb63376c Merge pull request #2 from pde/master
Initial checkin of config parser, some changes to the config format.
2014-06-09 14:53:23 -07:00
Jacob Hoffman-Andrews
bdbc46fc84 Add candidate starttls-everywhere config json 2014-06-09 13:10:43 -07:00
Jacob Hoffman-Andrews
0d43d2988a Update check-starttls.py to generate starttls everywhere config. 2014-06-09 13:08:01 -07:00
Jacob Hoffman-Andrews
79924108c7 Reorder JSON file to emphasize MX policies over address-domain -> MX domain mapping. 2014-06-09 10:12:09 -07:00
Peter Eckersley
c033905b16 Now validating most of the config json 2014-06-08 06:22:32 -07:00
Peter Eckersley
839c523048 Fix typos 2014-06-08 06:22:22 -07:00
Peter Eckersley
7c81f23a07 Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-07 06:50:21 -07:00
Jacob Hoffman-Andrews
e534a43d1a Make sender actually attempt TLS on outbound connections. 2014-06-06 16:07:38 -07:00
Peter Eckersley
fcd1a98201 Break ground on a config parser 2014-06-06 15:54:33 -07:00
Peter Eckersley
dc606eac7d Some tweaks to the config format 2014-06-06 15:54:22 -07:00
Jacob Hoffman-Andrews
372c96d9fd Update Postfix configuration and mail-send-loop 2014-06-06 14:06:08 -07:00
Jacob Hoffman-Andrews
5a9f90dc30 Merge branch 'master' of github.com:jsha/starttls-everywhere 2014-06-06 14:05:03 -07:00
Jacob Hoffman-Andrews
ce0a6a1814 Add example config.json 2014-06-06 14:04:38 -07:00
jsha
fa5acdf674 Simplify SPKI hash usage 2014-06-06 13:44:03 -07:00
jsha
31db3b7034 Merge pull request #1 from pde/master
Add specific postfix configs
2014-06-06 12:59:41 -07:00
Peter Eckersley
30938260d4 Split postfix configs across the VMs, and start making them do things 2014-06-05 19:45:21 -07:00
Peter Eckersley
7bd06a4d35 Work in progress 2014-06-05 17:04:28 -07:00
Peter Eckersley
ace9d2383d Start work on forcing TLS to valid 2014-06-05 16:51:38 -07:00
Jacob Hoffman-Andrews
ed0c024209 Improved provisioning and certificate checking. 2014-06-05 16:01:07 -07:00
Jacob Hoffman-Andrews
714cb17dcb Clarify example usage of pinsets by including a CA 2014-06-05 14:26:45 -07:00
Jacob Hoffman-Andrews
6fb51d5422 Example shouldn't include hashes from Chrome source. 2014-06-05 14:11:08 -07:00
jsha
4b5b9f164f nexthop-domains -> address-domains 2014-06-05 11:51:19 -07:00
jsha
3d9d5607bd Formatting issue #2 in design doc 2014-06-05 11:34:12 -07:00
jsha
aa417eec15 Formatting issue in design doc 2014-06-05 11:10:07 -07:00
jsha
844ec79f01 Add design doc 2014-06-05 11:05:08 -07:00
Jacob Hoffman-Andrews
f0b9ef2716 Add a Vagrantfile and the list of golden domains. 2014-06-05 10:43:01 -07:00
Jacob Hoffman-Andrews
8857302347 check-starttls and process-google-starttls-domains.py 2014-06-04 14:41:18 -07:00
429 changed files with 17302 additions and 6030 deletions

4
.gitignore vendored
View File

@@ -6,7 +6,8 @@ dist*/
/venv*/
/kgs/
/.tox/
/releases/
/releases*/
/log*
letsencrypt.log
certbot.log
letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64
@@ -39,6 +40,7 @@ tests/letstest/venv/
# pytest cache
.cache
.mypy_cache/
.pytest_cache/
# docker files
.docker

View File

@@ -41,7 +41,7 @@ load-plugins=linter_plugin
# --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,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import,duplicate-code
disable=fixme,locally-disabled,locally-enabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import,duplicate-code
# abstract-class-not-used cannot be disabled locally (at least in
# pylint 1.4.1), same for abstract-class-little-used

View File

@@ -4,61 +4,9 @@ cache:
directories:
- $HOME/.cache/pip
before_install:
- '([ $TRAVIS_OS_NAME == linux ] && dpkg -s libaugeas0) || (brew update && brew install augeas python3 && brew upgrade python && brew link python)'
before_script:
- 'if [ $TRAVIS_OS_NAME = osx ] ; then ulimit -n 1024 ; fi'
matrix:
include:
- python: "2.7"
env: TOXENV=py27_install BOULDER_INTEGRATION=v1
sudo: required
services: docker
- python: "2.7"
env: TOXENV=py27_install BOULDER_INTEGRATION=v2
sudo: required
services: docker
- python: "2.7"
env: TOXENV=cover FYI="this also tests py27"
- sudo: required
env: TOXENV=nginx_compat
services: docker
before_install:
addons:
- python: "2.7"
env: TOXENV=lint
- python: "3.5"
env: TOXENV=mypy
- python: "2.7"
env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest'
sudo: required
services: docker
- python: "3.4"
env: TOXENV=py34
sudo: required
services: docker
- python: "3.6"
env: TOXENV=py36
sudo: required
services: docker
- sudo: required
env: TOXENV=apache_compat
services: docker
before_install:
addons:
- sudo: required
env: TOXENV=le_auto_trusty
services: docker
before_install:
addons:
- python: "2.7"
env: TOXENV=apacheconftest
sudo: required
- python: "2.7"
env: TOXENV=nginxroundtrip
- export TOX_TESTENV_PASSENV=TRAVIS
# Only build pushes to the master branch, PRs, and branches beginning with
# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of
@@ -70,13 +18,33 @@ branches:
- /^\d+\.\d+\.x$/
- /^test-.*$/
# Jobs for the main test suite are always executed (including on PRs) except for pushes on master.
not-on-master: &not-on-master
if: NOT (type = push AND branch = master)
# Jobs for the extended test suite are executed for cron jobs and pushes on non-master branches.
extended-test-suite: &extended-test-suite
if: type = cron OR (type = push AND branch != master)
matrix:
include:
# Main test suite
- python: "2.7"
env: BOULDER_INTEGRATION=v1 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: BOULDER_INTEGRATION=v2 INTEGRATION_TEST=nginx TOXENV=py27-nginx-oldest
sudo: required
services: docker
<<: *extended-test-suite
# container-based infrastructure
sudo: false
addons:
apt:
sources:
- augeas
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
- python-dev
- python-virtualenv
@@ -88,23 +56,13 @@ addons:
# For certbot-nginx integration testing
- nginx-light
- openssl
# for apacheconftest
- apache2
- libapache2-mod-wsgi
- libapache2-mod-macro
install: "travis_retry $(command -v pip || command -v pip3) install tox coveralls"
install: "$(command -v pip || command -v pip3) install codecov tox"
script:
- travis_retry tox
- '[ -z "${BOULDER_INTEGRATION+x}" ] || (travis_retry tests/boulder-fetch.sh && tests/tox-boulder-integration.sh)'
- tox
- '[ -z "${BOULDER_INTEGRATION+x}" ] || (tests/boulder-fetch.sh && tests/tox-boulder-integration.sh)'
after_success: '[ "$TOXENV" == "cover" ] && coveralls'
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov'
notifications:
email: false
irc:
channels:
- secure: "SGWZl3ownKx9xKVV2VnGt7DqkTmutJ89oJV9tjKhSs84kLijU6EYdPnllqISpfHMTxXflNZuxtGo0wTDYHXBuZL47w1O32W6nzuXdra5zC+i4sYQwYULUsyfOv9gJX8zWAULiK0Z3r0oho45U+FR5ZN6TPCidi8/eGU+EEPwaAw="
on_success: never
on_failure: always
use_notice: true

View File

@@ -1,6 +1,546 @@
# Certbot change log
Certbot adheres to [Semantic Versioning](http://semver.org/).
Certbot adheres to [Semantic Versioning](https://semver.org/).
## 0.33.0 - master
### Added
* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation
path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+.
* CLI flag `--http-port` has been added for Nginx plugin exclusively, and replaces
`--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while
setting up a new SSL vhost. By default the HTTPS port is 443.
### Changed
* Support for TLS-SNI-01 has been removed from all official Certbot plugins.
* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone`
modules are deprecated and will be removed soon.
* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will
generate a deprecation warning if used, and will be removed soon.
* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op,
will generate a deprecation warning if used, and will be removed soon.
* CLI flag `--standalone-supported-challenges` has been removed.
### Fixed
* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5
is installed. We fixed a bug in Certbot causing it to interpret timestamps in
the OCSP response as being in the local timezone rather than UTC.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* certbot
* certbot-apache
* certbot-nginx
More details about these changes can be found on our GitHub repo.
## 0.32.0 - 2019-03-06
### Added
* If possible, Certbot uses built-in support for OCSP from recent cryptography
versions instead of the OpenSSL binary: as a consequence Certbot does not need
the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed.
### Changed
* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the
warnings described at https://github.com/certbot/josepy/issues/13.
* Apache plugin now respects CERTBOT_DOCS environment variable when adding
command line defaults.
* The running of manual plugin hooks is now always included in Certbot's log
output.
* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest.
* An ACME CA server may return a "Retry-After" HTTP header on authorization polling, as
specified in the ACME protocol, to indicate when the next polling should occur. Certbot now
reads this header if set and respect its value.
* The `acme` module avoids sending the `keyAuthorization` field in the JWS
payload when responding to a challenge as the field is not included in the
current ACME protocol. To ease the migration path for ACME CA servers,
Certbot and its `acme` module will first try the request without the
`keyAuthorization` field but will temporarily retry the request with the
field included if a `malformed` error is received. This fallback will be
removed in version 0.34.0.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-nginx
More details about these changes can be found on our GitHub repo.
## 0.31.0 - 2019-02-07
### Added
* Avoid reprocessing challenges that are already validated
when a certificate is issued.
* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges
with the `acme` module.
### Changed
* Certbot's official Docker images are now based on Alpine Linux 3.9 rather
than 3.7. The new version comes with OpenSSL 1.1.1.
* Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support
on 2.x branch is maintained).
* Apache plugin now attempts to configure all VirtualHosts matching requested
domain name instead of only a single one when answering the HTTP-01 challenge.
### Fixed
* Fixed accessing josepy contents through acme.jose when the full acme.jose
path is used.
* Clarify behavior for deleting certs as part of revocation.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-dns-cloudxns
* certbot-dns-dnsimple
* certbot-dns-dnsmadeeasy
* certbot-dns-gehirn
* certbot-dns-linode
* certbot-dns-luadns
* certbot-dns-nsone
* certbot-dns-ovh
* certbot-dns-sakuracloud
More details about these changes can be found on our GitHub repo.
## 0.30.2 - 2019-01-25
### Fixed
* Update the version of setuptools pinned in certbot-auto to 40.6.3 to
solve installation problems on newer OSes.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, this
release only affects certbot-auto.
More details about these changes can be found on our GitHub repo.
## 0.30.1 - 2019-01-24
### Fixed
* Always download the pinned version of pip in pipstrap to address breakages
* Rename old,default.conf to old-and-default.conf to address commas in filenames
breaking recent versions of pip.
* Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv to address breakages
from venv downloading the latest pip
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* certbot-apache
More details about these changes can be found on our GitHub repo.
## 0.30.0 - 2019-01-02
### Added
* Added the `update_account` subcommand for account management commands.
### Changed
* Copied account management functionality from the `register` subcommand
to the `update_account` subcommand.
* Marked usage `register --update-registration` for deprecation and
removal in a future release.
### Fixed
* Older modules in the josepy library can now be accessed through acme.jose
like it could in previous versions of acme. This is only done to preserve
backwards compatibility and support for doing this with new modules in josepy
will not be added. Users of the acme library should switch to using josepy
directly if they haven't done so already.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
More details about these changes can be found on our GitHub repo.
## 0.29.1 - 2018-12-05
### Added
*
### Changed
*
### Fixed
* The default work and log directories have been changed back to
/var/lib/letsencrypt and /var/log/letsencrypt respectively.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* certbot
More details about these changes can be found on our GitHub repo.
## 0.29.0 - 2018-12-05
### Added
* Noninteractive renewals with `certbot renew` (those not started from a
terminal) now randomly sleep 1-480 seconds before beginning work in
order to spread out load spikes on the server side.
* Added External Account Binding support in cli and acme library.
Command line arguments --eab-kid and --eab-hmac-key added.
### Changed
* Private key permissioning changes: Renewal preserves existing group mode
& gid of previous private key material. Private keys for new
lineages (i.e. new certs, not renewed) default to 0o600.
### Fixed
* Update code and dependencies to clean up Resource and Deprecation Warnings.
* Only depend on imgconverter extension for Sphinx >= 1.6
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-dns-cloudflare
* certbot-dns-digitalocean
* certbot-dns-google
* certbot-nginx
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/62?closed=1
## 0.28.0 - 2018-11-7
### Added
* `revoke` accepts `--cert-name`, and doesn't accept both `--cert-name` and `--cert-path`.
* Use the ACMEv2 newNonce endpoint when a new nonce is needed, and newNonce is available in the directory.
### Changed
* Removed documentation mentions of `#letsencrypt` IRC on Freenode.
* Write README to the base of (config-dir)/live directory
* `--manual` will explicitly warn users that earlier challenges should remain in place when setting up subsequent challenges.
* Warn when using deprecated acme.challenges.TLSSNI01
* Log warning about TLS-SNI deprecation in Certbot
* Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins
* OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies
* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds.
### Fixed
* Match Nginx parser update in allowing variable names to start with `${`.
* Fix ranking of vhosts in Nginx so that all port-matching vhosts come first
* Correct OVH integration tests on machines without internet access.
* Stop caching the results of ipv6_info in http01.py
* Test fix for Route53 plugin to prevent boto3 making outgoing connections.
* The grammar used by Augeas parser in Apache plugin was updated to fix various parsing errors.
* The CloudXNS, DNSimple, DNS Made Easy, Gehirn, Linode, LuaDNS, NS1, OVH, and
Sakura Cloud DNS plugins are now compatible with Lexicon 3.0+.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-dns-cloudxns
* certbot-dns-dnsimple
* certbot-dns-dnsmadeeasy
* certbot-dns-gehirn
* certbot-dns-linode
* certbot-dns-luadns
* certbot-dns-nsone
* certbot-dns-ovh
* certbot-dns-route53
* certbot-dns-sakuracloud
* certbot-nginx
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/59?closed=1
## 0.27.1 - 2018-09-06
### Fixed
* Fixed parameter name in OpenSUSE overrides for default parameters in the
Apache plugin. Certbot on OpenSUSE works again.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* certbot-apache
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/60?closed=1
## 0.27.0 - 2018-09-05
### Added
* The Apache plugin now accepts the parameter --apache-ctl which can be
used to configure the path to the Apache control script.
### Changed
* When using `acme.client.ClientV2` (or
`acme.client.BackwardsCompatibleClientV2` with an ACME server that supports a
newer version of the ACME protocol), an `acme.errors.ConflictError` will be
raised if you try to create an ACME account with a key that has already been
used. Previously, a JSON parsing error was raised in this scenario when using
the library with Let's Encrypt's ACMEv2 endpoint.
### Fixed
* When Apache is not installed, Certbot's Apache plugin no longer prints
messages about being unable to find apachectl to the terminal when the plugin
is not selected.
* If you're using the Apache plugin with the --apache-vhost-root flag set to a
directory containing a disabled virtual host for the domain you're requesting
a certificate for, the virtual host will now be temporarily enabled if
necessary to pass the HTTP challenge.
* The documentation for the Certbot package can now be built using Sphinx 1.6+.
* You can now call `query_registration` without having to first call
`new_account` on `acme.client.ClientV2` objects.
* The requirement of `setuptools>=1.0` has been removed from `certbot-dns-ovh`.
* Names in certbot-dns-sakuracloud's tests have been updated to refer to Sakura
Cloud rather than NS1 whose plugin certbot-dns-sakuracloud was based on.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
package with changes other than its version number was:
* acme
* certbot
* certbot-apache
* certbot-dns-ovh
* certbot-dns-sakuracloud
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/57?closed=1
## 0.26.1 - 2018-07-17
### Fixed
* Fix a bug that was triggered when users who had previously manually set `--server` to get ACMEv2 certs tried to renew ACMEv1 certs.
Despite us having broken lockstep, we are continuing to release new versions of all Certbot components during releases for the time being, however, the only package with changes other than its version number was:
* certbot
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/58?closed=1
## 0.26.0 - 2018-07-11
### Added
* A new security enhancement which we're calling AutoHSTS has been added to
Certbot's Apache plugin. This enhancement configures your webserver to send a
HTTP Strict Transport Security header with a low max-age value that is slowly
increased over time. The max-age value is not increased to a large value
until you've successfully managed to renew your certificate. This enhancement
can be requested with the --auto-hsts flag.
* New official DNS plugins have been created for Gehirn Infrastracture Service,
Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub
page at https://hub.docker.com/u/certbot and on PyPI.
* The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on
Let's Encrypt's ACMEv2 endpoint has been added.
* Certbot and its components now support Python 3.7.
* Certbot's install subcommand now allows you to interactively choose which
certificate to install from the list of certificates managed by Certbot.
* Certbot now accepts the flag `--no-autorenew` which causes any obtained
certificates to not be automatically renewed when it approaches expiration.
* Support for parsing the TLS-ALPN-01 challenge has been added back to the acme
library.
### Changed
* Certbot's default ACME server has been changed to Let's Encrypt's ACMEv2
endpoint. By default, this server will now be used for both new certificate
lineages and renewals.
* The Nginx plugin is no longer marked labeled as an "Alpha" version.
* The `prepare` method of Certbot's plugins is no longer called before running
"Updater" enhancements that are run on every invocation of `certbot renew`.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
packages with functional changes were:
* acme
* certbot
* certbot-apache
* certbot-dns-gehirn
* certbot-dns-linode
* certbot-dns-ovh
* certbot-dns-sakuracloud
* certbot-nginx
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/55?closed=1
## 0.25.1 - 2018-06-13
### Fixed
* TLS-ALPN-01 support has been removed from our acme library. Using our current
dependencies, we are unable to provide a correct implementation of this
challenge so we decided to remove it from the library until we can provide
proper support.
* Issues causing test failures when running the tests in the acme package with
pytest<3.0 has been resolved.
* certbot-nginx now correctly depends on acme>=0.25.0.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
packages with changes other than their version number were:
* acme
* certbot-nginx
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/56?closed=1
## 0.25.0 - 2018-06-06
### Added
* Support for the ready status type was added to acme. Without this change,
Certbot and acme users will begin encountering errors when using Let's
Encrypt's ACMEv2 API starting on June 19th for the staging environment and
July 5th for production. See
https://community.letsencrypt.org/t/acmev2-order-ready-status/62866 for more
information.
* Certbot now accepts the flag --reuse-key which will cause the same key to be
used in the certificate when the lineage is renewed rather than generating a
new key.
* You can now add multiple email addresses to your ACME account with Certbot by
providing a comma separated list of emails to the --email flag.
* Support for Let's Encrypt's upcoming TLS-ALPN-01 challenge was added to acme.
For more information, see
https://community.letsencrypt.org/t/tls-alpn-validation-method/63814/1.
* acme now supports specifying the source address to bind to when sending
outgoing connections. You still cannot specify this address using Certbot.
* If you run Certbot against Let's Encrypt's ACMEv2 staging server but don't
already have an account registered at that server URL, Certbot will
automatically reuse your staging account from Let's Encrypt's ACMEv1 endpoint
if it exists.
* Interfaces were added to Certbot allowing plugins to be called at additional
points. The `GenericUpdater` interface allows plugins to perform actions
every time `certbot renew` is run, regardless of whether any certificates are
due for renewal, and the `RenewDeployer` interface allows plugins to perform
actions when a certificate is renewed. See `certbot.interfaces` for more
information.
### Changed
* When running Certbot with --dry-run and you don't already have a staging
account, the created account does not contain an email address even if one
was provided to avoid expiration emails from Let's Encrypt's staging server.
* certbot-nginx does a better job of automatically detecting the location of
Nginx's configuration files when run on BSD based systems.
* acme now requires and uses pytest when running tests with setuptools with
`python setup.py test`.
* `certbot config_changes` no longer waits for user input before exiting.
### Fixed
* Misleading log output that caused users to think that Certbot's standalone
plugin failed to bind to a port when performing a challenge has been
corrected.
* An issue where certbot-nginx would fail to enable HSTS if the server block
already had an `add_header` directive has been resolved.
* certbot-nginx now does a better job detecting the server block to base the
configuration for TLS-SNI challenges on.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
packages with functional changes were:
* acme
* certbot
* certbot-apache
* certbot-nginx
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/54?closed=1
## 0.24.0 - 2018-05-02
### Added
* certbot now has an enhance subcommand which allows you to configure security
enhancements like HTTP to HTTPS redirects, OCSP stapling, and HSTS without
reinstalling a certificate.
* certbot-dns-rfc2136 now allows the user to specify the port to use to reach
the DNS server in its credentials file.
* acme now parses the wildcard field included in authorizations so it can be
used by users of the library.
### Changed
* certbot-dns-route53 used to wait for each DNS update to propagate before
sending the next one, but now it sends all updates before waiting which
speeds up issuance for multiple domains dramatically.
* Certbot's official Docker images are now based on Alpine Linux 3.7 rather
than 3.4 because 3.4 has reached its end-of-life.
* We've doubled the time Certbot will spend polling authorizations before
timing out.
* The level of the message logged when Certbot is being used with
non-standard paths warning that crontabs for renewal included in Certbot
packages from OS package managers may not work has been reduced. This stops
the message from being written to stderr every time `certbot renew` runs.
### Fixed
* certbot-auto now works with Python 3.6.
Despite us having broken lockstep, we are continuing to release new versions of
all Certbot components during releases for the time being, however, the only
packages with changes other than their version number were:
* acme
* certbot
* certbot-apache
* certbot-dns-digitalocean (only style improvements to tests)
* certbot-dns-rfc2136
More details about these changes can be found on our GitHub repo:
https://github.com/certbot/certbot/milestone/52?closed=1
## 0.23.0 - 2018-04-04
@@ -606,7 +1146,7 @@ https://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Ac
### Added
* When using the standalone plugin while running Certbot interactively
* When using the standalone 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

View File

@@ -1,8 +0,0 @@
ChangeLog
=========
To see the changes in a given release, view the issues closed in a given
release's GitHub milestone:
- `Past releases <https://github.com/certbot/certbot/milestones?state=closed>`_
- `Upcoming releases <https://github.com/certbot/certbot/milestones>`_

View File

@@ -1,17 +1,24 @@
FROM python:2-alpine3.7
FROM python:2-alpine3.9
ENTRYPOINT [ "certbot" ]
EXPOSE 80 443
VOLUME /etc/letsencrypt /var/lib/letsencrypt
WORKDIR /opt/certbot
COPY CHANGES.rst README.rst setup.py src/
COPY CHANGELOG.md README.rst setup.py src/
# Generate constraints file to pin dependency versions
COPY letsencrypt-auto-source/pieces/dependency-requirements.txt .
COPY tools /opt/certbot/tools
RUN sh -c 'cat dependency-requirements.txt | /opt/certbot/tools/strip_hashes.py > unhashed_requirements.txt'
RUN sh -c 'cat tools/dev_constraints.txt unhashed_requirements.txt | /opt/certbot/tools/merge_requirements.py > docker_constraints.txt'
COPY acme src/acme
COPY certbot src/certbot
RUN apk add --no-cache --virtual .certbot-deps \
libffi \
libssl1.0 \
libssl1.1 \
openssl \
ca-certificates \
binutils
@@ -21,7 +28,8 @@ RUN apk add --no-cache --virtual .build-deps \
openssl-dev \
musl-dev \
libffi-dev \
&& pip install --no-cache-dir \
&& pip install -r /opt/certbot/dependency-requirements.txt \
&& pip install --no-cache-dir --no-deps \
--editable /opt/certbot/src/acme \
--editable /opt/certbot/src \
&& apk del .build-deps

View File

@@ -16,6 +16,6 @@ RUN apt-get update && \
/tmp/* \
/var/tmp/*
RUN VENV_NAME="../venv" tools/venv.sh
RUN VENV_NAME="../venv" python tools/venv.py
ENV PATH /opt/certbot/venv/bin:$PATH

View File

@@ -34,7 +34,7 @@ RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only
# Dockerfile we make sure we cache as much as possible
COPY setup.py README.rst CHANGES.rst MANIFEST.in letsencrypt-auto-source/pieces/pipstrap.py /opt/certbot/src/
COPY setup.py README.rst CHANGELOG.md MANIFEST.in letsencrypt-auto-source/pieces/pipstrap.py /opt/certbot/src/
# all above files are necessary for setup.py and venv setup, however,
# package source code directory has to be copied separately to a
@@ -51,7 +51,7 @@ COPY certbot-apache /opt/certbot/src/certbot-apache/
COPY certbot-nginx /opt/certbot/src/certbot-nginx/
RUN virtualenv --no-site-packages -p python2 /opt/certbot/venv
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv
# PATH is set now so pipstrap upgrades the correct (v)env
ENV PATH /opt/certbot/venv/bin:$PATH

View File

@@ -1,5 +1,5 @@
include README.rst
include CHANGES.rst
include CHANGELOG.md
include CONTRIBUTING.md
include LICENSE.txt
include linter_plugin.py

View File

@@ -6,7 +6,7 @@ Anyone who has gone through the trouble of setting up a secure website knows wha
How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide <https://certbot.eff.org>`_. It generates instructions based on your configuration settings. In most cases, youll need `root or administrator access <https://certbot.eff.org/faq/#does-certbot-require-root-administrator-privileges>`_ to your web server to run Certbot.
If youre using a hosted service and dont have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Lets Encrypt.
Certbot is meant to be run directly on your web server, not on your personal computer. If youre using a hosted service and dont have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Lets Encrypt.
Certbot is a fully-featured, extensible client for the Let's
Encrypt CA (or any other CA that speaks the `ACME
@@ -91,8 +91,6 @@ Main Website: https://certbot.eff.org
Let's Encrypt Website: https://letsencrypt.org
IRC Channel: #letsencrypt on `Freenode`_
Community: https://community.letsencrypt.org
ACME spec: http://ietf-wg-acme.github.io/acme/
@@ -101,14 +99,12 @@ ACME working area in github: https://github.com/ietf-wg-acme/acme
|build-status| |coverage| |docs| |container|
.. _Freenode: https://webchat.freenode.net?channels=%23letsencrypt
.. |build-status| image:: https://travis-ci.org/certbot/certbot.svg?branch=master
:target: https://travis-ci.org/certbot/certbot
.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master
:target: https://travis-ci.com/certbot/certbot
:alt: Travis CI status
.. |coverage| image:: https://coveralls.io/repos/certbot/certbot/badge.svg?branch=master
:target: https://coveralls.io/r/certbot/certbot
.. |coverage| image:: https://codecov.io/gh/certbot/certbot/branch/master/graph/badge.svg
:target: https://codecov.io/gh/certbot/certbot
:alt: Coverage status
.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/

View File

@@ -1,5 +1,6 @@
include LICENSE.txt
include README.rst
include pytest.ini
recursive-include docs *
recursive-include examples *
recursive-include acme/testdata *

View File

@@ -1,12 +1,50 @@
"""ACME protocol implementation.
This module is an implementation of the `ACME protocol`_. Latest
supported version: `draft-ietf-acme-01`_.
This module is an implementation of the `ACME protocol`_.
.. _`ACME protocol`: https://ietf-wg-acme.github.io/acme
.. _`draft-ietf-acme-01`:
https://github.com/ietf-wg-acme/acme/tree/draft-ietf-acme-acme-01
"""
import sys
import warnings
# This code exists to keep backwards compatibility with people using acme.jose
# before it became the standalone josepy package.
#
# It is based on
# https://github.com/requests/requests/blob/1278ecdf71a312dc2268f3bfc0aabfab3c006dcf/requests/packages.py
import josepy as jose
for mod in list(sys.modules):
# This traversal is apparently necessary such that the identities are
# preserved (acme.jose.* is josepy.*)
if mod == 'josepy' or mod.startswith('josepy.'):
sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod]
# This class takes a similar approach to the cryptography project to deprecate attributes
# in public modules. See the _ModuleWithDeprecation class here:
# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129
class _TLSSNI01DeprecationModule(object):
"""
Internal class delegating to a module, and displaying warnings when
attributes related to TLS-SNI-01 are accessed.
"""
def __init__(self, module):
self.__dict__['_module'] = module
def __getattr__(self, attr):
if 'TLSSNI01' in attr:
warnings.warn('{0} attribute is deprecated, and will be removed soon.'.format(attr),
DeprecationWarning, stacklevel=2)
return getattr(self._module, attr)
def __setattr__(self, attr, value): # pragma: no cover
setattr(self._module, attr, value)
def __delattr__(self, attr): # pragma: no cover
delattr(self._module, attr)
def __dir__(self): # pragma: no cover
return ['_module'] + dir(self._module)

View File

@@ -4,15 +4,18 @@ import functools
import hashlib
import logging
import socket
import sys
from cryptography.hazmat.primitives import hashes # type: ignore
import josepy as jose
import OpenSSL
import requests
import six
from acme import errors
from acme import crypto_util
from acme import fields
from acme import _TLSSNI01DeprecationModule
logger = logging.getLogger(__name__)
@@ -106,6 +109,10 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
key_authorization = jose.Field("keyAuthorization")
thumbprint_hash_function = hashes.SHA256
def __init__(self, *args, **kwargs):
super(KeyAuthorizationChallengeResponse, self).__init__(*args, **kwargs)
self._dump_authorization_key(False)
def verify(self, chall, account_public_key):
"""Verify the key authorization.
@@ -138,17 +145,33 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
return True
def _dump_authorization_key(self, dump):
# type: (bool) -> None
"""
Set if keyAuthorization is dumped in the JSON representation of this ChallengeResponse.
NB: This method is declared as private because it will eventually be removed.
:param bool dump: True to dump the keyAuthorization, False otherwise
"""
object.__setattr__(self, '_dump_auth_key', dump)
def to_partial_json(self):
jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json()
if not self._dump_auth_key: # pylint: disable=no-member
jobj.pop('keyAuthorization', None)
return jobj
@six.add_metaclass(abc.ABCMeta)
class KeyAuthorizationChallenge(_TokenChallenge):
# pylint: disable=abstract-class-little-used,too-many-ancestors
"""Challenge based on Key Authorization.
:param response_cls: Subclass of `KeyAuthorizationChallengeResponse`
that will be used to generate `response`.
:param str typ: type of the challenge
"""
__metaclass__ = abc.ABCMeta
typ = NotImplemented
response_cls = NotImplemented
thumbprint_hash_function = (
KeyAuthorizationChallengeResponse.thumbprint_hash_function)
@@ -477,7 +500,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
try:
cert = self.probe_cert(domain=domain, **kwargs)
except errors.Error as error:
logger.debug(error, exc_info=True)
logger.debug(str(error), exc_info=True)
return False
return self.verify_cert(cert)
@@ -492,6 +515,9 @@ class TLSSNI01(KeyAuthorizationChallenge):
# boulder#962, ietf-wg-acme#22
#n = jose.Field("n", encoder=int, decoder=int)
def __init__(self, *args, **kwargs):
super(TLSSNI01, self).__init__(*args, **kwargs)
def validation(self, account_key, **kwargs):
"""Generate validation.
@@ -506,6 +532,33 @@ class TLSSNI01(KeyAuthorizationChallenge):
return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
@ChallengeResponse.register
class TLSALPN01Response(KeyAuthorizationChallengeResponse):
"""ACME TLS-ALPN-01 challenge response.
This class only allows initiating a TLS-ALPN-01 challenge returned from the
CA. Full support for responding to TLS-ALPN-01 challenges by generating and
serving the expected response certificate is not currently provided.
"""
typ = "tls-alpn-01"
@Challenge.register # pylint: disable=too-many-ancestors
class TLSALPN01(KeyAuthorizationChallenge):
"""ACME tls-alpn-01 challenge.
This class simply allows parsing the TLS-ALPN-01 challenge returned from
the CA. Full TLS-ALPN-01 support is not currently provided.
"""
typ = "tls-alpn-01"
response_cls = TLSALPN01Response
def validation(self, account_key, **kwargs):
"""Generate validation for the challenge."""
raise NotImplementedError()
@Challenge.register # pylint: disable=too-many-ancestors
class DNS(_TokenChallenge):
"""ACME "dns" challenge."""
@@ -587,3 +640,7 @@ class DNSResponse(ChallengeResponse):
"""
return chall.check_validation(self.validation, account_public_key)
# Patching ourselves to warn about TLS-SNI challenge deprecation and removal.
sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__])

View File

@@ -93,6 +93,9 @@ class DNS01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.msg.to_partial_json())
self.msg._dump_authorization_key(True) # pylint: disable=protected-access
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
@@ -164,6 +167,9 @@ class HTTP01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.msg.to_partial_json())
self.msg._dump_authorization_key(True) # pylint: disable=protected-access
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
@@ -284,6 +290,9 @@ class TLSSNI01ResponseTest(unittest.TestCase):
self.assertEqual(self.z_domain, self.response.z_domain)
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.response.to_partial_json())
self.response._dump_authorization_key(True) # pylint: disable=protected-access
self.assertEqual(self.jmsg, self.response.to_partial_json())
def test_from_json(self):
@@ -360,13 +369,13 @@ class TLSSNI01ResponseTest(unittest.TestCase):
class TLSSNI01Test(unittest.TestCase):
def setUp(self):
from acme.challenges import TLSSNI01
self.msg = TLSSNI01(
token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e'))
self.jmsg = {
'type': 'tls-sni-01',
'token': 'a82d5ff8ef740d12881f6d3c2277ab2e',
}
from acme.challenges import TLSSNI01
self.msg = TLSSNI01(
token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e'))
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
@@ -392,6 +401,76 @@ class TLSSNI01Test(unittest.TestCase):
KEY, cert_key=mock.sentinel.cert_key))
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
def test_deprecation_message(self):
with mock.patch('acme.warnings.warn') as mock_warn:
from acme.challenges import TLSSNI01
assert TLSSNI01
self.assertEqual(mock_warn.call_count, 1)
self.assertTrue('deprecated' in mock_warn.call_args[0][0])
class TLSALPN01ResponseTest(unittest.TestCase):
# pylint: disable=too-many-instance-attributes
def setUp(self):
from acme.challenges import TLSALPN01Response
self.msg = TLSALPN01Response(key_authorization=u'foo')
self.jmsg = {
'resource': 'challenge',
'type': 'tls-alpn-01',
'keyAuthorization': u'foo',
}
from acme.challenges import TLSALPN01
self.chall = TLSALPN01(token=(b'x' * 16))
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.msg.to_partial_json())
self.msg._dump_authorization_key(True) # pylint: disable=protected-access
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import TLSALPN01Response
self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import TLSALPN01Response
hash(TLSALPN01Response.from_json(self.jmsg))
class TLSALPN01Test(unittest.TestCase):
def setUp(self):
from acme.challenges import TLSALPN01
self.msg = TLSALPN01(
token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e'))
self.jmsg = {
'type': 'tls-alpn-01',
'token': 'a82d5ff8ef740d12881f6d3c2277ab2e',
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
def test_from_json(self):
from acme.challenges import TLSALPN01
self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import TLSALPN01
hash(TLSALPN01.from_json(self.jmsg))
def test_from_json_invalid_token_length(self):
from acme.challenges import TLSALPN01
self.jmsg['token'] = jose.encode_b64jose(b'abcd')
self.assertRaises(
jose.DeserializationError, TLSALPN01.from_json, self.jmsg)
def test_validation(self):
self.assertRaises(NotImplementedError, self.msg.validation, KEY)
class DNSTest(unittest.TestCase):

View File

@@ -9,17 +9,21 @@ import time
import six
from six.moves import http_client # pylint: disable=import-error
import josepy as jose
import OpenSSL
import re
from requests_toolbelt.adapters.source import SourceAddressAdapter
import requests
from requests.adapters import HTTPAdapter
import sys
from acme import challenges
from acme import crypto_util
from acme import errors
from acme import jws
from acme import messages
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Dict, List, Set, Text
logger = logging.getLogger(__name__)
@@ -30,6 +34,7 @@ logger = logging.getLogger(__name__)
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
if sys.version_info < (2, 7, 9): # pragma: no cover
try:
# pylint: disable=no-member
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore
except AttributeError:
import urllib3.contrib.pyopenssl # pylint: disable=import-error
@@ -47,7 +52,6 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
:ivar .ClientNetwork net: Client network.
:ivar int acme_version: ACME protocol version. 1 or 2.
"""
def __init__(self, directory, net, acme_version):
"""Initialize.
@@ -87,6 +91,8 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
"""
kwargs.setdefault('acme_version', self.acme_version)
if hasattr(self.directory, 'newNonce'):
kwargs.setdefault('new_nonce_url', getattr(self.directory, 'newNonce'))
return self.net.post(*args, **kwargs)
def update_registration(self, regr, update=None):
@@ -150,7 +156,23 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
:raises .UnexpectedUpdate:
"""
response = self._post(challb.uri, response)
# Because sending keyAuthorization in a response challenge has been removed from the ACME
# spec, it is not included in the KeyAuthorizationResponseChallenge JSON by default.
# However as a migration path, we temporarily expect a malformed error from the server,
# and fallback by resending the challenge response with the keyAuthorization field.
# TODO: Remove this fallback for Certbot 0.34.0
try:
response = self._post(challb.uri, response)
except messages.Error as error:
if (error.code == 'malformed'
and isinstance(response, challenges.KeyAuthorizationChallengeResponse)):
logger.debug('Error while responding to a challenge without keyAuthorization '
'in the JWS, your ACME CA server may not support it:\n%s', error)
logger.debug('Retrying request with keyAuthorization set.')
response._dump_authorization_key(True) # pylint: disable=protected-access
response = self._post(challb.uri, response)
else:
raise
try:
authzr_uri = response.links['up']['url']
except KeyError:
@@ -195,22 +217,6 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
return datetime.datetime.now() + datetime.timedelta(seconds=seconds)
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self.net.get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri)
return updated_authzr, response
def _revoke(self, cert, rsn, url):
"""Revoke certificate.
@@ -232,6 +238,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
raise errors.ClientError(
'Successful revocation must return HTTP OK status')
class Client(ClientBase):
"""ACME client for a v1 API.
@@ -384,6 +391,22 @@ class Client(ClientBase):
body=jose.ComparableX509(OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_ASN1, response.content)))
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self.net.get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri)
return updated_authzr, response
def poll_and_request_issuance(
self, csr, authzrs, mintime=5, max_attempts=10):
"""Poll and request issuance.
@@ -415,7 +438,7 @@ class Client(ClientBase):
"""
# pylint: disable=too-many-locals
assert max_attempts > 0
attempts = collections.defaultdict(int)
attempts = collections.defaultdict(int) # type: Dict[messages.AuthorizationResource, int]
exhausted = set()
# priority queue with datetime.datetime (based on Retry-After) as key,
@@ -529,7 +552,7 @@ class Client(ClientBase):
:rtype: `list` of `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
"""
chain = []
chain = [] # type: List[jose.ComparableX509]
uri = certr.cert_chain_uri
while uri is not None and len(chain) < max_length:
response, cert = self._get_cert(uri)
@@ -575,16 +598,57 @@ class ClientV2(ClientBase):
:param .NewRegistration new_account:
:raises .ConflictError: in case the account already exists
:returns: Registration Resource.
:rtype: `.RegistrationResource`
"""
response = self._post(self.directory['newAccount'], new_account)
# if account already exists
if response.status_code == 200 and 'Location' in response.headers:
raise errors.ConflictError(response.headers.get('Location'))
# "Instance of 'Field' has no key/contact member" bug:
# pylint: disable=no-member
regr = self._regr_from_response(response)
self.net.account = regr
return regr
def query_registration(self, regr):
"""Query server about registration.
:param messages.RegistrationResource: Existing Registration
Resource.
"""
self.net.account = regr
updated_regr = super(ClientV2, self).query_registration(regr)
self.net.account = updated_regr
return updated_regr
def update_registration(self, regr, update=None):
"""Update registration.
:param messages.RegistrationResource regr: Registration Resource.
:param messages.Registration update: Updated body of the
resource. If not provided, body will be taken from `regr`.
:returns: Updated Registration Resource.
:rtype: `.RegistrationResource`
"""
# https://github.com/certbot/certbot/issues/6155
new_regr = self._get_v2_account(regr)
return super(ClientV2, self).update_registration(new_regr, update)
def _get_v2_account(self, regr):
self.net.account = None
only_existing_reg = regr.body.update(only_return_existing=True)
response = self._post(self.directory['newAccount'], only_existing_reg)
updated_uri = response.headers['Location']
new_regr = regr.update(uri=updated_uri)
self.net.account = new_regr
return new_regr
def new_order(self, csr_pem):
"""Request a new Order object from the server.
@@ -606,13 +670,29 @@ class ClientV2(ClientBase):
body = messages.Order.from_json(response.json())
authorizations = []
for url in body.authorizations:
authorizations.append(self._authzr_from_response(self.net.get(url), uri=url))
authorizations.append(self._authzr_from_response(self._post_as_get(url), uri=url))
return messages.OrderResource(
body=body,
uri=response.headers.get('Location'),
authorizations=authorizations,
csr_pem=csr_pem)
def poll(self, authzr):
"""Poll Authorization Resource for status.
:param authzr: Authorization Resource
:type authzr: `.AuthorizationResource`
:returns: Updated Authorization Resource and HTTP response.
:rtype: (`.AuthorizationResource`, `requests.Response`)
"""
response = self._post_as_get(authzr.uri)
updated_authzr = self._authzr_from_response(
response, authzr.body.identifier, authzr.uri)
return updated_authzr, response
def poll_and_finalize(self, orderr, deadline=None):
"""Poll authorizations and finalize the order.
@@ -636,7 +716,7 @@ class ClientV2(ClientBase):
responses = []
for url in orderr.body.authorizations:
while datetime.datetime.now() < deadline:
authzr = self._authzr_from_response(self.net.get(url), uri=url)
authzr = self._authzr_from_response(self._post_as_get(url), uri=url)
if authzr.body.status != messages.STATUS_PENDING:
responses.append(authzr)
break
@@ -671,13 +751,12 @@ class ClientV2(ClientBase):
self._post(orderr.body.finalize, wrapped_csr)
while datetime.datetime.now() < deadline:
time.sleep(1)
response = self.net.get(orderr.uri)
response = self._post_as_get(orderr.uri)
body = messages.Order.from_json(response.json())
if body.error is not None:
raise errors.IssuanceError(body.error)
if body.certificate is not None:
certificate_response = self.net.get(body.certificate,
content_type=DER_CONTENT_TYPE).text
certificate_response = self._post_as_get(body.certificate).text
return orderr.update(body=body, fullchain_pem=certificate_response)
raise errors.TimeoutError()
@@ -694,6 +773,39 @@ class ClientV2(ClientBase):
"""
return self._revoke(cert, rsn, self.directory['revokeCert'])
def external_account_required(self):
"""Checks if ACME server requires External Account Binding authentication."""
if hasattr(self.directory, 'meta') and self.directory.meta.external_account_required:
return True
else:
return False
def _post_as_get(self, *args, **kwargs):
"""
Send GET request using the POST-as-GET protocol if needed.
The request will be first issued using POST-as-GET for ACME v2. If the ACME CA servers do
not support this yet and return an error, request will be retried using GET.
For ACME v1, only GET request will be tried, as POST-as-GET is not supported.
:param args:
:param kwargs:
:return:
"""
if self.acme_version >= 2:
# We add an empty payload for POST-as-GET requests
new_args = args[:1] + (None,) + args[1:]
try:
return self._post(*new_args, **kwargs) # pylint: disable=star-args
except messages.Error as error:
if error.code == 'malformed':
logger.debug('Error during a POST-as-GET request, '
'your ACME CA server may not support it:\n%s', error)
logger.debug('Retrying request with GET.')
else: # pragma: no cover
raise
# If POST-as-GET is not supported yet, we use a GET instead.
return self.net.get(*args, **kwargs)
class BackwardsCompatibleClientV2(object):
"""ACME client wrapper that tends towards V2-style calls, but
@@ -723,12 +835,7 @@ class BackwardsCompatibleClientV2(object):
self.client = ClientV2(directory, net=net)
def __getattr__(self, name):
if name in vars(self.client):
return getattr(self.client, name)
elif name in dir(ClientBase):
return getattr(self.client, name)
else:
raise AttributeError()
return getattr(self.client, name)
def new_account_and_tos(self, regr, check_tos_cb=None):
"""Combined register and agree_tos for V1, new_account for V2
@@ -835,6 +942,15 @@ class BackwardsCompatibleClientV2(object):
else:
return 1
def external_account_required(self):
"""Checks if the server requires an external account for ACMEv2 servers.
Always return False for ACMEv1 servers, as it doesn't use External Account Binding."""
if self.acme_version == 1:
return False
else:
return self.client.external_account_required()
class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
"""Wrapper around requests that signs POSTs for authentication.
@@ -856,18 +972,28 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
:param bool verify_ssl: Whether to verify certificates on SSL connections.
:param str user_agent: String to send as User-Agent header.
:param float timeout: Timeout for requests.
:param source_address: Optional source address to bind to when making requests.
:type source_address: str or tuple(str, int)
"""
def __init__(self, key, account=None, alg=jose.RS256, verify_ssl=True,
user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT):
user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT,
source_address=None):
# pylint: disable=too-many-arguments
self.key = key
self.account = account
self.alg = alg
self.verify_ssl = verify_ssl
self._nonces = set()
self._nonces = set() # type: Set[Text]
self.user_agent = user_agent
self.session = requests.Session()
self._default_timeout = timeout
adapter = HTTPAdapter()
if source_address is not None:
adapter = SourceAddressAdapter(source_address)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def __del__(self):
# Try to close the session, but don't show exceptions to the
@@ -888,7 +1014,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
:rtype: `josepy.JWS`
"""
jobj = obj.json_dumps(indent=2).encode()
jobj = obj.json_dumps(indent=2).encode() if obj else b''
logger.debug('JWS payload:\n%s', jobj)
kwargs = {
"alg": self.alg,
@@ -897,6 +1023,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
if acme_version == 2:
kwargs["url"] = url
# newAccount and revokeCert work without the kid
# newAccount must not have kid
if self.account is not None:
kwargs["kid"] = self.account["uri"]
kwargs["key"] = self.key
@@ -1017,7 +1144,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
if response.headers.get("Content-Type") == DER_CONTENT_TYPE:
debug_content = base64.b64encode(response.content)
else:
debug_content = response.content
debug_content = response.content.decode("utf-8")
logger.debug('Received response:\nHTTP %d\n%s\n\n%s',
response.status_code,
"\n".join(["{0}: {1}".format(k, v)
@@ -1052,10 +1179,15 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
else:
raise errors.MissingNonce(response)
def _get_nonce(self, url):
def _get_nonce(self, url, new_nonce_url):
if not self._nonces:
logger.debug('Requesting fresh nonce')
self._add_nonce(self.head(url))
if new_nonce_url is None:
response = self.head(url)
else:
# request a new nonce from the acme newNonce endpoint
response = self._check_response(self.head(new_nonce_url), content_type=None)
self._add_nonce(response)
return self._nonces.pop()
def post(self, *args, **kwargs):
@@ -1076,8 +1208,10 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE,
acme_version=1, **kwargs):
data = self._wrap_in_jws(obj, self._get_nonce(url), url, acme_version)
new_nonce_url = kwargs.pop('new_nonce_url', None)
data = self._wrap_in_jws(obj, self._get_nonce(url, new_nonce_url), url, acme_version)
kwargs.setdefault('headers', {'Content-Type': content_type})
response = self._send_request('POST', url, data=data, **kwargs)
response = self._check_response(response, content_type=content_type)
self._add_nonce(response)
return self._check_response(response, content_type=content_type)
return response

View File

@@ -1,4 +1,5 @@
"""Tests for acme.client."""
# pylint: disable=too-many-lines
import copy
import datetime
import json
@@ -17,6 +18,7 @@ from acme import jws as acme_jws
from acme import messages
from acme import messages_test
from acme import test_util
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
CERT_DER = test_util.load_vector('cert.der')
@@ -61,7 +63,8 @@ class ClientTestBase(unittest.TestCase):
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
reg = messages.Registration(
contact=self.contact, key=KEY.public_key())
self.new_reg = messages.NewRegistration(**dict(reg))
the_arg = dict(reg) # type: Dict
self.new_reg = messages.NewRegistration(**the_arg) # pylint: disable=star-args
self.regr = messages.RegistrationResource(
body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1')
@@ -132,12 +135,18 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
client = self._init()
self.assertEqual(client.acme_version, 2)
def test_query_registration_client_v2(self):
self.response.json.return_value = DIRECTORY_V2.to_json()
client = self._init()
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, client.query_registration(self.regr))
def test_forwarding(self):
self.response.json.return_value = DIRECTORY_V1.to_json()
client = self._init()
self.assertEqual(client.directory, client.client.directory)
self.assertEqual(client.key, KEY)
self.assertEqual(client.update_registration, client.client.update_registration)
self.assertEqual(client.deactivate_registration, client.client.deactivate_registration)
self.assertRaises(AttributeError, client.__getattr__, 'nonexistent')
self.assertRaises(AttributeError, client.__getattr__, 'new_account_and_tos')
self.assertRaises(AttributeError, client.__getattr__, 'new_account')
@@ -268,6 +277,44 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
client.revoke(messages_test.CERT, self.rsn)
mock_client().revoke.assert_called_once_with(messages_test.CERT, self.rsn)
def test_update_registration(self):
self.response.json.return_value = DIRECTORY_V1.to_json()
with mock.patch('acme.client.Client') as mock_client:
client = self._init()
client.update_registration(mock.sentinel.regr, None)
mock_client().update_registration.assert_called_once_with(mock.sentinel.regr, None)
# newNonce present means it will pick acme_version 2
def test_external_account_required_true(self):
self.response.json.return_value = messages.Directory({
'newNonce': 'http://letsencrypt-test.com/acme/new-nonce',
'meta': messages.Directory.Meta(external_account_required=True),
}).to_json()
client = self._init()
self.assertTrue(client.external_account_required())
# newNonce present means it will pick acme_version 2
def test_external_account_required_false(self):
self.response.json.return_value = messages.Directory({
'newNonce': 'http://letsencrypt-test.com/acme/new-nonce',
'meta': messages.Directory.Meta(external_account_required=False),
}).to_json()
client = self._init()
self.assertFalse(client.external_account_required())
def test_external_account_required_false_v1(self):
self.response.json.return_value = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=False),
}).to_json()
client = self._init()
self.assertFalse(client.external_account_required())
class ClientTest(ClientTestBase):
"""Tests for acme.client.Client."""
@@ -416,6 +463,34 @@ class ClientTest(ClientTestBase):
errors.ClientError, self.client.answer_challenge,
self.challr.body, challenges.DNSResponse(validation=None))
def test_answer_challenge_key_authorization_fallback(self):
self.response.links['up'] = {'url': self.challr.authzr_uri}
self.response.json.return_value = self.challr.body.to_json()
def _wrapper_post(url, obj, *args, **kwargs): # pylint: disable=unused-argument
"""
Simulate an old ACME CA server, that would respond a 'malformed'
error if keyAuthorization is missing.
"""
jobj = obj.to_partial_json()
if 'keyAuthorization' not in jobj:
raise messages.Error.with_code('malformed')
return self.response
self.net.post.side_effect = _wrapper_post
# This challenge response is of type KeyAuthorizationChallengeResponse, so the fallback
# should be triggered, and avoid an exception.
http_chall_response = challenges.HTTP01Response(key_authorization='test',
resource=mock.MagicMock())
self.client.answer_challenge(self.challr.body, http_chall_response)
# This challenge response is not of type KeyAuthorizationChallengeResponse, so the fallback
# should not be triggered, leading to an exception.
dns_chall_response = challenges.DNSResponse(validation=None)
self.assertRaises(
errors.Error, self.client.answer_challenge,
self.challr.body, dns_chall_response)
def test_retry_after_date(self):
self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT'
self.assertEqual(
@@ -650,7 +725,7 @@ class ClientTest(ClientTestBase):
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'])
self.assertEqual(self.rsn, obj.to_partial_json()['reason'])
def test_revoke_bad_status_raises_error(self):
self.response.status_code = http_client.METHOD_NOT_ALLOWED
@@ -660,6 +735,7 @@ class ClientTest(ClientTestBase):
self.certr,
self.rsn)
class ClientV2Test(ClientTestBase):
"""Tests for acme.client.ClientV2."""
@@ -697,6 +773,11 @@ class ClientV2Test(ClientTestBase):
self.assertEqual(self.regr, self.client.new_account(self.new_reg))
def test_new_account_conflict(self):
self.response.status_code = http_client.OK
self.response.headers['Location'] = self.regr.uri
self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg)
def test_new_order(self):
order_response = copy.deepcopy(self.response)
order_response.status_code = http_client.CREATED
@@ -710,9 +791,10 @@ class ClientV2Test(ClientTestBase):
authz_response2 = self.response
authz_response2.json.return_value = self.authz2.to_json()
authz_response2.headers['Location'] = self.authzr2.uri
self.net.get.side_effect = (authz_response, authz_response2)
self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr)
with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get:
mock_post_as_get.side_effect = (authz_response, authz_response2)
self.assertEqual(self.client.new_order(CSR_SAN_PEM), self.orderr)
@mock.patch('acme.client.datetime')
def test_poll_and_finalize(self, mock_datetime):
@@ -785,7 +867,62 @@ class ClientV2Test(ClientTestBase):
def test_revoke(self):
self.client.revoke(messages_test.CERT, self.rsn)
self.net.post.assert_called_once_with(
self.directory["revokeCert"], mock.ANY, acme_version=2)
self.directory["revokeCert"], mock.ANY, acme_version=2,
new_nonce_url=DIRECTORY_V2['newNonce'])
def test_update_registration(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, self.client.update_registration(self.regr))
self.assertNotEqual(self.client.net.account, None)
self.assertEqual(self.client.net.post.call_count, 2)
self.assertTrue(DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0])
self.response.json.return_value = self.regr.body.update(
contact=()).to_json()
def test_external_account_required_true(self):
self.client.directory = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=True)
})
self.assertTrue(self.client.external_account_required())
def test_external_account_required_false(self):
self.client.directory = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=False)
})
self.assertFalse(self.client.external_account_required())
def test_external_account_required_default(self):
self.assertFalse(self.client.external_account_required())
def test_post_as_get(self):
with mock.patch('acme.client.ClientV2._authzr_from_response') as mock_client:
mock_client.return_value = self.authzr2
self.client.poll(self.authzr2) # pylint: disable=protected-access
self.client.net.post.assert_called_once_with(
self.authzr2.uri, None, acme_version=2,
new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce')
self.client.net.get.assert_not_called()
class FakeError(messages.Error): # pylint: disable=too-many-ancestors
"""Fake error to reproduce a malformed request ACME error"""
def __init__(self): # pylint: disable=super-init-not-called
pass
@property
def code(self):
return 'malformed'
self.client.net.post.side_effect = FakeError()
self.client.poll(self.authzr2) # pylint: disable=protected-access
self.client.net.get.assert_called_once_with(self.authzr2.uri)
class MockJSONDeSerializable(jose.JSONDeSerializable):
@@ -842,7 +979,6 @@ class ClientNetworkTest(unittest.TestCase):
self.assertEqual(jws.signature.combined.kid, u'acct-uri')
self.assertEqual(jws.signature.combined.url, u'url')
def test_check_response_not_ok_jobj_no_error(self):
self.response.ok = False
self.response.json.return_value = {}
@@ -1005,8 +1141,8 @@ class ClientNetworkTest(unittest.TestCase):
# Requests Library Exceptions
except requests.exceptions.ConnectionError as z: #pragma: no cover
self.assertEqual("('Connection aborted.', "
"error(111, 'Connection refused'))", str(z))
self.assertTrue("'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z))
class ClientNetworkWithMockedResponseTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork which mock out response."""
@@ -1019,7 +1155,10 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.response = mock.MagicMock(ok=True, status_code=http_client.OK)
self.response.headers = {}
self.response.links = {}
self.checked_response = mock.MagicMock()
self.response.checked = False
self.acmev1_nonce_response = mock.MagicMock(ok=False,
status_code=http_client.METHOD_NOT_ALLOWED)
self.acmev1_nonce_response.headers = {}
self.obj = mock.MagicMock()
self.wrapped_obj = mock.MagicMock()
self.content_type = mock.sentinel.content_type
@@ -1031,13 +1170,21 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
def send_request(*args, **kwargs):
# pylint: disable=unused-argument,missing-docstring
self.assertFalse("new_nonce_url" in kwargs)
method = args[0]
uri = args[1]
if method == 'HEAD' and uri != "new_nonce_uri":
response = self.acmev1_nonce_response
else:
response = self.response
if self.available_nonces:
self.response.headers = {
response.headers = {
self.net.REPLAY_NONCE_HEADER:
self.available_nonces.pop().decode()}
else:
self.response.headers = {}
return self.response
response.headers = {}
return response
# pylint: disable=protected-access
self.net._send_request = self.send_request = mock.MagicMock(
@@ -1049,28 +1196,39 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
# pylint: disable=missing-docstring
self.assertEqual(self.response, response)
self.assertEqual(self.content_type, content_type)
return self.checked_response
self.assertTrue(self.response.ok)
self.response.checked = True
return self.response
def test_head(self):
self.assertEqual(self.response, self.net.head(
self.assertEqual(self.acmev1_nonce_response, self.net.head(
'http://example.com/', 'foo', bar='baz'))
self.send_request.assert_called_once_with(
'HEAD', 'http://example.com/', 'foo', bar='baz')
def test_head_v2(self):
self.assertEqual(self.response, self.net.head(
'new_nonce_uri', 'foo', bar='baz'))
self.send_request.assert_called_once_with(
'HEAD', 'new_nonce_uri', 'foo', bar='baz')
def test_get(self):
self.assertEqual(self.checked_response, self.net.get(
self.assertEqual(self.response, self.net.get(
'http://example.com/', content_type=self.content_type, bar='baz'))
self.assertTrue(self.response.checked)
self.send_request.assert_called_once_with(
'GET', 'http://example.com/', bar='baz')
def test_post_no_content_type(self):
self.content_type = self.net.JOSE_CONTENT_TYPE
self.assertEqual(self.checked_response, self.net.post('uri', self.obj))
self.assertEqual(self.response, self.net.post('uri', self.obj))
self.assertTrue(self.response.checked)
def test_post(self):
# pylint: disable=protected-access
self.assertEqual(self.checked_response, self.net.post(
self.assertEqual(self.response, self.net.post(
'uri', self.obj, content_type=self.content_type))
self.assertTrue(self.response.checked)
self.net._wrap_in_jws.assert_called_once_with(
self.obj, jose.b64decode(self.all_nonces.pop()), "uri", 1)
@@ -1102,7 +1260,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
def test_post_not_retried(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('malformed'),
self.checked_response]
self.response]
# pylint: disable=protected-access
self.net._check_response = check_response
@@ -1110,13 +1268,12 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
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]
post_once = mock.MagicMock()
post_once.side_effect = [messages.Error.with_code('badNonce'),
self.response]
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertEqual(self.checked_response, self.net.post(
self.assertEqual(self.response, self.net.post(
'uri', self.obj, content_type=self.content_type))
def test_head_get_post_error_passthrough(self):
@@ -1127,6 +1284,51 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.assertRaises(requests.exceptions.RequestException,
self.net.post, 'uri', obj=self.obj)
def test_post_bad_nonce_head(self):
# pylint: disable=protected-access
# regression test for https://github.com/certbot/certbot/issues/6092
bad_response = mock.MagicMock(ok=False, status_code=http_client.SERVICE_UNAVAILABLE)
self.net._send_request = mock.MagicMock()
self.net._send_request.return_value = bad_response
self.content_type = None
check_response = mock.MagicMock()
self.net._check_response = check_response
self.assertRaises(errors.ClientError, self.net.post, 'uri',
self.obj, content_type=self.content_type, acme_version=2,
new_nonce_url='new_nonce_uri')
self.assertEqual(check_response.call_count, 1)
def test_new_nonce_uri_removed(self):
self.content_type = None
self.net.post('uri', self.obj, content_type=None,
acme_version=2, new_nonce_url='new_nonce_uri')
class ClientNetworkSourceAddressBindingTest(unittest.TestCase):
"""Tests that if ClientNetwork has a source IP set manually, the underlying library has
used the provided source address."""
def setUp(self):
self.source_address = "8.8.8.8"
def test_source_address_set(self):
from acme.client import ClientNetwork
net = ClientNetwork(key=None, alg=None, source_address=self.source_address)
for adapter in net.session.adapters.values():
self.assertTrue(self.source_address in adapter.source_address)
def test_behavior_assumption(self):
"""This is a test that guardrails the HTTPAdapter behavior so that if the default for
a Session() changes, the assumptions here aren't violated silently."""
from acme.client import ClientNetwork
# Source address not specified, so the default adapter type should be bound -- this
# test should fail if the default adapter type is changed by requests
net = ClientNetwork(key=None, alg=None)
session = requests.Session()
for scheme in session.adapters.keys():
client_network_adapter = net.session.adapters.get(scheme)
default_adapter = session.adapters.get(scheme)
self.assertEqual(client_network_adapter.__class__, default_adapter.__class__)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -6,26 +6,26 @@ import os
import re
import socket
import OpenSSL
from OpenSSL import crypto
from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
import josepy as jose
from acme import errors
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Callable, Union, Tuple, Optional
# pylint: enable=unused-import, no-name-in-module
logger = logging.getLogger(__name__)
# TLSSNI01 certificate serving and probing is not affected by SSL
# vulnerabilities: prober needs to check certificate for expected
# contents anyway. Working SNI is the only thing that's necessary for
# the challenge and thus scoping down SSL/TLS method (version) would
# cause interoperability issues: TLSv1_METHOD is only compatible with
# Default SSL method selected here is the most compatible, while secure
# SSL method: TLSv1_METHOD is only compatible with
# TLSv1_METHOD, while SSLv23_METHOD is compatible with all other
# methods, including TLSv2_METHOD (read more at
# https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
# should be changed to use "set_options" to disable SSLv2 and SSLv3,
# in case it's used for things other than probing/serving!
_DEFAULT_TLSSNI01_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD # type: ignore
_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
class SSLSocket(object): # pylint: disable=too-few-public-methods
@@ -37,7 +37,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
"""
def __init__(self, sock, certs, method=_DEFAULT_TLSSNI01_SSL_METHOD):
def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD):
self.sock = sock
self.certs = certs
self.method = method
@@ -64,9 +64,9 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
logger.debug("Server name (%s) not recognized, dropping SSL",
server_name)
return
new_context = OpenSSL.SSL.Context(self.method)
new_context.set_options(OpenSSL.SSL.OP_NO_SSLv2)
new_context.set_options(OpenSSL.SSL.OP_NO_SSLv3)
new_context = SSL.Context(self.method)
new_context.set_options(SSL.OP_NO_SSLv2)
new_context.set_options(SSL.OP_NO_SSLv3)
new_context.use_privatekey(key)
new_context.use_certificate(cert)
connection.set_context(new_context)
@@ -89,18 +89,18 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
def accept(self): # pylint: disable=missing-docstring
sock, addr = self.sock.accept()
context = OpenSSL.SSL.Context(self.method)
context.set_options(OpenSSL.SSL.OP_NO_SSLv2)
context.set_options(OpenSSL.SSL.OP_NO_SSLv3)
context = SSL.Context(self.method)
context.set_options(SSL.OP_NO_SSLv2)
context.set_options(SSL.OP_NO_SSLv3)
context.set_tlsext_servername_callback(self._pick_certificate_cb)
ssl_sock = self.FakeConnection(OpenSSL.SSL.Connection(context, sock))
ssl_sock = self.FakeConnection(SSL.Connection(context, sock))
ssl_sock.set_accept_state()
logger.debug("Performing handshake with %s", addr)
try:
ssl_sock.do_handshake()
except OpenSSL.SSL.Error as error:
except SSL.Error as error:
# _pick_certificate_cb might have returned without
# creating SSL context (wrong server name)
raise socket.error(error)
@@ -109,7 +109,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
def probe_sni(name, host, port=443, timeout=300,
method=_DEFAULT_TLSSNI01_SSL_METHOD, source_address=('', 0)):
method=_DEFAULT_SSL_METHOD, source_address=('', 0)):
"""Probe SNI server for SSL certificate.
:param bytes name: Byte string to send as the server name in the
@@ -128,30 +128,33 @@ def probe_sni(name, host, port=443, timeout=300,
:rtype: OpenSSL.crypto.X509
"""
context = OpenSSL.SSL.Context(method)
context = SSL.Context(method)
context.set_timeout(timeout)
socket_kwargs = {'source_address': source_address}
host_protocol_agnostic = None if host == '::' or host == '0' else host
try:
# pylint: disable=star-args
logger.debug("Attempting to connect to %s:%d%s.", host_protocol_agnostic, port,
" from {0}:{1}".format(source_address[0], source_address[1]) if \
socket_kwargs else "")
sock = socket.create_connection((host_protocol_agnostic, port), **socket_kwargs)
logger.debug(
"Attempting to connect to %s:%d%s.", host, port,
" from {0}:{1}".format(
source_address[0],
source_address[1]
) if socket_kwargs else ""
)
socket_tuple = (host, port) # type: Tuple[str, int]
sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore
except socket.error as error:
raise errors.Error(error)
with contextlib.closing(sock) as client:
client_ssl = OpenSSL.SSL.Connection(context, client)
client_ssl = SSL.Connection(context, client)
client_ssl.set_connect_state()
client_ssl.set_tlsext_host_name(name) # pyOpenSSL>=0.13
try:
client_ssl.do_handshake()
client_ssl.shutdown()
except OpenSSL.SSL.Error as error:
except SSL.Error as error:
raise errors.Error(error)
return client_ssl.get_peer_certificate()
@@ -164,18 +167,18 @@ def make_csr(private_key_pem, domains, must_staple=False):
OCSP Must Staple: https://tools.ietf.org/html/rfc7633).
:returns: buffer PEM-encoded Certificate Signing Request.
"""
private_key = OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM, private_key_pem)
csr = OpenSSL.crypto.X509Req()
private_key = crypto.load_privatekey(
crypto.FILETYPE_PEM, private_key_pem)
csr = crypto.X509Req()
extensions = [
OpenSSL.crypto.X509Extension(
crypto.X509Extension(
b'subjectAltName',
critical=False,
value=', '.join('DNS:' + d for d in domains).encode('ascii')
),
]
if must_staple:
extensions.append(OpenSSL.crypto.X509Extension(
extensions.append(crypto.X509Extension(
b"1.3.6.1.5.5.7.1.24",
critical=False,
value=b"DER:30:03:02:01:05"))
@@ -183,8 +186,8 @@ def make_csr(private_key_pem, domains, must_staple=False):
csr.set_pubkey(private_key)
csr.set_version(2)
csr.sign(private_key, 'sha256')
return OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr)
return crypto.dump_certificate_request(
crypto.FILETYPE_PEM, csr)
def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req):
common_name = loaded_cert_or_req.get_subject().CN
@@ -221,11 +224,12 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
parts_separator = ", "
prefix = "DNS" + part_separator
if isinstance(cert_or_req, OpenSSL.crypto.X509):
func = OpenSSL.crypto.dump_certificate
if isinstance(cert_or_req, crypto.X509):
# pylint: disable=line-too-long
func = crypto.dump_certificate # type: Union[Callable[[int, crypto.X509Req], bytes], Callable[[int, crypto.X509], bytes]]
else:
func = OpenSSL.crypto.dump_certificate_request
text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
func = crypto.dump_certificate_request
text = func(crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
# WARNING: this function does not support multiple SANs extensions.
# Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
match = re.search(r"X509v3 Subject Alternative Name:(?: critical)?\s*(.*)", text)
@@ -252,12 +256,12 @@ def gen_ss_cert(key, domains, not_before=None,
"""
assert domains, "Must provide one or more hostnames for the cert."
cert = OpenSSL.crypto.X509()
cert = crypto.X509()
cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16))
cert.set_version(2)
extensions = [
OpenSSL.crypto.X509Extension(
crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
]
@@ -266,7 +270,7 @@ def gen_ss_cert(key, domains, not_before=None,
cert.set_issuer(cert.get_subject())
if force_san or len(domains) > 1:
extensions.append(OpenSSL.crypto.X509Extension(
extensions.append(crypto.X509Extension(
b"subjectAltName",
critical=False,
value=b", ".join(b"DNS:" + d.encode() for d in domains)
@@ -281,7 +285,7 @@ def gen_ss_cert(key, domains, not_before=None,
cert.sign(key, "sha256")
return cert
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle.
:param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
@@ -298,7 +302,7 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
if isinstance(cert, jose.ComparableX509):
# pylint: disable=protected-access
cert = cert.wrapped
return OpenSSL.crypto.dump_certificate(filetype, cert)
return crypto.dump_certificate(filetype, cert)
# assumes that OpenSSL.crypto.dump_certificate includes ending
# newline character

View File

@@ -13,6 +13,7 @@ import OpenSSL
from acme import errors
from acme import test_util
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
class SSLSocketAndProbeSNITest(unittest.TestCase):
@@ -41,28 +42,38 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
self.server_thread = threading.Thread(
# pylint: disable=no-member
target=self.server.handle_request)
self.server_thread.start()
time.sleep(1) # TODO: avoid race conditions in other way
def tearDown(self):
self.server_thread.join()
if self.server_thread.is_alive():
# The thread may have already terminated.
self.server_thread.join() # pragma: no cover
def _probe(self, name):
from acme.crypto_util import probe_sni
return jose.ComparableX509(probe_sni(
name, host='127.0.0.1', port=self.port))
def _start_server(self):
self.server_thread.start()
time.sleep(1) # TODO: avoid race conditions in other way
def test_probe_ok(self):
self._start_server()
self.assertEqual(self.cert, self._probe(b'foo'))
def test_probe_not_recognized_name(self):
self._start_server()
self.assertRaises(errors.Error, self._probe, b'bar')
# 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
# self.assertRaises(errors.Error, self._probe, b'bar')
def test_probe_connection_error(self):
# pylint has a hard time with six
self.server.server_close() # pylint: disable=no-member
original_timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(1)
self.assertRaises(errors.Error, self._probe, b'bar')
finally:
socket.setdefaulttimeout(original_timeout)
class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
@@ -165,7 +176,7 @@ class RandomSnTest(unittest.TestCase):
def setUp(self):
self.cert_count = 5
self.serial_num = []
self.serial_num = [] # type: List[int]
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
@@ -198,8 +209,8 @@ class MakeCSRTest(unittest.TestCase):
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEquals(len(csr.get_extensions()), 1)
self.assertEquals(csr.get_extensions()[0].get_data(),
self.assertEqual(len(csr.get_extensions()), 1)
self.assertEqual(csr.get_extensions()[0].get_data(),
OpenSSL.crypto.X509Extension(
b'subjectAltName',
critical=False,
@@ -216,7 +227,7 @@ class MakeCSRTest(unittest.TestCase):
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEquals(len(csr.get_extensions()), 2)
self.assertEqual(len(csr.get_extensions()), 2)
# NOTE: Ideally we would filter by the TLS Feature OID, but
# OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID,
# and the shortname field is just "UNDEF"

View File

@@ -110,6 +110,8 @@ class ConflictError(ClientError):
In the version of ACME implemented by Boulder, this is used to find an
account if you only have the private key, but don't know the account URL.
Also used in V2 of the ACME client for the same purpose.
"""
def __init__(self, location):
self.location = location

53
acme/acme/jose_test.py Normal file
View File

@@ -0,0 +1,53 @@
"""Tests for acme.jose shim."""
import importlib
import unittest
class JoseTest(unittest.TestCase):
"""Tests for acme.jose shim."""
def _test_it(self, submodule, attribute):
if submodule:
acme_jose_path = 'acme.jose.' + submodule
josepy_path = 'josepy.' + submodule
else:
acme_jose_path = 'acme.jose'
josepy_path = 'josepy'
acme_jose_mod = importlib.import_module(acme_jose_path)
josepy_mod = importlib.import_module(josepy_path)
self.assertIs(acme_jose_mod, josepy_mod)
self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute))
# We use the imports below with eval, but pylint doesn't
# understand that.
# pylint: disable=eval-used,unused-variable
import acme
import josepy
acme_jose_mod = eval(acme_jose_path)
josepy_mod = eval(josepy_path)
self.assertIs(acme_jose_mod, josepy_mod)
self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute))
def test_top_level(self):
self._test_it('', 'RS512')
def test_submodules(self):
# This test ensures that the modules in josepy that were
# available at the time it was moved into its own package are
# available under acme.jose. Backwards compatibility with new
# modules or testing code is not maintained.
mods_and_attrs = [('b64', 'b64decode',),
('errors', 'Error',),
('interfaces', 'JSONDeSerializable',),
('json_util', 'Field',),
('jwa', 'HS256',),
('jwk', 'JWK',),
('jws', 'JWS',),
('util', 'ImmutableMap',),]
for mod, attr in mods_and_attrs:
self._test_it(mod, attr)
if __name__ == '__main__':
unittest.main() # pragma: no cover

16
acme/acme/magic_typing.py Normal file
View File

@@ -0,0 +1,16 @@
"""Shim class to not have to depend on typing module in prod."""
import sys
class TypingClass(object):
"""Ignore import errors by getting anything"""
def __getattr__(self, name):
return None
try:
# mypy doesn't respect modifying sys.modules
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
# pylint: disable=unused-import
from typing import Collection, IO # type: ignore
# pylint: enable=unused-import
except ImportError:
sys.modules[__name__] = TypingClass()

View File

@@ -0,0 +1,41 @@
"""Tests for acme.magic_typing."""
import sys
import unittest
import mock
class MagicTypingTest(unittest.TestCase):
"""Tests for acme.magic_typing."""
def test_import_success(self):
try:
import typing as temp_typing
except ImportError: # pragma: no cover
temp_typing = None # pragma: no cover
typing_class_mock = mock.MagicMock()
text_mock = mock.MagicMock()
typing_class_mock.Text = text_mock
sys.modules['typing'] = typing_class_mock
if 'acme.magic_typing' in sys.modules:
del sys.modules['acme.magic_typing'] # pragma: no cover
from acme.magic_typing import Text # pylint: disable=no-name-in-module
self.assertEqual(Text, text_mock)
del sys.modules['acme.magic_typing']
sys.modules['typing'] = temp_typing
def test_import_failure(self):
try:
import typing as temp_typing
except ImportError: # pragma: no cover
temp_typing = None # pragma: no cover
sys.modules['typing'] = None
if 'acme.magic_typing' in sys.modules:
del sys.modules['acme.magic_typing'] # pragma: no cover
from acme.magic_typing import Text # pylint: disable=no-name-in-module
self.assertTrue(Text is None)
del sys.modules['acme.magic_typing']
sys.modules['typing'] = temp_typing
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -1,6 +1,10 @@
"""ACME protocol messages."""
import collections
import six
import json
try:
from collections.abc import Hashable # pylint: disable=no-name-in-module
except ImportError: # pragma: no cover
from collections import Hashable
import josepy as jose
@@ -8,6 +12,7 @@ from acme import challenges
from acme import errors
from acme import fields
from acme import util
from acme import jws
OLD_ERROR_PREFIX = "urn:acme:error:"
ERROR_PREFIX = "urn:ietf:params:acme:error:"
@@ -27,6 +32,7 @@ ERROR_CODES = {
'tls': 'The server experienced a TLS error during domain verification',
'unauthorized': 'The client lacks sufficient authorization',
'unknownHost': 'The server could not resolve a domain name',
'externalAccountRequired': 'The server requires external account binding',
}
ERROR_TYPE_DESCRIPTIONS = dict(
@@ -104,7 +110,7 @@ class Error(jose.JSONObjectWithFields, errors.Error):
if part is not None).decode()
class _Constant(jose.JSONDeSerializable, collections.Hashable): # type: ignore
class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore
"""ACME constant."""
__slots__ = ('name',)
POSSIBLE_NAMES = NotImplemented
@@ -145,6 +151,7 @@ STATUS_PROCESSING = Status('processing')
STATUS_VALID = Status('valid')
STATUS_INVALID = Status('invalid')
STATUS_REVOKED = Status('revoked')
STATUS_READY = Status('ready')
class IdentifierType(_Constant):
@@ -175,6 +182,7 @@ class Directory(jose.JSONDeSerializable):
_terms_of_service_v2 = jose.Field('termsOfService', omitempty=True)
website = jose.Field('website', omitempty=True)
caa_identities = jose.Field('caaIdentities', omitempty=True)
external_account_required = jose.Field('externalAccountRequired', omitempty=True)
def __init__(self, **kwargs):
kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
@@ -257,6 +265,24 @@ class ResourceBody(jose.JSONObjectWithFields):
"""ACME Resource Body."""
class ExternalAccountBinding(object):
"""ACME External Account Binding"""
@classmethod
def from_data(cls, account_public_key, kid, hmac_key, directory):
"""Create External Account Binding Resource from contact details, kid and hmac."""
key_json = json.dumps(account_public_key.to_partial_json()).encode()
decoded_hmac_key = jose.b64.b64decode(hmac_key)
url = directory["newAccount"]
eab = jws.JWS.sign(key_json, jose.jwk.JWKOct(key=decoded_hmac_key),
jose.jwa.HS256, None,
url, kid)
return eab.to_partial_json()
class Registration(ResourceBody):
"""Registration Resource Body.
@@ -273,19 +299,25 @@ class Registration(ResourceBody):
agreement = jose.Field('agreement', omitempty=True)
status = jose.Field('status', omitempty=True)
terms_of_service_agreed = jose.Field('termsOfServiceAgreed', omitempty=True)
only_return_existing = jose.Field('onlyReturnExisting', omitempty=True)
external_account_binding = jose.Field('externalAccountBinding', omitempty=True)
phone_prefix = 'tel:'
email_prefix = 'mailto:'
@classmethod
def from_data(cls, phone=None, email=None, **kwargs):
def from_data(cls, phone=None, email=None, external_account_binding=None, **kwargs):
"""Create registration resource from contact details."""
details = list(kwargs.pop('contact', ()))
if phone is not None:
details.append(cls.phone_prefix + phone)
if email is not None:
details.append(cls.email_prefix + email)
details.extend([cls.email_prefix + mail for mail in email.split(',')])
kwargs['contact'] = tuple(details)
if external_account_binding:
kwargs['external_account_binding'] = external_account_binding
return cls(**kwargs)
def _filter_contact(self, prefix):
@@ -521,7 +553,7 @@ class Order(ResourceBody):
"""
identifiers = jose.Field('identifiers', omitempty=True)
status = jose.Field('status', decoder=Status.from_json,
omitempty=True, default=STATUS_PENDING)
omitempty=True)
authorizations = jose.Field('authorizations', omitempty=True)
certificate = jose.Field('certificate', omitempty=True)
finalize = jose.Field('finalize', omitempty=True)
@@ -551,4 +583,3 @@ class OrderResource(ResourceWithURI):
class NewOrder(Order):
"""New order."""
resource_type = 'new-order'
resource = fields.Resource(resource_type)

View File

@@ -6,6 +6,7 @@ import mock
from acme import challenges
from acme import test_util
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
CERT = test_util.load_comparable_cert('cert.der')
@@ -85,7 +86,7 @@ class ConstantTest(unittest.TestCase):
from acme.messages import _Constant
class MockConstant(_Constant): # pylint: disable=missing-docstring
POSSIBLE_NAMES = {}
POSSIBLE_NAMES = {} # type: Dict
self.MockConstant = MockConstant # pylint: disable=invalid-name
self.const_a = MockConstant('a')
@@ -173,6 +174,24 @@ class DirectoryTest(unittest.TestCase):
self.assertTrue(result)
class ExternalAccountBindingTest(unittest.TestCase):
def setUp(self):
from acme.messages import Directory
self.key = jose.jwk.JWKRSA(key=KEY.public_key())
self.kid = "kid-for-testing"
self.hmac_key = "hmac-key-for-testing"
self.dir = Directory({
'newAccount': 'http://url/acme/new-account',
})
def test_from_data(self):
from acme.messages import ExternalAccountBinding
eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir)
self.assertEqual(len(eab), 3)
self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 'signature']))
class RegistrationTest(unittest.TestCase):
"""Tests for acme.messages.Registration."""
@@ -204,6 +223,22 @@ class RegistrationTest(unittest.TestCase):
'mailto:admin@foo.com',
))
def test_new_registration_from_data_with_eab(self):
from acme.messages import NewRegistration, ExternalAccountBinding, Directory
key = jose.jwk.JWKRSA(key=KEY.public_key())
kid = "kid-for-testing"
hmac_key = "hmac-key-for-testing"
directory = Directory({
'newAccount': 'http://url/acme/new-account',
})
eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory)
reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab)
self.assertEqual(reg.contact, (
'mailto:admin@foo.com',
))
self.assertEqual(sorted(reg.external_account_binding.keys()),
sorted(['protected', 'payload', 'signature']))
def test_phones(self):
self.assertEqual(('1234',), self.reg.phones)
@@ -423,6 +458,19 @@ class OrderResourceTest(unittest.TestCase):
'authorizations': None,
})
class NewOrderTest(unittest.TestCase):
"""Tests for acme.messages.NewOrder."""
def setUp(self):
from acme.messages import NewOrder
self.reg = NewOrder(
identifiers=mock.sentinel.identifiers)
def test_to_partial_json(self):
self.assertEqual(self.reg.to_json(), {
'identifiers': mock.sentinel.identifiers,
})
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -16,6 +16,8 @@ import OpenSSL
from acme import challenges
from acme import crypto_util
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from acme import _TLSSNI01DeprecationModule
logger = logging.getLogger(__name__)
@@ -36,7 +38,7 @@ class TLSServer(socketserver.TCPServer):
self.certs = kwargs.pop("certs", {})
self.method = kwargs.pop(
# pylint: disable=protected-access
"method", crypto_util._DEFAULT_TLSSNI01_SSL_METHOD)
"method", crypto_util._DEFAULT_SSL_METHOD)
self.allow_reuse_address = kwargs.pop("allow_reuse_address", True)
socketserver.TCPServer.__init__(self, *args, **kwargs)
@@ -66,8 +68,8 @@ class BaseDualNetworkedServers(object):
def __init__(self, ServerClass, server_address, *remaining_args, **kwargs):
port = server_address[1]
self.threads = []
self.servers = []
self.threads = [] # type: List[threading.Thread]
self.servers = [] # type: List[ACMEServerMixin]
# Must try True first.
# Ubuntu, for example, will fail to bind to IPv4 if we've already bound
@@ -82,9 +84,22 @@ class BaseDualNetworkedServers(object):
new_address = (server_address[0],) + (port,) + server_address[2:]
new_args = (new_address,) + remaining_args
server = ServerClass(*new_args, **kwargs) # pylint: disable=star-args
except socket.error:
logger.debug("Failed to bind to %s:%s using %s", new_address[0],
logger.debug(
"Successfully bound to %s:%s using %s", new_address[0],
new_address[1], "IPv6" if ip_version else "IPv4")
except socket.error:
if self.servers:
# Already bound using IPv6.
logger.debug(
"Certbot wasn't able to bind to %s:%s using %s, this " +
"is often expected due to the dual stack nature of " +
"IPv6 socket implementations.",
new_address[0], new_address[1],
"IPv6" if ip_version else "IPv4")
else:
logger.debug(
"Failed to bind to %s:%s using %s", new_address[0],
new_address[1], "IPv6" if ip_version else "IPv4")
else:
self.servers.append(server)
# If two servers are set up and port 0 was passed in, ensure we always
@@ -189,7 +204,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.simple_http_resources = kwargs.pop("simple_http_resources", set())
socketserver.BaseRequestHandler.__init__(self, *args, **kwargs)
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def log_message(self, format, *args): # pylint: disable=redefined-builtin
"""Log arbitrary message."""
@@ -262,7 +277,7 @@ def simple_tls_sni_01_server(cli_args, forever=True):
certs = {}
_, hosts, _ = next(os.walk('.'))
_, hosts, _ = next(os.walk('.')) # type: ignore # https://github.com/python/mypy/issues/465
for host in hosts:
with open(os.path.join(host, "cert.pem")) as cert_file:
cert_contents = cert_file.read()
@@ -282,5 +297,9 @@ def simple_tls_sni_01_server(cli_args, forever=True):
server.handle_request()
# Patching ourselves to warn about TLS-SNI challenge deprecation and removal.
sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__])
if __name__ == "__main__":
sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover

View File

@@ -1,11 +1,13 @@
"""Tests for acme.standalone."""
import multiprocessing
import os
import shutil
import socket
import threading
import tempfile
import time
import unittest
import time
from contextlib import closing
from six.moves import http_client # pylint: disable=import-error
from six.moves import socketserver # type: ignore # pylint: disable=import-error
@@ -18,6 +20,7 @@ from acme import challenges
from acme import crypto_util
from acme import errors
from acme import test_util
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
class TLSServerTest(unittest.TestCase):
@@ -48,7 +51,7 @@ class TLSSNI01ServerTest(unittest.TestCase):
test_util.load_cert('rsa2048_cert.pem'),
)}
from acme.standalone import TLSSNI01Server
self.server = TLSSNI01Server(("", 0), certs=self.certs)
self.server = TLSSNI01Server(('localhost', 0), certs=self.certs)
# pylint: disable=no-member
self.thread = threading.Thread(target=self.server.serve_forever)
self.thread.start()
@@ -72,7 +75,7 @@ class HTTP01ServerTest(unittest.TestCase):
def setUp(self):
self.account_key = jose.JWK.load(
test_util.load_vector('rsa1024_key.pem'))
self.resources = set()
self.resources = set() # type: Set
from acme.standalone import HTTP01Server
self.server = HTTP01Server(('', 0), resources=self.resources)
@@ -133,8 +136,11 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
self.address_family = socket.AF_INET
socketserver.TCPServer.__init__(self, *args, **kwargs)
if ipv6:
# NB: On Windows, socket.IPPROTO_IPV6 constant may be missing.
# We use the corresponding value (41) instead.
level = getattr(socket, "IPPROTO_IPV6", 41)
# pylint: disable=no-member
self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
self.socket.setsockopt(level, socket.IPV6_V6ONLY, 1)
try:
self.server_bind()
self.server_activate()
@@ -147,15 +153,15 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
mock_bind.side_effect = socket.error
from acme.standalone import BaseDualNetworkedServers
self.assertRaises(socket.error, BaseDualNetworkedServers,
BaseDualNetworkedServersTest.SingleProtocolServer,
("", 0),
socketserver.BaseRequestHandler)
BaseDualNetworkedServersTest.SingleProtocolServer,
('', 0),
socketserver.BaseRequestHandler)
def test_ports_equal(self):
from acme.standalone import BaseDualNetworkedServers
servers = BaseDualNetworkedServers(
BaseDualNetworkedServersTest.SingleProtocolServer,
("", 0),
('', 0),
socketserver.BaseRequestHandler)
socknames = servers.getsocknames()
prev_port = None
@@ -177,7 +183,7 @@ class TLSSNI01DualNetworkedServersTest(unittest.TestCase):
test_util.load_cert('rsa2048_cert.pem'),
)}
from acme.standalone import TLSSNI01DualNetworkedServers
self.servers = TLSSNI01DualNetworkedServers(("", 0), certs=self.certs)
self.servers = TLSSNI01DualNetworkedServers(('localhost', 0), certs=self.certs)
self.servers.serve_forever()
def tearDown(self):
@@ -201,7 +207,7 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase):
def setUp(self):
self.account_key = jose.JWK.load(
test_util.load_vector('rsa1024_key.pem'))
self.resources = set()
self.resources = set() # type: Set
from acme.standalone import HTTP01DualNetworkedServers
self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources)
@@ -259,41 +265,41 @@ class TestSimpleTLSSNI01Server(unittest.TestCase):
shutil.copy(test_util.vector_path('rsa2048_key.pem'),
os.path.join(localhost_dir, 'key.pem'))
with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock:
sock.bind(('', 0))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.port = sock.getsockname()[1]
from acme.standalone import simple_tls_sni_01_server
self.port = 1234
self.thread = threading.Thread(
target=simple_tls_sni_01_server, kwargs={
'cli_args': ('xxx', '--port', str(self.port)),
'forever': False,
},
)
self.process = multiprocessing.Process(target=simple_tls_sni_01_server,
args=(['path', '-p', str(self.port)],))
self.old_cwd = os.getcwd()
os.chdir(self.test_cwd)
def tearDown(self):
os.chdir(self.old_cwd)
self.thread.join()
if self.process.is_alive():
self.process.terminate()
shutil.rmtree(self.test_cwd)
def test_it(self):
max_attempts = 5
for attempt in range(max_attempts):
try:
cert = crypto_util.probe_sni(
b'localhost', b'0.0.0.0', self.port)
except errors.Error:
self.assertTrue(attempt + 1 < max_attempts, "Timeout!")
time.sleep(1) # wait until thread starts
else:
self.assertEqual(jose.ComparableX509(cert),
test_util.load_comparable_cert(
'rsa2048_cert.pem'))
break
@mock.patch('acme.standalone.TLSSNI01Server.handle_request')
def test_mock(self, handle):
from acme.standalone import simple_tls_sni_01_server
simple_tls_sni_01_server(cli_args=['path', '-p', str(self.port)], forever=False)
self.assertEqual(handle.call_count, 1)
if attempt == 0:
# the first attempt is always meant to fail, so we can test
# the socket failure code-path for probe_sni, as well
self.thread.start()
def test_live(self):
self.process.start()
cert = None
for _ in range(50):
time.sleep(0.1)
try:
cert = crypto_util.probe_sni(b'localhost', b'127.0.0.1', self.port)
break
except errors.Error: # pragma: no cover
pass
self.assertEqual(jose.ComparableX509(cert),
test_util.load_comparable_cert('rsa2048_cert.pem'))
if __name__ == "__main__":

View File

@@ -10,7 +10,7 @@ import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import josepy as jose
import OpenSSL
from OpenSSL import crypto
def vector_path(*names):
@@ -39,8 +39,8 @@ def _guess_loader(filename, loader_pem, loader_der):
def load_cert(*names):
"""Load certificate."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_certificate(loader, load_vector(*names))
def load_comparable_cert(*names):
@@ -51,8 +51,8 @@ def load_comparable_cert(*names):
def load_csr(*names):
"""Load certificate request."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_certificate_request(loader, load_vector(*names))
def load_comparable_csr(*names):
@@ -71,8 +71,8 @@ def load_rsa_private_key(*names):
def load_pyopenssl_private_key(*names):
"""Load pyOpenSSL private key."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_privatekey(loader, load_vector(*names))
def skip_unless(condition, reason): # pragma: no cover

View File

@@ -16,13 +16,6 @@ Contents:
.. automodule:: acme
:members:
Example client:
.. include:: ../examples/example_client.py
:code: python
Indices and tables
==================

View File

@@ -1,47 +0,0 @@
"""Example script showing how to use acme client API."""
import logging
import os
import pkg_resources
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
import josepy as jose
import OpenSSL
from acme import client
from acme import messages
logging.basicConfig(level=logging.DEBUG)
DIRECTORY_URL = 'https://acme-staging.api.letsencrypt.org/directory'
BITS = 2048 # minimum for Boulder
DOMAIN = 'example1.com' # example.com is ignored by Boulder
# generate_private_key requires cryptography>=0.5
key = jose.JWKRSA(key=rsa.generate_private_key(
public_exponent=65537,
key_size=BITS,
backend=default_backend()))
acme = client.Client(DIRECTORY_URL, key)
regr = acme.register()
logging.info('Auto-accepting TOS: %s', regr.terms_of_service)
acme.agree_to_tos(regr)
logging.debug(regr)
authzr = acme.request_challenges(
identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=DOMAIN))
logging.debug(authzr)
authzr, authzr_response = acme.poll(authzr)
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, pkg_resources.resource_string(
'acme', os.path.join('testdata', 'csr.der')))
try:
acme.request_issuance(jose.util.ComparableX509(csr), (authzr,))
except messages.Error as error:
print ("This script is doomed to fail as no authorization "
"challenges are ever solved. Error from server: {0}".format(error))

View File

@@ -0,0 +1,240 @@
"""Example ACME-V2 API for HTTP-01 challenge.
Brief:
This a complete usage example of the python-acme API.
Limitations of this example:
- Works for only one Domain name
- Performs only HTTP-01 challenge
- Uses ACME-v2
Workflow:
(Account creation)
- Create account key
- Register account and accept TOS
(Certificate actions)
- Select HTTP-01 within offered challenges by the CA server
- Set up http challenge resource
- Set up standalone web server
- Create domain private key and CSR
- Issue certificate
- Renew certificate
- Revoke certificate
(Account update actions)
- Change contact information
- Deactivate Account
"""
from contextlib import contextmanager
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
import OpenSSL
from acme import challenges
from acme import client
from acme import crypto_util
from acme import errors
from acme import messages
from acme import standalone
import josepy as jose
# Constants:
# This is the staging point for ACME-V2 within Let's Encrypt.
DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
USER_AGENT = 'python-acme-example'
# Account key size
ACC_KEY_BITS = 2048
# Certificate private key size
CERT_PKEY_BITS = 2048
# Domain name for the certificate.
DOMAIN = 'client.example.com'
# If you are running Boulder locally, it is possible to configure any port
# number to execute the challenge, but real CA servers will always use port
# 80, as described in the ACME specification.
PORT = 80
# Useful methods and classes:
def new_csr_comp(domain_name, pkey_pem=None):
"""Create certificate signing request."""
if pkey_pem is None:
# Create private key.
pkey = OpenSSL.crypto.PKey()
pkey.generate_key(OpenSSL.crypto.TYPE_RSA, CERT_PKEY_BITS)
pkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM,
pkey)
csr_pem = crypto_util.make_csr(pkey_pem, [domain_name])
return pkey_pem, csr_pem
def select_http01_chall(orderr):
"""Extract authorization resource from within order resource."""
# Authorization Resource: authz.
# This object holds the offered challenges by the server and their status.
authz_list = orderr.authorizations
for authz in authz_list:
# Choosing challenge.
# authz.body.challenges is a set of ChallengeBody objects.
for i in authz.body.challenges:
# Find the supported challenge.
if isinstance(i.chall, challenges.HTTP01):
return i
raise Exception('HTTP-01 challenge was not offered by the CA server.')
@contextmanager
def challenge_server(http_01_resources):
"""Manage standalone server set up and shutdown."""
# Setting up a fake server that binds at PORT and any address.
address = ('', PORT)
try:
servers = standalone.HTTP01DualNetworkedServers(address,
http_01_resources)
# Start client standalone web server.
servers.serve_forever()
yield servers
finally:
# Shutdown client web server and unbind from PORT
servers.shutdown_and_server_close()
def perform_http01(client_acme, challb, orderr):
"""Set up standalone webserver and perform HTTP-01 challenge."""
response, validation = challb.response_and_validation(client_acme.net.key)
resource = standalone.HTTP01RequestHandler.HTTP01Resource(
chall=challb.chall, response=response, validation=validation)
with challenge_server({resource}):
# Let the CA server know that we are ready for the challenge.
client_acme.answer_challenge(challb, response)
# Wait for challenge status and then issue a certificate.
# It is possible to set a deadline time.
finalized_orderr = client_acme.poll_and_finalize(orderr)
return finalized_orderr.fullchain_pem
# Main examples:
def example_http():
"""This example executes the whole process of fulfilling a HTTP-01
challenge for one specific domain.
The workflow consists of:
(Account creation)
- Create account key
- Register account and accept TOS
(Certificate actions)
- Select HTTP-01 within offered challenges by the CA server
- Set up http challenge resource
- Set up standalone web server
- Create domain private key and CSR
- Issue certificate
- Renew certificate
- Revoke certificate
(Account update actions)
- Change contact information
- Deactivate Account
"""
# Create account key
acc_key = jose.JWKRSA(
key=rsa.generate_private_key(public_exponent=65537,
key_size=ACC_KEY_BITS,
backend=default_backend()))
# Register account and accept TOS
net = client.ClientNetwork(acc_key, user_agent=USER_AGENT)
directory = messages.Directory.from_json(net.get(DIRECTORY_URL).json())
client_acme = client.ClientV2(directory, net=net)
# Terms of Service URL is in client_acme.directory.meta.terms_of_service
# Registration Resource: regr
# Creates account with contact information.
email = ('fake@example.com')
regr = client_acme.new_account(
messages.NewRegistration.from_data(
email=email, terms_of_service_agreed=True))
# Create domain private key and CSR
pkey_pem, csr_pem = new_csr_comp(DOMAIN)
# Issue certificate
orderr = client_acme.new_order(csr_pem)
# Select HTTP-01 within offered challenges by the CA server
challb = select_http01_chall(orderr)
# The certificate is ready to be used in the variable "fullchain_pem".
fullchain_pem = perform_http01(client_acme, challb, orderr)
# Renew certificate
_, csr_pem = new_csr_comp(DOMAIN, pkey_pem)
orderr = client_acme.new_order(csr_pem)
challb = select_http01_chall(orderr)
# Performing challenge
fullchain_pem = perform_http01(client_acme, challb, orderr)
# Revoke certificate
fullchain_com = jose.ComparableX509(
OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, fullchain_pem))
try:
client_acme.revoke(fullchain_com, 0) # revocation reason = 0
except errors.ConflictError:
# Certificate already revoked.
pass
# Query registration status.
client_acme.net.account = regr
try:
regr = client_acme.query_registration(regr)
except errors.Error as err:
if err.typ == messages.OLD_ERROR_PREFIX + 'unauthorized' \
or err.typ == messages.ERROR_PREFIX + 'unauthorized':
# Status is deactivated.
pass
raise
# Change contact information
email = 'newfake@example.com'
regr = client_acme.update_registration(
regr.update(
body=regr.body.update(
contact=('mailto:' + email,)
)
)
)
# Deactivate account/registration
regr = client_acme.deactivate_registration(regr)
if __name__ == "__main__":
example_http()

2
acme/pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
norecursedirs = .* build dist CVS _darcs {arch} *.egg

View File

@@ -1,24 +1,26 @@
import sys
from setuptools import setup
from setuptools import find_packages
from setuptools.command.test import test as TestCommand
import sys
version = '0.24.0.dev0'
version = '0.33.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
# load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8)
'cryptography>=0.8',
'cryptography>=1.2.3',
# formerly known as acme.jose:
'josepy>=1.0.0',
# 1.1.0+ is required to avoid the warnings described at
# https://github.com/certbot/josepy/issues/13.
'josepy>=1.1.0',
# Connection.set_tlsext_host_name (>=0.13)
'mock',
'PyOpenSSL>=0.13',
'PyOpenSSL>=0.13.1',
'pyrfc3339',
'pytz',
'requests[security]>=2.4.1', # security extras added in 2.4.1
'requests[security]>=2.6.0', # security extras added in 2.4.1
'requests-toolbelt>=0.3.0',
'setuptools',
'six>=1.9.0', # needed for python_2_unicode_compatible
]
@@ -35,6 +37,21 @@ docs_extras = [
]
class PyTest(TestCommand):
user_options = []
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ''
def run_tests(self):
import shlex
# import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(shlex.split(self.pytest_args))
sys.exit(errno)
setup(
name='acme',
version=version,
@@ -45,7 +62,7 @@ setup(
license='Apache License 2.0',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
@@ -55,6 +72,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],
@@ -67,4 +85,6 @@ setup(
'docs': docs_extras,
},
test_suite='acme',
tests_require=["pytest"],
cmdclass={"test": PyTest},
)

40
appveyor.yml Normal file
View File

@@ -0,0 +1,40 @@
image: Visual Studio 2015
environment:
matrix:
- TOXENV: py35
- TOXENV: py37-cover
branches:
only:
- master
- /^\d+\.\d+\.x$/ # Version branches like X.X.X
- /^test-.*$/
init:
# Since master can receive only commits from PR that have already been tested, following
# condition avoid to launch all jobs except the coverage one for commits pushed to master.
- ps: |
if (-Not $Env:APPVEYOR_PULL_REQUEST_NUMBER -And $Env:APPVEYOR_REPO_BRANCH -Eq 'master' `
-And -Not ($Env:TOXENV -Like '*-cover'))
{ $Env:APPVEYOR_SKIP_FINALIZE_ON_EXIT = 'true'; Exit-AppVeyorBuild }
install:
# Use Python 3.7 by default
- "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%"
# Check env
- "python --version"
# Upgrade pip to avoid warnings
- "python -m pip install --upgrade pip"
# Ready to install tox and coverage
- "pip install tox codecov"
build: off
test_script:
- set TOX_TESTENV_PASSENV=APPVEYOR
# Test env is set by TOXENV env variable
- tox
on_success:
- if exist .coverage codecov

View File

@@ -1,4 +1,5 @@
""" Utility functions for certbot-apache plugin """
import binascii
import os
from certbot import util
@@ -98,3 +99,8 @@ def parse_define_file(filepath, varname):
var_parts = v[2:].partition("=")
return_vars[var_parts[0]] = var_parts[2]
return return_vars
def unique_id():
""" Returns an unique id to be used as a VirtualHost identifier"""
return binascii.hexlify(os.urandom(16)).decode("utf-8")

View File

@@ -44,67 +44,134 @@ autoload xfm
*****************************************************************)
let dels (s:string) = del s s
(* The continuation sequence that indicates that we should consider the
* next line part of the current line *)
let cont = /\\\\\r?\n/
(* Whitespace within a line: space, tab, and the continuation sequence *)
let ws = /[ \t]/ | cont
(* Any possible character - '.' does not match \n *)
let any = /(.|\n)/
(* Any character preceded by a backslash *)
let esc_any = /\\\\(.|\n)/
(* Newline sequence - both for Unix and DOS newlines *)
let nl = /\r?\n/
(* Whitespace at the end of a line *)
let eol = del (ws* . nl) "\n"
(* deal with continuation lines *)
let sep_spc = del /([ \t]+|[ \t]*\\\\\r?\n[ \t]*)+/ " "
let sep_osp = del /([ \t]*|[ \t]*\\\\\r?\n[ \t]*)*/ ""
let sep_eq = del /[ \t]*=[ \t]*/ "="
let sep_spc = del ws+ " "
let sep_osp = del ws* ""
let sep_eq = del (ws* . "=" . ws*) "="
let nmtoken = /[a-zA-Z:_][a-zA-Z0-9:_.-]*/
let word = /[a-z][a-z0-9._-]*/i
let eol = Util.doseol
let empty = Util.empty_dos
(* A complete line that is either just whitespace or a comment that only
* contains whitespace *)
let empty = [ del (ws* . /#?/ . ws* . nl) "\n" ]
let indent = Util.indent
let comment_val_re = /([^ \t\r\n](.|\\\\\r?\n)*[^ \\\t\r\n]|[^ \t\r\n])/
let comment = [ label "#comment" . del /[ \t]*#[ \t]*/ "# "
. store comment_val_re . eol ]
(* A comment that is not just whitespace. We define it in terms of the
* things that are not allowed as part of such a comment:
* 1) Starts with whitespace
* 2) Ends with whitespace, a backslash or \r
* 3) Unescaped newlines
*)
let comment =
let comment_start = del (ws* . "#" . ws* ) "# " in
let unesc_eol = /[^\]?/ . nl in
let w = /[^\t\n\r \\]/ in
let r = /[\r\\]/ in
let s = /[\t\r ]/ in
(*
* we'd like to write
* let b = /\\\\/ in
* let t = /[\t\n\r ]/ in
* let x = b . (t? . (s|w)* ) in
* but the definition of b depends on commit 244c0edd in 1.9.0 and
* would make the lens unusable with versions before 1.9.0. So we write
* x out which works in older versions, too
*)
let x = /\\\\[\t\n\r ]?[^\n\\]*/ in
let line = ((r . s* . w|w|r) . (s|w)* . x*|(r.s* )?).w.(s*.w)* in
[ label "#comment" . comment_start . store line . eol ]
(* borrowed from shellvars.aug *)
let char_arg_dir = /([^\\ '"{\t\r\n]|[^ '"{\t\r\n]+[^\\ \t\r\n])|\\\\"|\\\\'|\\\\ /
let char_arg_sec = /([^\\ '"\t\r\n>]|[^ '"\t\r\n>]+[^\\ \t\r\n>])|\\\\"|\\\\'|\\\\ /
let char_arg_wl = /([^\\ '"},\t\r\n]|[^ '"},\t\r\n]+[^\\ '"},\t\r\n])/
let cdot = /\\\\./
let cl = /\\\\\n/
let dquot =
let no_dquot = /[^"\\\r\n]/
in /"/ . (no_dquot|cdot|cl)* . /"/
in /"/ . (no_dquot|esc_any)* . /"/
let dquot_msg =
let no_dquot = /([^ \t"\\\r\n]|[^"\\\r\n]+[^ \t"\\\r\n])/
in /"/ . (no_dquot|cdot|cl)*
in /"/ . (no_dquot|esc_any)* . no_dquot
let squot =
let no_squot = /[^'\\\r\n]/
in /'/ . (no_squot|cdot|cl)* . /'/
in /'/ . (no_squot|esc_any)* . /'/
let comp = /[<>=]?=/
(******************************************************************
* Attributes
*****************************************************************)
let arg_dir = [ label "arg" . store (char_arg_dir+|dquot|squot) ]
(* The arguments for a directive come in two flavors: quoted with single or
* double quotes, or bare. Bare arguments may not start with a single or
* double quote; since we also treat "word lists" special, i.e. lists
* enclosed in curly braces, bare arguments may not start with those,
* either.
*
* Bare arguments may not contain unescaped spaces, but we allow escaping
* with '\\'. Quoted arguments can contain anything, though the quote must
* be escaped with '\\'.
*)
let bare = /([^{"' \t\n\r]|\\\\.)([^ \t\n\r]|\\\\.)*[^ \t\n\r\\]|[^{"' \t\n\r\\]/
let arg_quoted = [ label "arg" . store (dquot|squot) ]
let arg_bare = [ label "arg" . store bare ]
(* message argument starts with " but ends at EOL *)
let arg_dir_msg = [ label "arg" . store dquot_msg ]
let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ]
let arg_wl = [ label "arg" . store (char_arg_wl+|dquot|squot) ]
(* comma-separated wordlist as permitted in the SSLRequire directive *)
let arg_wordlist =
let wl_start = Util.del_str "{" in
let wl_end = Util.del_str "}" in
let wl_start = dels "{" in
let wl_end = dels "}" in
let wl_sep = del /[ \t]*,[ \t]*/ ", "
in [ label "wordlist" . wl_start . arg_wl . (wl_sep . arg_wl)* . wl_end ]
let argv (l:lens) = l . (sep_spc . l)*
(* the arguments of a directive. We use this once we have parsed the name
* of the directive, and the space right after it. When dir_args is used,
* we also know that we have at least one argument. We need to be careful
* with the spacing between arguments: quoted arguments and word lists do
* not need to have space between them, but bare arguments do.
*
* Apache apparently is also happy if the last argument starts with a double
* quote, but has no corresponding closing duoble quote, which is what
* arg_dir_msg handles
*)
let dir_args =
let arg_nospc = arg_quoted|arg_wordlist in
(arg_bare . sep_spc | arg_nospc . sep_osp)* . (arg_bare|arg_nospc|arg_dir_msg)
let directive =
(* arg_dir_msg may be the last or only argument *)
let dir_args = (argv (arg_dir|arg_wordlist) . (sep_spc . arg_dir_msg)?) | arg_dir_msg
in [ indent . label "directive" . store word . (sep_spc . dir_args)? . eol ]
[ indent . label "directive" . store word . (sep_spc . dir_args)? . eol ]
let arg_sec = [ label "arg" . store (char_arg_sec+|comp|dquot|squot) ]
let section (body:lens) =
(* opt_eol includes empty lines *)
let opt_eol = del /([ \t]*#?\r?\n)*/ "\n" in
let opt_eol = del /([ \t]*#?[ \t]*\r?\n)*/ "\n" in
let inner = (sep_spc . argv arg_sec)? . sep_osp .
dels ">" . opt_eol . ((body|comment) . (body|empty|comment)*)? .
indent . dels "</" in
@@ -133,6 +200,7 @@ let filter = (incl "/etc/apache2/apache2.conf") .
(incl "/etc/httpd/conf.d/*.conf") .
(incl "/etc/httpd/httpd.conf") .
(incl "/etc/httpd/conf/httpd.conf") .
(incl "/etc/httpd/conf.modules.d/*.conf") .
Util.stdexcl
let xfm = transform lns filter

View File

@@ -1,5 +1,6 @@
"""Apache Configuration based off of Augeas Configurator."""
# pylint: disable=too-many-lines
import copy
import fnmatch
import logging
import os
@@ -13,13 +14,16 @@ import zope.component
import zope.interface
from acme import challenges
from acme.magic_typing import Any, DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import
from certbot.plugins import common
from certbot.plugins.util import path_surgery
from certbot.plugins.enhancements import AutoHSTSEnhancement
from certbot_apache import apache_util
from certbot_apache import augeas_configurator
@@ -28,7 +32,6 @@ from certbot_apache import display_ops
from certbot_apache import http_01
from certbot_apache import obj
from certbot_apache import parser
from certbot_apache import tls_sni_01
from collections import defaultdict
@@ -87,55 +90,92 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
description = "Apache Web Server plugin - Beta"
description = "Apache Web Server plugin"
if os.environ.get("CERTBOT_DOCS") == "1":
description += ( # pragma: no cover
" (Please note that the default values of the Apache plugin options"
" change depending on the operating system Certbot is run on.)"
)
OS_DEFAULTS = dict(
server_root="/etc/apache2",
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", "options-ssl-apache.conf")
)
def constant(self, key):
"""Get constant for OS_DEFAULTS"""
return self.OS_DEFAULTS.get(key)
def option(self, key):
"""Get a value from options"""
return self.options.get(key)
def _prepare_options(self):
"""
Set the values possibly changed by command line parameters to
OS_DEFAULTS constant dictionary
"""
opts = ["enmod", "dismod", "le_vhost_ext", "server_root", "vhost_root",
"logs_root", "challenge_location", "handle_modules", "handle_sites",
"ctl"]
for o in opts:
# Config options use dashes instead of underscores
if self.conf(o.replace("_", "-")) is not None:
self.options[o] = self.conf(o.replace("_", "-"))
else:
self.options[o] = self.OS_DEFAULTS[o]
# Special cases
self.options["version_cmd"][0] = self.option("ctl")
self.options["restart_cmd"][0] = self.option("ctl")
self.options["conftest_cmd"][0] = self.option("ctl")
@classmethod
def add_parser_arguments(cls, add):
add("enmod", default=cls.OS_DEFAULTS["enmod"],
help="Path to the Apache 'a2enmod' binary.")
add("dismod", default=cls.OS_DEFAULTS["dismod"],
help="Path to the Apache 'a2dismod' binary.")
add("le-vhost-ext", default=cls.OS_DEFAULTS["le_vhost_ext"],
help="SSL vhost configuration extension.")
add("server-root", default=cls.OS_DEFAULTS["server_root"],
help="Apache server root directory.")
# When adding, modifying or deleting command line arguments, be sure to
# include the changes in the list used in method _prepare_options() to
# ensure consistent behavior.
# Respect CERTBOT_DOCS environment variable and use default values from
# base class regardless of the underlying distribution (overrides).
if os.environ.get("CERTBOT_DOCS") == "1":
DEFAULTS = ApacheConfigurator.OS_DEFAULTS
else:
# cls.OS_DEFAULTS can be distribution specific, see override classes
DEFAULTS = cls.OS_DEFAULTS
add("enmod", default=DEFAULTS["enmod"],
help="Path to the Apache 'a2enmod' binary")
add("dismod", default=DEFAULTS["dismod"],
help="Path to the Apache 'a2dismod' binary")
add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"],
help="SSL vhost configuration extension")
add("server-root", default=DEFAULTS["server_root"],
help="Apache server root directory")
add("vhost-root", default=None,
help="Apache server VirtualHost configuration root")
add("logs-root", default=cls.OS_DEFAULTS["logs_root"],
add("logs-root", default=DEFAULTS["logs_root"],
help="Apache server logs directory")
add("challenge-location",
default=cls.OS_DEFAULTS["challenge_location"],
help="Directory path for challenge configuration.")
add("handle-modules", default=cls.OS_DEFAULTS["handle_mods"],
help="Let installer handle enabling required modules for you." +
default=DEFAULTS["challenge_location"],
help="Directory path for challenge configuration")
add("handle-modules", default=DEFAULTS["handle_modules"],
help="Let installer handle enabling required modules for you " +
"(Only Ubuntu/Debian currently)")
add("handle-sites", default=cls.OS_DEFAULTS["handle_sites"],
help="Let installer handle enabling sites for you." +
add("handle-sites", default=DEFAULTS["handle_sites"],
help="Let installer handle enabling sites for you " +
"(Only Ubuntu/Debian currently)")
util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
add("ctl", default=DEFAULTS["ctl"],
help="Full path to Apache control script")
util.add_deprecated_argument(
add, argument_name="init-script", nargs=1)
@@ -150,20 +190,23 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
super(ApacheConfigurator, self).__init__(*args, **kwargs)
# Add name_server association dict
self.assoc = dict()
self.assoc = dict() # type: Dict[str, obj.VirtualHost]
# Outstanding challenges
self._chall_out = set()
self._chall_out = set() # type: Set[KeyAuthorizationAnnotatedChallenge]
# List of vhosts configured per wildcard domain on this run.
# used by deploy_cert() and enhance()
self._wildcard_vhosts = dict()
self._wildcard_vhosts = dict() # type: Dict[str, List[obj.VirtualHost]]
# Maps enhancements to vhosts we've enabled the enhancement for
self._enhanced_vhosts = defaultdict(set)
self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]]
# Temporary state for AutoHSTS enhancement
self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]]
# These will be set in the prepare function
self._prepared = False
self.parser = None
self.version = version
self.vhosts = None
self.vhostroot = None
self.options = copy.deepcopy(self.OS_DEFAULTS)
self._enhance_func = {"redirect": self._enable_redirect,
"ensure-http-header": self._set_http_header,
"staple-ocsp": self._enable_ocsp_stapling}
@@ -171,15 +214,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
@property
def mod_ssl_conf(self):
"""Full absolute path to SSL configuration file."""
return os.path.join(self.config.config_dir,
constants.MOD_SSL_CONF_DEST)
return os.path.join(self.config.config_dir, constants.MOD_SSL_CONF_DEST)
@property
def updated_mod_ssl_conf_digest(self):
"""Full absolute path to digest of updated SSL configuration file."""
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
def prepare(self):
"""Prepare the authenticator/installer.
@@ -195,12 +236,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
except ImportError:
raise errors.NoInstallationError("Problem in Augeas installation")
self._prepare_options()
# Verify Apache is installed
restart_cmd = self.constant("restart_cmd")[0]
if not util.exe_exists(restart_cmd):
if not path_surgery(restart_cmd):
raise errors.NoInstallationError(
'Cannot find Apache control command {0}'.format(restart_cmd))
self._verify_exe_availability(self.option("ctl"))
# Make sure configuration is valid
self.config_test()
@@ -220,12 +259,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"version 1.2.0 or higher, please make sure you have you have "
"those installed.")
# Parse vhost-root if defined on cli
if not self.conf("vhost-root"):
self.vhostroot = self.constant("vhost_root")
else:
self.vhostroot = os.path.abspath(self.conf("vhost-root"))
self.parser = self.get_parser()
# Check for errors in parsing files with Augeas
@@ -239,11 +272,19 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Prevent two Apache plugins from modifying a config at once
try:
util.lock_dir_until_exit(self.conf("server-root"))
util.lock_dir_until_exit(self.option("server_root"))
except (OSError, errors.LockError):
logger.debug("Encountered error:", exc_info=True)
raise errors.PluginError(
"Unable to lock %s", self.conf("server-root"))
"Unable to lock %s", self.option("server_root"))
self._prepared = True
def _verify_exe_availability(self, exe):
"""Checks availability of Apache executable"""
if not util.exe_exists(exe):
if not path_surgery(exe):
raise errors.NoInstallationError(
'Cannot find Apache executable {0}'.format(exe))
def _check_aug_version(self):
""" Checks that we have recent enough version of libaugeas.
@@ -262,8 +303,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
def get_parser(self):
"""Initializes the ApacheParser"""
# If user provided vhost_root value in command line, use it
return parser.ApacheParser(
self.aug, self.conf("server-root"), self.conf("vhost-root"),
self.aug, self.option("server_root"), self.conf("vhost-root"),
self.version, configurator=self)
def _wildcard_domain(self, domain):
@@ -351,7 +393,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if len(name.split(".")) == len(domain.split(".")):
return fnmatch.fnmatch(name, domain)
def _choose_vhosts_wildcard(self, domain, create_ssl=True):
"""Prompts user to choose vhosts to install a wildcard certificate for"""
@@ -396,7 +437,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self._wildcard_vhosts[domain] = return_vhosts
return return_vhosts
def _deploy_cert(self, vhost, cert_path, key_path, chain_path, fullchain_path):
"""
Helper function for deploy_cert() that handles the actual deployment
@@ -404,8 +444,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
domain originally passed for deploy_cert(). This is especially true
with wildcard certificates
"""
# This is done first so that ssl module is enabled and cert_path,
# cert_key... can all be parsed appropriately
self.prepare_server_https("443")
@@ -545,8 +583,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.assoc[target_name] = vhost
return vhost
def included_in_wildcard(self, names, target_name):
"""Is target_name covered by a wildcard?
def domain_in_names(self, names, target_name):
"""Checks if target domain is covered by one or more of the provided
names. The target name is matched by wildcard as well as exact match.
:param names: server aliases
:type names: `collections.Iterable` of `str`
@@ -617,7 +656,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
names = vhost.get_names()
if target_name in names:
points = 3
elif self.included_in_wildcard(names, target_name):
elif self.domain_in_names(names, target_name):
points = 2
elif any(addr.get_addr() == target_name for addr in vhost.addrs):
points = 1
@@ -659,7 +698,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:rtype: set
"""
all_names = set()
all_names = set() # type: Set[str]
vhost_macro = []
@@ -800,8 +839,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
# Search base config, and all included paths for VirtualHosts
file_paths = {}
internal_paths = defaultdict(set)
file_paths = {} # type: Dict[str, str]
internal_paths = defaultdict(set) # type: DefaultDict[str, Set[str]]
vhs = []
# Make a list of parser paths because the parser_paths
# dictionary may be modified during the loop.
@@ -1030,36 +1069,19 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:param boolean temp: If the change is temporary
"""
if self.conf("handle-modules"):
if self.option("handle_modules"):
if self.version >= (2, 4) and ("socache_shmcb_module" not in
self.parser.modules):
self.enable_mod("socache_shmcb", temp=temp)
if "ssl_module" not in self.parser.modules:
self.enable_mod("ssl", temp=temp)
def make_addrs_sni_ready(self, addrs):
"""Checks to see if the server is ready for SNI challenges.
:param addrs: Addresses to check SNI compatibility
:type addrs: :class:`~certbot_apache.obj.Addr`
"""
# Version 2.4 and later are automatically SNI ready.
if self.version >= (2, 4):
return
for addr in addrs:
if not self.is_name_vhost(addr):
logger.debug("Setting VirtualHost at %s to be a name "
"based virtual host", addr)
self.add_name_vhost(addr)
def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals
"""Makes an ssl_vhost version of a nonssl_vhost.
Duplicates vhost and adds default ssl options
New vhost will reside as (nonssl_vhost.path) +
``self.constant("le_vhost_ext")``
``self.option("le_vhost_ext")``
.. note:: This function saves the configuration
@@ -1158,18 +1180,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")):
# Defined by user on CLI
fp = os.path.join(os.path.realpath(self.vhostroot),
fp = os.path.join(os.path.realpath(self.option("vhost_root")),
os.path.basename(non_ssl_vh_fp))
else:
# Use non-ssl filepath
fp = os.path.realpath(non_ssl_vh_fp)
if fp.endswith(".conf"):
return fp[:-(len(".conf"))] + self.conf("le_vhost_ext")
return fp[:-(len(".conf"))] + self.option("le_vhost_ext")
else:
return fp + self.conf("le_vhost_ext")
return fp + self.option("le_vhost_ext")
def _sift_rewrite_rule(self, line):
"""Decides whether a line should be copied to a SSL vhost.
@@ -1239,7 +1259,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if not self.parser.parsed_in_current(ssl_fp):
self.parser.parse_file(ssl_fp)
except IOError:
logger.fatal("Error writing/reading to file in make_vhost_ssl")
logger.critical("Error writing/reading to file in make_vhost_ssl", exc_info=True)
raise errors.PluginError("Unable to write/read in make_vhost_ssl")
if sift:
@@ -1327,7 +1347,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
try:
span_val = self.aug.span(vhost.path)
except ValueError:
logger.fatal("Error while reading the VirtualHost %s from "
logger.critical("Error while reading the VirtualHost %s from "
"file %s", vhost.name, vhost.filep, exc_info=True)
raise errors.PluginError("Unable to read VirtualHost from file")
span_filep = span_val[0]
@@ -1433,7 +1453,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
matches = self.parser.find_dir(
"ServerAlias", start=vh_path, exclude=False)
aliases = (self.aug.get(match) for match in matches)
return self.included_in_wildcard(aliases, target_name)
return self.domain_in_names(aliases, target_name)
def _add_name_vhost_if_necessary(self, vhost):
"""Add NameVirtualHost Directives if necessary for new vhost.
@@ -1470,6 +1490,67 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if need_to_save:
self.save()
def find_vhost_by_id(self, id_str):
"""
Searches through VirtualHosts and tries to match the id in a comment
:param str id_str: Id string for matching
:returns: The matched VirtualHost or None
:rtype: :class:`~certbot_apache.obj.VirtualHost` or None
:raises .errors.PluginError: If no VirtualHost is found
"""
for vh in self.vhosts:
if self._find_vhost_id(vh) == id_str:
return vh
msg = "No VirtualHost with ID {} was found.".format(id_str)
logger.warning(msg)
raise errors.PluginError(msg)
def _find_vhost_id(self, vhost):
"""Tries to find the unique ID from the VirtualHost comments. This is
used for keeping track of VirtualHost directive over time.
:param vhost: Virtual host to add the id
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
:returns: The unique ID or None
:rtype: str or None
"""
# Strip the {} off from the format string
search_comment = constants.MANAGED_COMMENT_ID.format("")
id_comment = self.parser.find_comments(search_comment, vhost.path)
if id_comment:
# Use the first value, multiple ones shouldn't exist
comment = self.parser.get_arg(id_comment[0])
return comment.split(" ")[-1]
return None
def add_vhost_id(self, vhost):
"""Adds an unique ID to the VirtualHost as a comment for mapping back
to it on later invocations, as the config file order might have changed.
If ID already exists, returns that instead.
:param vhost: Virtual host to add or find the id
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
:returns: The unique ID for vhost
:rtype: str or None
"""
vh_id = self._find_vhost_id(vhost)
if vh_id:
return vh_id
id_string = apache_util.unique_id()
comment = constants.MANAGED_COMMENT_ID.format(id_string)
self.parser.add_comment(vhost.path, comment)
return id_string
def _escape(self, fp):
fp = fp.replace(",", "\\,")
fp = fp.replace("[", "\\[")
@@ -1529,6 +1610,78 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logger.warning("Failed %s for %s", enhancement, domain)
raise
def _autohsts_increase(self, vhost, id_str, nextstep):
"""Increase the AutoHSTS max-age value
:param vhost: Virtual host object to modify
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
:param str id_str: The unique ID string of VirtualHost
:param int nextstep: Next AutoHSTS max-age value index
"""
nextstep_value = constants.AUTOHSTS_STEPS[nextstep]
self._autohsts_write(vhost, nextstep_value)
self._autohsts[id_str] = {"laststep": nextstep, "timestamp": time.time()}
def _autohsts_write(self, vhost, nextstep_value):
"""
Write the new HSTS max-age value to the VirtualHost file
"""
hsts_dirpath = None
header_path = self.parser.find_dir("Header", None, vhost.path)
if header_path:
pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)'
for match in header_path:
if re.search(pat, self.aug.get(match).lower()):
hsts_dirpath = match
if not hsts_dirpath:
err_msg = ("Certbot was unable to find the existing HSTS header "
"from the VirtualHost at path {0}.").format(vhost.filep)
raise errors.PluginError(err_msg)
# Prepare the HSTS header value
hsts_maxage = "\"max-age={0}\"".format(nextstep_value)
# Update the header
# Our match statement was for string strict-transport-security, but
# we need to update the value instead. The next index is for the value
hsts_dirpath = hsts_dirpath.replace("arg[3]", "arg[4]")
self.aug.set(hsts_dirpath, hsts_maxage)
note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost "
"in {1}\n".format(nextstep_value, vhost.filep))
logger.debug(note_msg)
self.save_notes += note_msg
self.save(note_msg)
def _autohsts_fetch_state(self):
"""
Populates the AutoHSTS state from the pluginstorage
"""
try:
self._autohsts = self.storage.fetch("autohsts")
except KeyError:
self._autohsts = dict()
def _autohsts_save_state(self):
"""
Saves the state of AutoHSTS object to pluginstorage
"""
self.storage.put("autohsts", self._autohsts)
self.storage.save()
def _autohsts_vhost_in_lineage(self, vhost, lineage):
"""
Searches AutoHSTS managed VirtualHosts that belong to the lineage.
Matches the private key path.
"""
return bool(
self.parser.find_dir("SSLCertificateKeyFile",
lineage.key_path, vhost.path))
def _enable_ocsp_stapling(self, ssl_vhost, unused_options):
"""Enables OCSP Stapling
@@ -1748,7 +1901,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
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.
@@ -1770,7 +1922,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# There can be other RewriteRule directive lines in vhost config.
# 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)
rewrite_args_dict = defaultdict(list) # type: DefaultDict[str, List[str]]
pat = r'(.*directive\[\d+\]).*'
for match in rewrite_path:
m = re.match(pat, match)
@@ -1864,7 +2016,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if ssl_vhost.aliases:
serveralias = "ServerAlias " + " ".join(ssl_vhost.aliases)
rewrite_rule_args = []
rewrite_rule_args = [] # type: List[str]
if self.get_version() >= (2, 3, 9):
rewrite_rule_args = constants.REWRITE_HTTPS_ARGS_WITH_END
else:
@@ -1885,7 +2037,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
addr in self._get_proposed_addrs(ssl_vhost)),
servername, serveralias,
" ".join(rewrite_rule_args),
self.conf("logs-root")))
self.option("logs_root")))
def _write_out_redirect(self, ssl_vhost, text):
# This is the default name
@@ -1897,7 +2049,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if len(ssl_vhost.name) < (255 - (len(redirect_filename) + 1)):
redirect_filename = "le-redirect-%s.conf" % ssl_vhost.name
redirect_filepath = os.path.join(self.vhostroot,
redirect_filepath = os.path.join(self.option("vhost_root"),
redirect_filename)
# Register the new file that will be created
@@ -2016,20 +2168,19 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:raises .errors.MisconfigurationError: If reload fails
"""
error = ""
try:
util.run_script(self.constant("restart_cmd"))
util.run_script(self.option("restart_cmd"))
except errors.SubprocessError as err:
logger.info("Unable to restart apache using %s",
self.constant("restart_cmd"))
alt_restart = self.constant("restart_cmd_alt")
self.option("restart_cmd"))
alt_restart = self.option("restart_cmd_alt")
if alt_restart:
logger.debug("Trying alternative restart command: %s",
alt_restart)
# There is an alternative restart command available
# This usually is "restart" verb while original is "graceful"
try:
util.run_script(self.constant(
util.run_script(self.option(
"restart_cmd_alt"))
return
except errors.SubprocessError as secerr:
@@ -2045,7 +2196,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
util.run_script(self.constant("conftest_cmd"))
util.run_script(self.option("conftest_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@@ -2061,11 +2212,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
try:
stdout, _ = util.run_script(self.constant("version_cmd"))
stdout, _ = util.run_script(self.option("version_cmd"))
except errors.SubprocessError:
raise errors.PluginError(
"Unable to run %s -v" %
self.constant("version_cmd"))
self.option("version_cmd"))
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
matches = regex.findall(stdout)
@@ -2090,7 +2241,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
###########################################################################
def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use
"""Return list of challenge preferences."""
return [challenges.TLSSNI01, challenges.HTTP01]
return [challenges.HTTP01]
def perform(self, achalls):
"""Perform the configuration related challenge.
@@ -2103,20 +2254,15 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self._chall_out.update(achalls)
responses = [None] * len(achalls)
http_doer = http_01.ApacheHttp01(self)
sni_doer = tls_sni_01.ApacheTlsSni01(self)
for i, achall in enumerate(achalls):
# Currently also have chall_doer hold associated index of the
# challenge. This helps to put all of the responses back together
# when they are all complete.
if isinstance(achall.chall, challenges.HTTP01):
http_doer.add_chall(achall, i)
else: # tls-sni-01
sni_doer.add_chall(achall, i)
http_doer.add_chall(achall, i)
http_response = http_doer.perform()
sni_response = sni_doer.perform()
if http_response or sni_response:
if http_response:
# Must reload in order to activate the challenges.
# Handled here because we may be able to load up other challenge
# types
@@ -2127,7 +2273,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
time.sleep(3)
self._update_responses(responses, http_response, http_doer)
self._update_responses(responses, sni_response, sni_doer)
return responses
@@ -2155,4 +2300,181 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# certbot for unprivileged users via setuid), this function will need
# to be modified.
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
self.constant("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
def enable_autohsts(self, _unused_lineage, domains):
"""
Enable the AutoHSTS enhancement for defined domains
:param _unused_lineage: Certificate lineage object, unused
:type _unused_lineage: certbot.storage.RenewableCert
:param domains: List of domains in certificate to enhance
:type domains: str
"""
self._autohsts_fetch_state()
_enhanced_vhosts = []
for d in domains:
matched_vhosts = self.choose_vhosts(d, create_if_no_ssl=False)
# We should be handling only SSL vhosts for AutoHSTS
vhosts = [vhost for vhost in matched_vhosts if vhost.ssl]
if not vhosts:
msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a "
"domain {0} for enabling AutoHSTS enhancement.")
msg = msg_tmpl.format(d)
logger.warning(msg)
raise errors.PluginError(msg)
for vh in vhosts:
try:
self._enable_autohsts_domain(vh)
_enhanced_vhosts.append(vh)
except errors.PluginEnhancementAlreadyPresent:
if vh in _enhanced_vhosts:
continue
msg = ("VirtualHost for domain {0} in file {1} has a " +
"String-Transport-Security header present, exiting.")
raise errors.PluginEnhancementAlreadyPresent(
msg.format(d, vh.filep))
if _enhanced_vhosts:
note_msg = "Enabling AutoHSTS"
self.save(note_msg)
logger.info(note_msg)
self.restart()
# Save the current state to pluginstorage
self._autohsts_save_state()
def _enable_autohsts_domain(self, ssl_vhost):
"""Do the initial AutoHSTS deployment to a vhost
:param ssl_vhost: The VirtualHost object to deploy the AutoHSTS
:type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` or None
:raises errors.PluginEnhancementAlreadyPresent: When already enhanced
"""
# This raises the exception
self._verify_no_matching_http_header(ssl_vhost,
"Strict-Transport-Security")
if "headers_module" not in self.parser.modules:
self.enable_mod("headers")
# Prepare the HSTS header value
hsts_header = constants.HEADER_ARGS["Strict-Transport-Security"][:-1]
initial_maxage = constants.AUTOHSTS_STEPS[0]
hsts_header.append("\"max-age={0}\"".format(initial_maxage))
# Add ID to the VirtualHost for mapping back to it later
uniq_id = self.add_vhost_id(ssl_vhost)
self.save_notes += "Adding unique ID {0} to VirtualHost in {1}\n".format(
uniq_id, ssl_vhost.filep)
# Add the actual HSTS header
self.parser.add_dir(ssl_vhost.path, "Header", hsts_header)
note_msg = ("Adding gradually increasing HSTS header with initial value "
"of {0} to VirtualHost in {1}\n".format(
initial_maxage, ssl_vhost.filep))
self.save_notes += note_msg
# Save the current state to pluginstorage
self._autohsts[uniq_id] = {"laststep": 0, "timestamp": time.time()}
def update_autohsts(self, _unused_domain):
"""
Increase the AutoHSTS values of VirtualHosts that the user has enabled
this enhancement for.
:param _unused_domain: Not currently used
:type _unused_domain: Not Available
"""
self._autohsts_fetch_state()
if not self._autohsts:
# No AutoHSTS enabled for any domain
return
curtime = time.time()
save_and_restart = False
for id_str, config in list(self._autohsts.items()):
if config["timestamp"] + constants.AUTOHSTS_FREQ > curtime:
# Skip if last increase was < AUTOHSTS_FREQ ago
continue
nextstep = config["laststep"] + 1
if nextstep < len(constants.AUTOHSTS_STEPS):
# If installer hasn't been prepared yet, do it now
if not self._prepared:
self.prepare()
# Have not reached the max value yet
try:
vhost = self.find_vhost_by_id(id_str)
except errors.PluginError:
msg = ("Could not find VirtualHost with ID {0}, disabling "
"AutoHSTS for this VirtualHost").format(id_str)
logger.warning(msg)
# Remove the orphaned AutoHSTS entry from pluginstorage
self._autohsts.pop(id_str)
continue
self._autohsts_increase(vhost, id_str, nextstep)
msg = ("Increasing HSTS max-age value for VirtualHost with id "
"{0}").format(id_str)
self.save_notes += msg
save_and_restart = True
if save_and_restart:
self.save("Increased HSTS max-age values")
self.restart()
self._autohsts_save_state()
def deploy_autohsts(self, lineage):
"""
Checks if autohsts vhost has reached maximum auto-increased value
and changes the HSTS max-age to a high value.
:param lineage: Certificate lineage object
:type lineage: certbot.storage.RenewableCert
"""
self._autohsts_fetch_state()
if not self._autohsts:
# No autohsts enabled for any vhost
return
vhosts = []
affected_ids = []
# Copy, as we are removing from the dict inside the loop
for id_str, config in list(self._autohsts.items()):
if config["laststep"]+1 >= len(constants.AUTOHSTS_STEPS):
# max value reached, try to make permanent
try:
vhost = self.find_vhost_by_id(id_str)
except errors.PluginError:
msg = ("VirtualHost with id {} was not found, unable to "
"make HSTS max-age permanent.").format(id_str)
logger.warning(msg)
self._autohsts.pop(id_str)
continue
if self._autohsts_vhost_in_lineage(vhost, lineage):
vhosts.append(vhost)
affected_ids.append(id_str)
save_and_restart = False
for vhost in vhosts:
self._autohsts_write(vhost, constants.AUTOHSTS_PERMANENT)
msg = ("Strict-Transport-Security max-age value for "
"VirtualHost in {0} was made permanent.").format(vhost.filep)
logger.debug(msg)
self.save_notes += msg+"\n"
save_and_restart = True
if save_and_restart:
self.save("Made HSTS max-age permanent")
self.restart()
for id_str in affected_ids:
self._autohsts.pop(id_str)
# Update AutoHSTS storage (We potentially removed vhosts from managed)
self._autohsts_save_state()
AutoHSTSEnhancement.register(ApacheConfigurator) # pylint: disable=no-member

View File

@@ -48,3 +48,16 @@ UIR_ARGS = ["always", "set", "Content-Security-Policy",
HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS,
"Upgrade-Insecure-Requests": UIR_ARGS}
AUTOHSTS_STEPS = [60, 300, 900, 3600, 21600, 43200, 86400]
"""AutoHSTS increase steps: 1min, 5min, 15min, 1h, 6h, 12h, 24h"""
AUTOHSTS_PERMANENT = 31536000
"""Value for the last max-age of HSTS"""
AUTOHSTS_FREQ = 172800
"""Minimum time since last increase to perform a new one: 48h"""
MANAGED_COMMENT = "DO NOT REMOVE - Managed by Certbot"
MANAGED_COMMENT_ID = MANAGED_COMMENT+", VirtualHost id: {0}"
"""Managed by Certbot comments and the VirtualHost identification template"""

View File

@@ -113,8 +113,7 @@ def _vhost_menu(domain, vhosts):
code, tag = zope.component.getUtility(interfaces.IDisplay).menu(
"We were unable to find a vhost with a ServerName "
"or Address of {0}.{1}Which virtual host would you "
"like to choose?\n(note: conf files with multiple "
"vhosts are not yet supported)".format(domain, os.linesep),
"like to choose?".format(domain, os.linesep),
choices, force_interactive=True)
except errors.MissingCommandlineFlag:
msg = (

View File

@@ -2,9 +2,11 @@
import logging
import os
from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module
from certbot import errors
from certbot.plugins import common
from certbot_apache.obj import VirtualHost # pylint: disable=unused-import
from certbot_apache.parser import get_aug_path
logger = logging.getLogger(__name__)
@@ -51,7 +53,7 @@ class ApacheHttp01(common.TLSSNI01):
self.challenge_dir = os.path.join(
self.configurator.config.work_dir,
"http_challenges")
self.moded_vhosts = set()
self.moded_vhosts = set() # type: Set[VirtualHost]
def perform(self):
"""Perform all HTTP-01 challenges."""
@@ -87,15 +89,27 @@ class ApacheHttp01(common.TLSSNI01):
self.configurator.enable_mod(mod, temp=True)
def _mod_config(self):
selected_vhosts = [] # type: List[VirtualHost]
http_port = str(self.configurator.config.http01_port)
for chall in self.achalls:
vh = self.configurator.find_best_http_vhost(
chall.domain, filter_defaults=False,
port=str(self.configurator.config.http01_port))
if vh:
self._set_up_include_directives(vh)
else:
for vh in self._relevant_vhosts():
self._set_up_include_directives(vh)
# Search for matching VirtualHosts
for vh in self._matching_vhosts(chall.domain):
selected_vhosts.append(vh)
# Ensure that we have one or more VirtualHosts that we can continue
# with. (one that listens to port configured with --http-01-port)
found = False
for vhost in selected_vhosts:
if any(a.is_wildcard() or a.get_port() == http_port for a in vhost.addrs):
found = True
if not found:
for vh in self._relevant_vhosts():
selected_vhosts.append(vh)
# Add the challenge configuration
for vh in selected_vhosts:
self._set_up_include_directives(vh)
self.configurator.reverter.register_file_creation(
True, self.challenge_conf_pre)
@@ -119,6 +133,20 @@ class ApacheHttp01(common.TLSSNI01):
with open(self.challenge_conf_post, "w") as new_conf:
new_conf.write(config_text_post)
def _matching_vhosts(self, domain):
"""Return all VirtualHost objects that have the requested domain name or
a wildcard name that would match the domain in ServerName or ServerAlias
directive.
"""
matching_vhosts = []
for vhost in self.configurator.vhosts:
if self.configurator.domain_in_names(vhost.get_names(), domain):
# domain_in_names also matches the exact names, so no need
# to check "domain in vhost.get_names()" explicitly here
matching_vhosts.append(vhost)
return matching_vhosts
def _relevant_vhosts(self):
http01_port = str(self.configurator.config.http01_port)
relevant_vhosts = []
@@ -171,4 +199,9 @@ class ApacheHttp01(common.TLSSNI01):
self.configurator.parser.add_dir(
vhost.path, "Include", self.challenge_conf_post)
if not vhost.enabled:
self.configurator.parser.add_dir(
get_aug_path(self.configurator.parser.loc["default"]),
"Include", vhost.filep)
self.moded_vhosts.add(vhost)

View File

@@ -1,6 +1,7 @@
"""Module contains classes used by the Apache Configurator."""
import re
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from certbot.plugins import common
@@ -140,7 +141,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
def get_names(self):
"""Return a set of all names."""
all_names = set()
all_names = set() # type: Set[str]
all_names.update(self.aliases)
# Strip out any scheme:// and <port> field from servername
if self.name is not None:
@@ -251,7 +252,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
# already_found acts to keep everything very conservative.
# Don't allow multiple ip:ports in same set.
already_found = set()
already_found = set() # type: Set[str]
for addr in vhost.addrs:
for local_addr in self.addrs:

View File

@@ -16,14 +16,14 @@ class ArchConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/httpd/conf",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
apache_cmd="apachectl",
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(

View File

@@ -18,25 +18,33 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
logs_root="/var/log/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
apache_cmd="apachectl",
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", "centos-options-ssl-apache.conf")
)
def _prepare_options(self):
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in CentOS.
"""
super(CentOSConfigurator, self)._prepare_options()
self.options["restart_cmd_alt"][0] = self.option("ctl")
def get_parser(self):
"""Initializes the ApacheParser"""
return CentOSParser(
self.aug, self.conf("server-root"), self.conf("vhost-root"),
self.aug, self.option("server_root"), self.option("vhost_root"),
self.version, configurator=self)
@@ -47,10 +55,10 @@ class CentOSParser(parser.ApacheParser):
self.sysconfig_filep = "/etc/sysconfig/httpd"
super(CentOSParser, self).__init__(*args, **kwargs)
def update_runtime_variables(self, *args, **kwargs):
def update_runtime_variables(self):
""" Override for update_runtime_variables for custom parsing """
# Opportunistic, works if SELinux not enforced
super(CentOSParser, self).update_runtime_variables(*args, **kwargs)
super(CentOSParser, self).update_runtime_variables()
self.parse_sysconfig_var()
def parse_sysconfig_var(self):

View File

@@ -16,14 +16,14 @@ class DarwinConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/other",
vhost_files="*.conf",
logs_root="/var/log/apache2",
version_cmd=['/usr/sbin/httpd', '-v'],
apache_cmd="/usr/sbin/httpd",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/other",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(

View File

@@ -23,14 +23,14 @@ class DebianConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod="a2enmod",
dismod="a2dismod",
le_vhost_ext="-le-ssl.conf",
handle_mods=True,
handle_modules=True,
handle_sites=True,
challenge_location="/etc/apache2",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
@@ -134,11 +134,11 @@ class DebianConfigurator(configurator.ApacheConfigurator):
# Generate reversal command.
# Try to be safe here... check that we can probably reverse before
# applying enmod command
if not util.exe_exists(self.conf("dismod")):
if not util.exe_exists(self.option("dismod")):
raise errors.MisconfigurationError(
"Unable to find a2dismod, please make sure a2enmod and "
"a2dismod are configured correctly for certbot.")
self.reverter.register_undo_command(
temp, [self.conf("dismod"), "-f", mod_name])
util.run_script([self.conf("enmod"), mod_name])
temp, [self.option("dismod"), "-f", mod_name])
util.run_script([self.option("enmod"), mod_name])

View File

@@ -18,25 +18,33 @@ class GentooConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/vhosts.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
version_cmd=['/usr/sbin/apache2', '-v'],
apache_cmd="apache2ctl",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
restart_cmd=['apache2ctl', 'graceful'],
restart_cmd_alt=['apache2ctl', 'restart'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", "options-ssl-apache.conf")
)
def _prepare_options(self):
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in Gentoo.
"""
super(GentooConfigurator, self)._prepare_options()
self.options["restart_cmd_alt"][0] = self.option("ctl")
def get_parser(self):
"""Initializes the ApacheParser"""
return GentooParser(
self.aug, self.conf("server-root"), self.conf("vhost-root"),
self.aug, self.option("server_root"), self.option("vhost_root"),
self.version, configurator=self)
@@ -61,7 +69,7 @@ class GentooParser(parser.ApacheParser):
def update_modules(self):
"""Get loaded modules from httpd process, and add them to DOM"""
mod_cmd = [self.configurator.constant("apache_cmd"), "modules"]
mod_cmd = [self.configurator.option("ctl"), "modules"]
matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module")
for mod in matches:
self.add_mod(mod.strip())

View File

@@ -16,14 +16,14 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator):
vhost_root="/etc/apache2/vhosts.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod="a2enmod",
dismod="a2dismod",
le_vhost_ext="-le-ssl.conf",
handle_mods=False,
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(

View File

@@ -9,12 +9,14 @@ import sys
import six
from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module
from certbot import errors
logger = logging.getLogger(__name__)
class ApacheParser(object):
# pylint: disable=too-many-public-methods
"""Class handles the fine details of parsing the Apache Configuration.
.. todo:: Make parsing general... remove sites-available etc...
@@ -38,9 +40,9 @@ class ApacheParser(object):
# issues with aug.load() after adding new files / defines to parse tree
self.configurator = configurator
self.modules = set()
self.parser_paths = {}
self.variables = {}
self.modules = set() # type: Set[str]
self.parser_paths = {} # type: Dict[str, List[str]]
self.variables = {} # type: Dict[str, str]
self.aug = aug
# Find configuration root and make sure augeas can parse it.
@@ -67,7 +69,7 @@ class ApacheParser(object):
# Must also attempt to parse additional virtual host root
if vhostroot:
self.parse_file(os.path.abspath(vhostroot) + "/" +
self.configurator.constant("vhost_files"))
self.configurator.option("vhost_files"))
# check to see if there were unparsed define statements
if version < (2, 4):
@@ -91,12 +93,7 @@ class ApacheParser(object):
# Add new path to parser paths
new_dir = os.path.dirname(inc_path)
new_file = os.path.basename(inc_path)
if new_dir in self.existing_paths.keys():
# Add to existing path
self.existing_paths[new_dir].append(new_file)
else:
# Create a new path
self.existing_paths[new_dir] = [new_file]
self.existing_paths.setdefault(new_dir, []).append(new_file)
def add_mod(self, mod_name):
"""Shortcut for updating parser modules."""
@@ -119,7 +116,7 @@ class ApacheParser(object):
the iteration issue. Else... parse and enable mods at same time.
"""
mods = set()
mods = set() # type: Set[str]
matches = self.find_dir("LoadModule")
iterator = iter(matches)
# Make sure prev_size != cur_size for do: while: iteration
@@ -150,7 +147,7 @@ class ApacheParser(object):
"""Get Defines from httpd process"""
variables = dict()
define_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
define_cmd = [self.configurator.option("ctl"), "-t", "-D",
"DUMP_RUN_CFG"]
matches = self.parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
try:
@@ -177,7 +174,7 @@ class ApacheParser(object):
# configuration files
_ = self.find_dir("Include")
inc_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
inc_cmd = [self.configurator.option("ctl"), "-t", "-D",
"DUMP_INCLUDES"]
matches = self.parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
if matches:
@@ -188,7 +185,7 @@ class ApacheParser(object):
def update_modules(self):
"""Get loaded modules from httpd process, and add them to DOM"""
mod_cmd = [self.configurator.constant("apache_cmd"), "-t", "-D",
mod_cmd = [self.configurator.option("ctl"), "-t", "-D",
"DUMP_MODULES"]
matches = self.parse_from_subprocess(mod_cmd, r"(.*)_module")
for mod in matches:
@@ -349,6 +346,37 @@ class ApacheParser(object):
else:
self.aug.set(first_dir + "/arg", args)
def add_comment(self, aug_conf_path, comment):
"""Adds the comment to the augeas path
:param str aug_conf_path: Augeas configuration path to add directive
:param str comment: Comment content
"""
self.aug.set(aug_conf_path + "/#comment[last() + 1]", comment)
def find_comments(self, arg, start=None):
"""Finds a comment with specified content from the provided DOM path
:param str arg: Comment content to search
:param str start: Beginning Augeas path to begin looking
:returns: List of augeas paths containing the comment content
:rtype: list
"""
if not start:
start = get_aug_path(self.root)
comments = self.aug.match("%s//*[label() = '#comment']" % start)
results = []
for comment in comments:
c_content = self.aug.get(comment)
if c_content and arg in c_content:
results.append(comment)
return results
def find_dir(self, directive, arg=None, start=None, exclude=True):
"""Finds directive in the configuration.
@@ -408,7 +436,7 @@ class ApacheParser(object):
else:
arg_suffix = "/*[self::arg=~regexp('%s')]" % case_i(arg)
ordered_matches = []
ordered_matches = [] # type: List[str]
# TODO: Wildcards should be included in alphabetical order
# https://httpd.apache.org/docs/2.4/mod/core.html#include

View File

@@ -3,6 +3,11 @@
# A hackish script to see if the client is behaving as expected
# with each of the "passing" conf files.
if [ -z "$SERVER" ]; then
echo "Please set SERVER to the ACME server's directory URL."
exit 1
fi
export EA=/etc/apache2/
TESTDIR="`dirname $0`"
cd $TESTDIR/passing
@@ -46,6 +51,7 @@ function Cleanup() {
# if our environment asks us to enable modules, do our best!
if [ "$1" = --debian-modules ] ; then
sudo apt-get install -y apache2
sudo apt-get install -y libapache2-mod-wsgi
sudo apt-get install -y libapache2-mod-macro
@@ -55,13 +61,16 @@ if [ "$1" = --debian-modules ] ; then
done
fi
CERTBOT_CMD="sudo $(command -v certbot) --server $SERVER -vvvv"
CERTBOT_CMD="$CERTBOT_CMD --debug --apache --register-unsafely-without-email"
CERTBOT_CMD="$CERTBOT_CMD --agree-tos certonly -t --no-verify-ssl"
FAILS=0
trap CleanupExit INT
for f in *.conf ; do
echo -n testing "$f"...
Setup
RESULT=`echo c | sudo $(command -v certbot) -vvvv --debug --staging --apache --register-unsafely-without-email --agree-tos certonly -t 2>&1`
RESULT=`echo c | $CERTBOT_CMD 2>&1`
if echo $RESULT | grep -Eq \("Which names would you like"\|"mod_macro is not yet"\) ; then
echo passed
else

View File

@@ -1,7 +1,7 @@
#LoadModule ssl_module modules/mod_ssl.so
Listen 443
<VirtualHost *:443>
Listen 4443
<VirtualHost *:4443>
# The ServerName directive sets the request scheme, hostname and port that
# the server uses to identify itself. This is used when creating
# redirection URLs. In the context of virtual hosts, the ServerName

View File

@@ -0,0 +1,187 @@
# pylint: disable=too-many-public-methods,too-many-lines
"""Test for certbot_apache.configurator AutoHSTS functionality"""
import re
import unittest
import mock
# six is used in mock.patch()
import six # pylint: disable=unused-import
from certbot import errors
from certbot_apache import constants
from certbot_apache.tests import util
class AutoHSTSTest(util.ApacheTest):
"""Tests for AutoHSTS feature"""
# pylint: disable=protected-access
def setUp(self): # pylint: disable=arguments-differ
super(AutoHSTSTest, self).setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
self.config.parser.modules.add("headers_module")
self.config.parser.modules.add("mod_headers.c")
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
def get_autohsts_value(self, vh_path):
""" Get value from Strict-Transport-Security header """
header_path = self.config.parser.find_dir("Header", None, vh_path)
if header_path:
pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)'
for head in header_path:
if re.search(pat, self.config.parser.aug.get(head).lower()):
return self.config.parser.aug.get(head.replace("arg[3]",
"arg[4]"))
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
def test_autohsts_enable_headers_mod(self, mock_enable, _restart):
self.config.parser.modules.discard("headers_module")
self.config.parser.modules.discard("mod_header.c")
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
self.assertTrue(mock_enable.called)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_autohsts_deploy_already_exists(self, _restart):
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
self.assertRaises(errors.PluginEnhancementAlreadyPresent,
self.config.enable_autohsts,
mock.MagicMock(), ["ocspvhost.com"])
@mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.prepare")
def test_autohsts_increase(self, mock_prepare, _mock_restart):
self.config._prepared = False
maxage = "\"max-age={0}\""
initial_val = maxage.format(constants.AUTOHSTS_STEPS[0])
inc_val = maxage.format(constants.AUTOHSTS_STEPS[1])
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Verify initial value
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
initial_val)
# Increase
self.config.update_autohsts(mock.MagicMock())
# Verify increased value
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
inc_val)
self.assertTrue(mock_prepare.called)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.configurator.ApacheConfigurator._autohsts_increase")
def test_autohsts_increase_noop(self, mock_increase, _restart):
maxage = "\"max-age={0}\""
initial_val = maxage.format(constants.AUTOHSTS_STEPS[0])
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Verify initial value
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
initial_val)
self.config.update_autohsts(mock.MagicMock())
# Freq not patched, so value shouldn't increase
self.assertFalse(mock_increase.called)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0)
def test_autohsts_increase_no_header(self, _restart):
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Remove the header
dir_locs = self.config.parser.find_dir("Header", None,
self.vh_truth[7].path)
dir_loc = "/".join(dir_locs[0].split("/")[:-1])
self.config.parser.aug.remove(dir_loc)
self.assertRaises(errors.PluginError,
self.config.update_autohsts,
mock.MagicMock())
@mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_autohsts_increase_and_make_permanent(self, _mock_restart):
maxage = "\"max-age={0}\""
max_val = maxage.format(constants.AUTOHSTS_PERMANENT)
mock_lineage = mock.MagicMock()
mock_lineage.key_path = "/etc/apache2/ssl/key-certbot_15.pem"
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
for i in range(len(constants.AUTOHSTS_STEPS)-1):
# Ensure that value is not made permanent prematurely
self.config.deploy_autohsts(mock_lineage)
self.assertNotEqual(self.get_autohsts_value(self.vh_truth[7].path),
max_val)
self.config.update_autohsts(mock.MagicMock())
# Value should match pre-permanent increment step
cur_val = maxage.format(constants.AUTOHSTS_STEPS[i+1])
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
cur_val)
# Ensure that the value is raised to max
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
maxage.format(constants.AUTOHSTS_STEPS[-1]))
# Make permanent
self.config.deploy_autohsts(mock_lineage)
self.assertEqual(self.get_autohsts_value(self.vh_truth[7].path),
max_val)
def test_autohsts_update_noop(self):
with mock.patch("time.time") as mock_time:
# Time mock is used to make sure that the execution does not
# continue when no autohsts entries exist in pluginstorage
self.config.update_autohsts(mock.MagicMock())
self.assertFalse(mock_time.called)
def test_autohsts_make_permanent_noop(self):
self.config.storage.put = mock.MagicMock()
self.config.deploy_autohsts(mock.MagicMock())
# Make sure that the execution does not continue when no entries in store
self.assertFalse(self.config.storage.put.called)
@mock.patch("certbot_apache.display_ops.select_vhost")
def test_autohsts_no_ssl_vhost(self, mock_select):
mock_select.return_value = self.vh_truth[0]
with mock.patch("certbot_apache.configurator.logger.warning") as mock_log:
self.assertRaises(errors.PluginError,
self.config.enable_autohsts,
mock.MagicMock(), "invalid.example.com")
self.assertTrue(
"Certbot was not able to find SSL" in mock_log.call_args[0][0])
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.add_vhost_id")
def test_autohsts_dont_enhance_twice(self, mock_id, _restart):
mock_id.return_value = "1234567"
self.config.enable_autohsts(mock.MagicMock(),
["ocspvhost.com", "ocspvhost.com"])
self.assertEqual(mock_id.call_count, 1)
def test_autohsts_remove_orphaned(self):
# pylint: disable=protected-access
self.config._autohsts_fetch_state()
self.config._autohsts["orphan_id"] = {"laststep": 0, "timestamp": 0}
self.config._autohsts_save_state()
self.config.update_autohsts(mock.MagicMock())
self.assertFalse("orphan_id" in self.config._autohsts)
# Make sure it's removed from the pluginstorage file as well
self.config._autohsts = None
self.config._autohsts_fetch_state()
self.assertFalse(self.config._autohsts)
def test_autohsts_make_permanent_vhost_not_found(self):
# pylint: disable=protected-access
self.config._autohsts_fetch_state()
self.config._autohsts["orphan_id"] = {"laststep": 999, "timestamp": 0}
self.config._autohsts_save_state()
with mock.patch("certbot_apache.configurator.logger.warning") as mock_log:
self.config.deploy_autohsts(mock.MagicMock())
self.assertTrue(mock_log.called)
self.assertTrue(
"VirtualHost with id orphan_id was not" in mock_log.call_args[0][0])
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -81,9 +81,9 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
mock_osi.return_value = ("centos", "7")
self.config.parser.update_runtime_variables()
self.assertEquals(mock_get.call_count, 3)
self.assertEquals(len(self.config.parser.modules), 4)
self.assertEquals(len(self.config.parser.variables), 2)
self.assertEqual(mock_get.call_count, 3)
self.assertEqual(len(self.config.parser.modules), 4)
self.assertEqual(len(self.config.parser.variables), 2)
self.assertTrue("TEST2" in self.config.parser.variables.keys())
self.assertTrue("mod_another.c" in self.config.parser.modules)
@@ -127,7 +127,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
def test_alt_restart_works(self, mock_run_script):
mock_run_script.side_effect = [None, errors.SubprocessError, None]
self.config.restart()
self.assertEquals(mock_run_script.call_count, 3)
self.assertEqual(mock_run_script.call_count, 3)
@mock.patch("certbot_apache.configurator.util.run_script")
def test_alt_restart_errors(self, mock_run_script):
@@ -135,5 +135,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
errors.SubprocessError,
errors.SubprocessError]
self.assertRaises(errors.MisconfigurationError, self.config.restart)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -115,9 +115,53 @@ class MultipleVhostsTest(util.ApacheTest):
# Weak test..
ApacheConfigurator.add_parser_arguments(mock.MagicMock())
def test_docs_parser_arguments(self):
os.environ["CERTBOT_DOCS"] = "1"
from certbot_apache.configurator import ApacheConfigurator
mock_add = mock.MagicMock()
ApacheConfigurator.add_parser_arguments(mock_add)
parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext",
"vhost_root", "logs_root", "challenge_location",
"handle_modules", "handle_sites", "ctl"]
exp = dict()
for k in ApacheConfigurator.OS_DEFAULTS:
if k in parserargs:
exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k]
# Special cases
exp["vhost-root"] = None
exp["init-script"] = None
found = set()
for call in mock_add.call_args_list:
# init-script is a special case: deprecated argument
if call[0][0] != "init-script":
self.assertEqual(exp[call[0][0]], call[1]['default'])
found.add(call[0][0])
# Make sure that all (and only) the expected values exist
self.assertEqual(len(mock_add.call_args_list), len(found))
for e in exp:
self.assertTrue(e in found)
del os.environ["CERTBOT_DOCS"]
def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use
from certbot_apache.entrypoint import OVERRIDE_CLASSES
for cls in OVERRIDE_CLASSES.values():
cls.add_parser_arguments(mock.MagicMock())
def test_all_configurators_defaults_defined(self):
from certbot_apache.entrypoint import OVERRIDE_CLASSES
from certbot_apache.configurator import ApacheConfigurator
parameters = set(ApacheConfigurator.OS_DEFAULTS.keys())
for cls in OVERRIDE_CLASSES.values():
self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys())))
def test_constant(self):
self.assertEqual(self.config.constant("server_root"), "/etc/apache2")
self.assertEqual(self.config.constant("nonexistent"), None)
self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in
self.config.option("server_root"))
self.assertEqual(self.config.option("nonexistent"), None)
@certbot_util.patch_get_utility()
def test_get_all_names(self, mock_getutility):
@@ -126,7 +170,8 @@ class MultipleVhostsTest(util.ApacheTest):
names = self.config.get_all_names()
self.assertEqual(names, set(
["certbot.demo", "ocspvhost.com", "encryption-example.demo",
"nonsym.link", "vhost.in.rootconf", "www.certbot.demo"]
"nonsym.link", "vhost.in.rootconf", "www.certbot.demo",
"duplicate.example.com"]
))
@certbot_util.patch_get_utility()
@@ -145,8 +190,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.vhosts.append(vhost)
names = self.config.get_all_names()
# Names get filtered, only 5 are returned
self.assertEqual(len(names), 8)
self.assertEqual(len(names), 9)
self.assertTrue("zombo.com" in names)
self.assertTrue("google.com" in names)
self.assertTrue("certbot.demo" in names)
@@ -187,7 +231,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_get_virtual_hosts(self):
"""Make sure all vhosts are being properly found."""
vhs = self.config.get_virtual_hosts()
self.assertEqual(len(vhs), 10)
self.assertEqual(len(vhs), 12)
found = 0
for vhost in vhs:
@@ -198,7 +242,7 @@ class MultipleVhostsTest(util.ApacheTest):
else:
raise Exception("Missed: %s" % vhost) # pragma: no cover
self.assertEqual(found, 10)
self.assertEqual(found, 12)
# Handle case of non-debian layout get_virtual_hosts
with mock.patch(
@@ -206,7 +250,7 @@ class MultipleVhostsTest(util.ApacheTest):
) as mock_conf:
mock_conf.return_value = False
vhs = self.config.get_virtual_hosts()
self.assertEqual(len(vhs), 10)
self.assertEqual(len(vhs), 12)
@mock.patch("certbot_apache.display_ops.select_vhost")
def test_choose_vhost_none_avail(self, mock_select):
@@ -309,7 +353,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.vhosts = [
vh for vh in self.config.vhosts
if vh.name not in ["certbot.demo", "nonsym.link",
"encryption-example.demo",
"encryption-example.demo", "duplicate.example.com",
"ocspvhost.com", "vhost.in.rootconf"]
and "*.blue.purple.com" not in vh.aliases
]
@@ -320,7 +364,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_non_default_vhosts(self):
# pylint: disable=protected-access
vhosts = self.config._non_default_vhosts(self.config.vhosts)
self.assertEqual(len(vhosts), 8)
self.assertEqual(len(vhosts), 10)
def test_deploy_cert_enable_new_vhost(self):
# Create
@@ -353,14 +397,11 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.parser.find_dir = mock_find_dir
mock_add.reset_mock()
self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access
tried_to_add = []
for a in mock_add.call_args_list:
tried_to_add.append(a[0][1] == "Include" and
a[0][2] == self.config.mod_ssl_conf)
# Include shouldn't be added, as patched find_dir "finds" existing one
self.assertFalse(any(tried_to_add))
if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf:
self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \
# pragma: no cover
def test_deploy_cert(self):
self.config.parser.modules.add("ssl_module")
@@ -654,22 +695,10 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(ssl_vhost_slink.name, "nonsym.link")
def test_make_vhost_ssl_nonexistent_vhost_path(self):
def conf_side_effect(arg):
""" Mock function for ApacheConfigurator.conf """
confvars = {
"vhost-root": "/tmp/nonexistent",
"le_vhost_ext": "-le-ssl.conf",
"handle-sites": True}
return confvars[arg]
with mock.patch(
"certbot_apache.configurator.ApacheConfigurator.conf"
) as mock_conf:
mock_conf.side_effect = conf_side_effect
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
self.assertEqual(os.path.dirname(ssl_vhost.filep),
os.path.dirname(os.path.realpath(
self.vh_truth[1].filep)))
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
self.assertEqual(os.path.dirname(ssl_vhost.filep),
os.path.dirname(os.path.realpath(
self.vh_truth[1].filep)))
def test_make_vhost_ssl(self):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
@@ -690,7 +719,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]),
self.config.is_name_vhost(ssl_vhost))
self.assertEqual(len(self.config.vhosts), 11)
self.assertEqual(len(self.config.vhosts), 13)
def test_clean_vhost_ssl(self):
# pylint: disable=protected-access
@@ -783,32 +812,19 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(self.config.add_name_vhost.call_count, 2)
@mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform")
@mock.patch("certbot_apache.configurator.tls_sni_01.ApacheTlsSni01.perform")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_perform(self, mock_restart, mock_tls_perform, mock_http_perform):
def test_perform(self, mock_restart, mock_http_perform):
# Only tests functionality specific to configurator.perform
# Note: As more challenges are offered this will have to be expanded
account_key, achalls = self.get_key_and_achalls()
all_expected = []
http_expected = []
tls_expected = []
for achall in achalls:
response = achall.response(account_key)
if isinstance(achall.chall, challenges.HTTP01):
http_expected.append(response)
else:
tls_expected.append(response)
all_expected.append(response)
mock_http_perform.return_value = http_expected
mock_tls_perform.return_value = tls_expected
expected = [achall.response(account_key) for achall in achalls]
mock_http_perform.return_value = expected
responses = self.config.perform(achalls)
self.assertEqual(mock_http_perform.call_count, 1)
self.assertEqual(mock_tls_perform.call_count, 1)
self.assertEqual(responses, all_expected)
self.assertEqual(responses, expected)
self.assertEqual(mock_restart.call_count, 1)
@@ -1271,7 +1287,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.config._enable_redirect(self.vh_truth[1], "")
self.assertEqual(len(self.config.vhosts), 11)
self.assertEqual(len(self.config.vhosts), 13)
def test_create_own_redirect_for_old_apache_version(self):
self.config.parser.modules.add("rewrite_module")
@@ -1282,7 +1298,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.config._enable_redirect(self.vh_truth[1], "")
self.assertEqual(len(self.config.vhosts), 11)
self.assertEqual(len(self.config.vhosts), 13)
def test_sift_rewrite_rule(self):
# pylint: disable=protected-access
@@ -1320,15 +1336,6 @@ class MultipleVhostsTest(util.ApacheTest):
return account_key, (achall1, achall2, achall3)
def test_make_addrs_sni_ready(self):
self.config.version = (2, 2)
self.config.make_addrs_sni_ready(
set([obj.Addr.fromstring("*:443"), obj.Addr.fromstring("*:80")]))
self.assertTrue(self.config.parser.find_dir(
"NameVirtualHost", "*:80", exclude=False))
self.assertTrue(self.config.parser.find_dir(
"NameVirtualHost", "*:443", exclude=False))
def test_aug_version(self):
mock_match = mock.Mock(return_value=["something"])
self.config.aug.match = mock_match
@@ -1404,11 +1411,11 @@ class MultipleVhostsTest(util.ApacheTest):
vhs = self.config._choose_vhosts_wildcard("*.certbot.demo",
create_ssl=True)
# Check that the dialog was called with one vh: certbot.demo
self.assertEquals(mock_select_vhs.call_args[0][0][0], self.vh_truth[3])
self.assertEquals(len(mock_select_vhs.call_args_list), 1)
self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[3])
self.assertEqual(len(mock_select_vhs.call_args_list), 1)
# And the actual returned values
self.assertEquals(len(vhs), 1)
self.assertEqual(len(vhs), 1)
self.assertTrue(vhs[0].name == "certbot.demo")
self.assertTrue(vhs[0].ssl)
@@ -1423,7 +1430,7 @@ class MultipleVhostsTest(util.ApacheTest):
vhs = self.config._choose_vhosts_wildcard("*.certbot.demo",
create_ssl=False)
self.assertFalse(mock_makessl.called)
self.assertEquals(vhs[0], self.vh_truth[1])
self.assertEqual(vhs[0], self.vh_truth[1])
@mock.patch("certbot_apache.configurator.ApacheConfigurator._vhosts_for_wildcard")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.make_vhost_ssl")
@@ -1436,15 +1443,15 @@ class MultipleVhostsTest(util.ApacheTest):
mock_select_vhs.return_value = [self.vh_truth[7]]
vhs = self.config._choose_vhosts_wildcard("whatever",
create_ssl=True)
self.assertEquals(mock_select_vhs.call_args[0][0][0], self.vh_truth[7])
self.assertEquals(len(mock_select_vhs.call_args_list), 1)
self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[7])
self.assertEqual(len(mock_select_vhs.call_args_list), 1)
# Ensure that make_vhost_ssl was not called, vhost.ssl == true
self.assertFalse(mock_makessl.called)
# And the actual returned values
self.assertEquals(len(vhs), 1)
self.assertEqual(len(vhs), 1)
self.assertTrue(vhs[0].ssl)
self.assertEquals(vhs[0], self.vh_truth[7])
self.assertEqual(vhs[0], self.vh_truth[7])
def test_deploy_cert_wildcard(self):
@@ -1457,7 +1464,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.deploy_cert("*.wildcard.example.org", "/tmp/path",
"/tmp/path", "/tmp/path", "/tmp/path")
self.assertTrue(mock_dep.called)
self.assertEquals(len(mock_dep.call_args_list), 1)
self.assertEqual(len(mock_dep.call_args_list), 1)
self.assertEqual(self.vh_truth[7], mock_dep.call_args_list[0][0][0])
@mock.patch("certbot_apache.display_ops.select_vhost_multiple")
@@ -1490,6 +1497,21 @@ class MultipleVhostsTest(util.ApacheTest):
"Upgrade-Insecure-Requests")
self.assertTrue(mock_choose.called)
def test_add_vhost_id(self):
for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]:
vh_id = self.config.add_vhost_id(vh)
self.assertEqual(vh, self.config.find_vhost_by_id(vh_id))
def test_find_vhost_by_id_404(self):
self.assertRaises(errors.PluginError,
self.config.find_vhost_by_id,
"nonexistent")
def test_add_vhost_id_already_exists(self):
first_id = self.config.add_vhost_id(self.vh_truth[0])
second_id = self.config.add_vhost_id(self.vh_truth[0])
self.assertEqual(first_id, second_id)
class AugeasVhostsTest(util.ApacheTest):
"""Test vhosts with illegal names dependent on augeas version."""
@@ -1510,12 +1532,12 @@ class AugeasVhostsTest(util.ApacheTest):
def test_choosevhost_with_illegal_name(self):
self.config.aug = mock.MagicMock()
self.config.aug.match.side_effect = RuntimeError
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old,default.conf"
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
chosen_vhost = self.config._create_vhost(path)
self.assertEqual(None, chosen_vhost)
def test_choosevhost_works(self):
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old,default.conf"
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
chosen_vhost = self.config._create_vhost(path)
self.assertTrue(chosen_vhost == None or chosen_vhost.path == path)
@@ -1571,7 +1593,7 @@ class AugeasVhostsTest(util.ApacheTest):
broken_vhost)
class MultiVhostsTest(util.ApacheTest):
"""Test vhosts with illegal names dependent on augeas version."""
"""Test configuration with multiple virtualhosts in a single file."""
# pylint: disable=protected-access
def setUp(self): # pylint: disable=arguments-differ
@@ -1638,7 +1660,8 @@ class MultiVhostsTest(util.ApacheTest):
self.assertTrue(self.config.parser.find_dir(
"RewriteEngine", "on", ssl_vhost.path, False))
conf_text = open(ssl_vhost.filep).read()
with open(ssl_vhost.filep) as the_file:
conf_text = the_file.read()
commented_rewrite_rule = ("# RewriteRule \"^/secrets/(.+)\" "
"\"https://new.example.com/docs/$1\" [R,L]")
uncommented_rewrite_rule = ("RewriteRule \"^/docs/(.+)\" "
@@ -1654,7 +1677,8 @@ class MultiVhostsTest(util.ApacheTest):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
conf_lines = open(ssl_vhost.filep).readlines()
with open(ssl_vhost.filep) as the_file:
conf_lines = the_file.readlines()
conf_line_set = [l.strip() for l in conf_lines]
not_commented_cond1 = ("RewriteCond "
"%{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f")
@@ -1691,7 +1715,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.config.updated_mod_ssl_conf_digest)
def _current_ssl_options_hash(self):
return crypto_util.sha256sum(self.config.constant("MOD_SSL_CONF_SRC"))
return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC"))
def _assert_current_file(self):
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
@@ -1727,7 +1751,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.assertFalse(mock_logger.warning.called)
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
self.assertEqual(crypto_util.sha256sum(
self.config.constant("MOD_SSL_CONF_SRC")),
self.config.option("MOD_SSL_CONF_SRC")),
self._current_ssl_options_hash())
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
self._current_ssl_options_hash())
@@ -1743,7 +1767,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
"%s has been manually modified; updated file "
"saved to %s. We recommend updating %s for security purposes.")
self.assertEqual(crypto_util.sha256sum(
self.config.constant("MOD_SSL_CONF_SRC")),
self.config.option("MOD_SSL_CONF_SRC")),
self._current_ssl_options_hash())
# only print warning once
with mock.patch("certbot.plugins.common.logger") as mock_logger:

View File

@@ -20,7 +20,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ
super(MultipleVhostsTestDebian, self).setUp()
self.config = util.get_apache_configurator(
self.config_path, None, self.config_dir, self.work_dir,
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="debian")
self.config = self.mock_deploy_cert(self.config)
self.vh_truth = util.get_vh_truth(self.temp_dir,

View File

@@ -117,19 +117,19 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
self.config.parser.modules = set()
with mock.patch("certbot.util.get_os_info") as mock_osi:
# Make sure we have the have the CentOS httpd constants
# Make sure we have the have the Gentoo httpd constants
mock_osi.return_value = ("gentoo", "123")
self.config.parser.update_runtime_variables()
self.assertEquals(mock_get.call_count, 1)
self.assertEquals(len(self.config.parser.modules), 4)
self.assertEqual(mock_get.call_count, 1)
self.assertEqual(len(self.config.parser.modules), 4)
self.assertTrue("mod_another.c" in self.config.parser.modules)
@mock.patch("certbot_apache.configurator.util.run_script")
def test_alt_restart_works(self, mock_run_script):
mock_run_script.side_effect = [None, errors.SubprocessError, None]
self.config.restart()
self.assertEquals(mock_run_script.call_count, 3)
self.assertEqual(mock_run_script.call_count, 3)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -4,12 +4,13 @@ import os
import unittest
from acme import challenges
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges
from certbot import errors
from certbot.tests import acme_util
from certbot_apache.parser import get_aug_path
from certbot_apache.tests import util
@@ -23,11 +24,11 @@ class ApacheHttp01Test(util.ApacheTest):
super(ApacheHttp01Test, self).setUp(*args, **kwargs)
self.account_key = self.rsa512jwk
self.achalls = []
self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge]
vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
# Takes the vhosts for encryption-example.demo, certbot.demo, and
# vhost.in.rootconf
# Takes the vhosts for encryption-example.demo, certbot.demo
# and vhost.in.rootconf
self.vhosts = [vh_truth[0], vh_truth[3], vh_truth[10]]
for i in range(NUM_ACHALLS):
@@ -38,7 +39,7 @@ class ApacheHttp01Test(util.ApacheTest):
"pending"),
domain=self.vhosts[i].name, account_key=self.account_key))
modules = ["rewrite", "authz_core", "authz_host"]
modules = ["ssl", "rewrite", "authz_core", "authz_host"]
for mod in modules:
self.config.parser.modules.add("mod_{0}.c".format(mod))
self.config.parser.modules.add(mod + "_module")
@@ -110,6 +111,17 @@ class ApacheHttp01Test(util.ApacheTest):
domain="something.nonexistent", account_key=self.account_key)]
self.common_perform_test(achalls, vhosts)
def test_configure_multiple_vhosts(self):
vhosts = [v for v in self.config.vhosts if "duplicate.example.com" in v.get_names()]
self.assertEqual(len(vhosts), 2)
achalls = [
achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.chall_to_challb(
challenges.HTTP01(token=((b'a' * 16))),
"pending"),
domain="duplicate.example.com", account_key=self.account_key)]
self.common_perform_test(achalls, vhosts)
def test_no_vhost(self):
for achall in self.achalls:
self.http.add_chall(achall)
@@ -134,6 +146,21 @@ class ApacheHttp01Test(util.ApacheTest):
def test_perform_3_achall_apache_2_4(self):
self.combinations_perform_test(num_achalls=3, minor_version=4)
def test_activate_disabled_vhost(self):
vhosts = [v for v in self.config.vhosts if v.name == "certbot.demo"]
achalls = [
achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.chall_to_challb(
challenges.HTTP01(token=((b'a' * 16))),
"pending"),
domain="certbot.demo", account_key=self.account_key)]
vhosts[0].enabled = False
self.common_perform_test(achalls, vhosts)
matches = self.config.parser.find_dir(
"Include", vhosts[0].filep,
get_aug_path(self.config.parser.loc["default"]))
self.assertEqual(len(matches), 1)
def combinations_perform_test(self, num_achalls, minor_version):
"""Test perform with the given achall count and Apache version."""
achalls = self.achalls[:num_achalls]
@@ -160,15 +187,14 @@ class ApacheHttp01Test(util.ApacheTest):
self._test_challenge_file(achall)
for vhost in vhosts:
if not vhost.ssl:
matches = self.config.parser.find_dir("Include",
self.http.challenge_conf_pre,
vhost.path)
self.assertEqual(len(matches), 1)
matches = self.config.parser.find_dir("Include",
self.http.challenge_conf_post,
vhost.path)
self.assertEqual(len(matches), 1)
matches = self.config.parser.find_dir("Include",
self.http.challenge_conf_pre,
vhost.path)
self.assertEqual(len(matches), 1)
matches = self.config.parser.find_dir("Include",
self.http.challenge_conf_post,
vhost.path)
self.assertEqual(len(matches), 1)
self.assertTrue(os.path.exists(challenge_dir))

View File

@@ -52,7 +52,7 @@ class BasicParserTest(util.ParserTest):
test2 = self.parser.find_dir("documentroot")
self.assertEqual(len(test), 1)
self.assertEqual(len(test2), 7)
self.assertEqual(len(test2), 8)
def test_add_dir(self):
aug_default = "/files" + self.parser.loc["default"]
@@ -84,7 +84,7 @@ class BasicParserTest(util.ParserTest):
self.assertEqual(self.parser.aug.get(match), str(i + 1))
def test_empty_arg(self):
self.assertEquals(None,
self.assertEqual(None,
self.parser.get_arg("/files/whatever/nonexistent"))
def test_add_dir_to_ifmodssl(self):
@@ -282,11 +282,11 @@ class BasicParserTest(util.ParserTest):
self.assertRaises(
errors.PluginError, self.parser.update_runtime_variables)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.constant")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.option")
@mock.patch("certbot_apache.parser.subprocess.Popen")
def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_const):
def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt):
mock_popen.side_effect = OSError
mock_const.return_value = "nonexistent"
mock_opt.return_value = "nonexistent"
self.assertRaises(
errors.MisconfigurationError,
self.parser.update_runtime_variables)
@@ -299,6 +299,13 @@ class BasicParserTest(util.ParserTest):
errors.MisconfigurationError,
self.parser.update_runtime_variables)
def test_add_comment(self):
from certbot_apache.parser import get_aug_path
self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456")
comm = self.parser.find_comments("123456")
self.assertEqual(len(comm), 1)
self.assertTrue(self.parser.loc["name"] in comm[0])
class ParserInitTest(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ

View File

@@ -0,0 +1 @@
../sites-available/old-and-default.conf

View File

@@ -0,0 +1,9 @@
<VirtualHost 10.2.3.4:80>
ServerName duplicate.example.com
ServerAdmin webmaster@certbot.demo
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>

View File

@@ -0,0 +1,14 @@
<IfModule mod_ssl.c>
<VirtualHost 10.2.3.4:443>
ServerName duplicate.example.com
ServerAdmin webmaster@certbot.demo
DocumentRoot /var/www/html
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
SSLCertificateFile /etc/apache2/certs/certbot-cert_5.pem
SSLCertificateKeyFile /etc/apache2/ssl/key-certbot_15.pem
</VirtualHost>
</IfModule>

View File

@@ -0,0 +1 @@
../sites-available/duplicatehttp.conf

View File

@@ -0,0 +1 @@
../sites-available/duplicatehttps.conf

View File

@@ -1,151 +0,0 @@
"""Test for certbot_apache.tls_sni_01."""
import shutil
import unittest
import mock
from certbot import errors
from certbot.plugins import common_test
from certbot_apache import obj
from certbot_apache.tests import util
from six.moves import xrange # pylint: disable=redefined-builtin, import-error
class TlsSniPerformTest(util.ApacheTest):
"""Test the ApacheTlsSni01 challenge."""
auth_key = common_test.AUTH_KEY
achalls = common_test.ACHALLS
def setUp(self): # pylint: disable=arguments-differ
super(TlsSniPerformTest, self).setUp()
config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir,
self.work_dir)
config.config.tls_sni_01_port = 443
from certbot_apache import tls_sni_01
self.sni = tls_sni_01.ApacheTlsSni01(config)
def tearDown(self):
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
def test_perform0(self):
resp = self.sni.perform()
self.assertEqual(len(resp), 0)
@mock.patch("certbot.util.exe_exists")
@mock.patch("certbot.util.run_script")
def test_perform1(self, _, mock_exists):
self.sni.configurator.parser.modules.add("socache_shmcb_module")
self.sni.configurator.parser.modules.add("ssl_module")
mock_exists.return_value = True
self.sni.configurator.parser.update_runtime_variables = mock.Mock()
achall = self.achalls[0]
self.sni.add_chall(achall)
response = self.achalls[0].response(self.auth_key)
mock_setup_cert = mock.MagicMock(return_value=response)
# pylint: disable=protected-access
self.sni._setup_challenge_cert = mock_setup_cert
responses = self.sni.perform()
mock_setup_cert.assert_called_once_with(achall)
# Check to make sure challenge config path is included in apache config
self.assertEqual(
len(self.sni.configurator.parser.find_dir(
"Include", self.sni.challenge_conf)), 1)
self.assertEqual(len(responses), 1)
self.assertEqual(responses[0], response)
def test_perform2(self):
# Avoid load module
self.sni.configurator.parser.modules.add("ssl_module")
self.sni.configurator.parser.modules.add("socache_shmcb_module")
acme_responses = []
for achall in self.achalls:
self.sni.add_chall(achall)
acme_responses.append(achall.response(self.auth_key))
mock_setup_cert = mock.MagicMock(side_effect=acme_responses)
# pylint: disable=protected-access
self.sni._setup_challenge_cert = mock_setup_cert
with mock.patch(
"certbot_apache.override_debian.DebianConfigurator.enable_mod"):
sni_responses = self.sni.perform()
self.assertEqual(mock_setup_cert.call_count, 2)
# Make sure calls made to mocked function were correct
self.assertEqual(
mock_setup_cert.call_args_list[0], mock.call(self.achalls[0]))
self.assertEqual(
mock_setup_cert.call_args_list[1], mock.call(self.achalls[1]))
self.assertEqual(
len(self.sni.configurator.parser.find_dir(
"Include", self.sni.challenge_conf)),
1)
self.assertEqual(len(sni_responses), 2)
for i in xrange(2):
self.assertEqual(sni_responses[i], acme_responses[i])
def test_mod_config(self):
z_domains = []
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.decode('ascii')]))
self.sni._mod_config() # pylint: disable=protected-access
self.sni.configurator.save()
self.sni.configurator.parser.find_dir(
"Include", self.sni.challenge_conf)
vh_match = self.sni.configurator.aug.match(
"/files" + self.sni.challenge_conf + "//VirtualHost")
vhs = []
for match in vh_match:
# pylint: disable=protected-access
vhs.append(self.sni.configurator._create_vhost(match))
self.assertEqual(len(vhs), 2)
for vhost in vhs:
self.assertEqual(vhost.addrs, set([obj.Addr.fromstring("*:443")]))
names = vhost.get_names()
self.assertTrue(names in z_domains)
def test_get_addrs_default(self):
self.sni.configurator.choose_vhost = mock.Mock(
return_value=obj.VirtualHost(
"path", "aug_path",
set([obj.Addr.fromstring("_default_:443")]),
False, False)
)
# pylint: disable=protected-access
self.assertEqual(
set([obj.Addr.fromstring("*:443")]),
self.sni._get_addrs(self.achalls[0]))
def test_get_addrs_no_vhost_found(self):
self.sni.configurator.choose_vhost = mock.Mock(
side_effect=errors.MissingCommandlineFlag(
"Failed to run Apache plugin non-interactively"))
# pylint: disable=protected-access
self.assertEqual(
set([obj.Addr.fromstring("*:443")]),
self.sni._get_addrs(self.achalls[0]))
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -87,7 +87,6 @@ class ParserTest(ApacheTest):
def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals
config_path, vhost_path,
config_dir, work_dir, version=(2, 4, 7),
conf=None,
os_info="generic",
conf_vhost_path=None):
"""Create an Apache Configurator with the specified options.
@@ -98,9 +97,10 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc
backups = os.path.join(work_dir, "backups")
mock_le_config = mock.MagicMock(
apache_server_root=config_path,
apache_vhost_root=conf_vhost_path,
apache_vhost_root=None,
apache_le_vhost_ext="-le-ssl.conf",
apache_challenge_location=config_path,
apache_enmod=None,
backup_dir=backups,
config_dir=config_dir,
http01_port=80,
@@ -108,37 +108,25 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
work_dir=work_dir)
orig_os_constant = configurator.ApacheConfigurator(mock_le_config,
name="apache",
version=version).constant
def mock_os_constant(key, vhost_path=vhost_path):
"""Mock default vhost path"""
if key == "vhost_root":
return vhost_path
else:
return orig_os_constant(key)
with mock.patch("certbot_apache.configurator.ApacheConfigurator.constant") as mock_cons:
mock_cons.side_effect = mock_os_constant
with mock.patch("certbot_apache.configurator.util.run_script"):
with mock.patch("certbot_apache.configurator.util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
with mock.patch("certbot_apache.parser.ApacheParser."
"update_runtime_variables"):
try:
config_class = entrypoint.OVERRIDE_CLASSES[os_info]
except KeyError:
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version)
# This allows testing scripts to set it a bit more
# quickly
if conf is not None:
config.conf = conf # pragma: no cover
config.prepare()
with mock.patch("certbot_apache.configurator.util.run_script"):
with mock.patch("certbot_apache.configurator.util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
with mock.patch("certbot_apache.parser.ApacheParser."
"update_runtime_variables"):
try:
config_class = entrypoint.OVERRIDE_CLASSES[os_info]
except KeyError:
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version)
if not conf_vhost_path:
config_class.OS_DEFAULTS["vhost_root"] = vhost_path
else:
# Custom virtualhost path was requested
config.config.apache_vhost_root = conf_vhost_path
config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"]
config.prepare()
return config
@@ -208,7 +196,17 @@ def get_vh_truth(temp_dir, config_name):
"/files" + os.path.join(temp_dir, config_name,
"apache2/apache2.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), False, True,
"vhost.in.rootconf")]
"vhost.in.rootconf"),
obj.VirtualHost(
os.path.join(prefix, "duplicatehttp.conf"),
os.path.join(aug_pre, "duplicatehttp.conf/VirtualHost"),
set([obj.Addr.fromstring("10.2.3.4:80")]), False, True,
"duplicate.example.com"),
obj.VirtualHost(
os.path.join(prefix, "duplicatehttps.conf"),
os.path.join(aug_pre, "duplicatehttps.conf/IfModule/VirtualHost"),
set([obj.Addr.fromstring("10.2.3.4:443")]), True, True,
"duplicate.example.com")]
return vh_truth
if config_name == "debian_apache_2_4/multi_vhosts":
prefix = os.path.join(

View File

@@ -1,173 +0,0 @@
"""A class that performs TLS-SNI-01 challenges for Apache"""
import os
import logging
from certbot.plugins import common
from certbot.errors import PluginError, MissingCommandlineFlag
from certbot_apache import obj
logger = logging.getLogger(__name__)
class ApacheTlsSni01(common.TLSSNI01):
"""Class that performs TLS-SNI-01 challenges within the Apache configurator
:ivar configurator: ApacheConfigurator object
:type configurator: :class:`~apache.configurator.ApacheConfigurator`
:ivar list achalls: Annotated TLS-SNI-01
(`.KeyAuthorizationAnnotatedChallenge`) challenges.
:param list indices: Meant to hold indices of challenges in a
larger array. ApacheTlsSni01 is capable of solving many challenges
at once which causes an indexing issue within ApacheConfigurator
who must return all responses in order. Imagine ApacheConfigurator
maintaining state about where all of the http-01 Challenges,
TLS-SNI-01 Challenges belong in the response array. This is an
optional utility.
:param str challenge_conf: location of the challenge config file
"""
VHOST_TEMPLATE = """\
<VirtualHost {vhost}>
ServerName {server_name}
UseCanonicalName on
SSLStrictSNIVHostCheck on
LimitRequestBody 1048576
Include {ssl_options_conf_path}
SSLCertificateFile {cert_path}
SSLCertificateKeyFile {key_path}
DocumentRoot {document_root}
</VirtualHost>
"""
def __init__(self, *args, **kwargs):
super(ApacheTlsSni01, self).__init__(*args, **kwargs)
self.challenge_conf = os.path.join(
self.configurator.conf("challenge-location"),
"le_tls_sni_01_cert_challenge.conf")
def perform(self):
"""Perform a TLS-SNI-01 challenge."""
if not self.achalls:
return []
# Save any changes to the configuration as a precaution
# About to make temporary changes to the config
self.configurator.save("Changes before challenge setup", True)
# Prepare the server for HTTPS
self.configurator.prepare_server_https(
str(self.configurator.config.tls_sni_01_port), True)
responses = []
# Create all of the challenge certs
for achall in self.achalls:
responses.append(self._setup_challenge_cert(achall))
# Setup the configuration
addrs = self._mod_config()
self.configurator.save("Don't lose mod_config changes", True)
self.configurator.make_addrs_sni_ready(addrs)
# Save reversible changes
self.configurator.save("SNI Challenge", True)
return responses
def _mod_config(self):
"""Modifies Apache config files to include challenge vhosts.
Result: Apache config includes virtual servers for issued challs
:returns: All TLS-SNI-01 addresses used
:rtype: set
"""
addrs = set()
config_text = "<IfModule mod_ssl.c>\n"
for achall in self.achalls:
achall_addrs = self._get_addrs(achall)
addrs.update(achall_addrs)
config_text += self._get_config_text(achall, achall_addrs)
config_text += "</IfModule>\n"
self.configurator.parser.add_include(
self.configurator.parser.loc["default"], self.challenge_conf)
self.configurator.reverter.register_file_creation(
True, self.challenge_conf)
logger.debug("writing a config file with text:\n %s", config_text)
with open(self.challenge_conf, "w") as new_conf:
new_conf.write(config_text)
return addrs
def _get_addrs(self, achall):
"""Return the Apache addresses needed for TLS-SNI-01."""
# TODO: Checkout _default_ rules.
addrs = set()
default_addr = obj.Addr(("*", str(
self.configurator.config.tls_sni_01_port)))
try:
vhost = self.configurator.choose_vhost(achall.domain,
create_if_no_ssl=False)
except (PluginError, MissingCommandlineFlag):
# We couldn't find the virtualhost for this domain, possibly
# because it's a new vhost that's not configured yet
# (GH #677). See also GH #2600.
logger.warning("Falling back to default vhost %s...", default_addr)
addrs.add(default_addr)
return addrs
for addr in vhost.addrs:
if "_default_" == addr.get_addr():
addrs.add(default_addr)
else:
addrs.add(
addr.get_sni_addr(
self.configurator.config.tls_sni_01_port))
return addrs
def _get_config_text(self, achall, ip_addrs):
"""Chocolate virtual server configuration text
:param .KeyAuthorizationAnnotatedChallenge achall: Annotated
TLS-SNI-01 challenge.
:param list ip_addrs: addresses of challenged domain
:class:`list` of type `~.obj.Addr`
:returns: virtual host configuration text
:rtype: str
"""
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 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.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),
document_root=document_root).replace("\n", os.linesep)

View File

@@ -1,2 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1
acme[dev]==0.25.0
certbot[dev]==0.26.0

View File

@@ -1,16 +1,16 @@
import sys
from setuptools import setup
from setuptools import find_packages
from setuptools.command.test import test as TestCommand
import sys
version = '0.24.0.dev0'
version = '0.33.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'acme>=0.25.0',
'certbot>=0.26.0',
'mock',
'python-augeas',
'setuptools',
@@ -23,6 +23,22 @@ docs_extras = [
'sphinx_rtd_theme',
]
class PyTest(TestCommand):
user_options = []
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ''
def run_tests(self):
import shlex
# import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(shlex.split(self.pytest_args))
sys.exit(errno)
setup(
name='certbot-apache',
version=version,
@@ -33,7 +49,7 @@ setup(
license='Apache License 2.0',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[
'Development Status :: 3 - Alpha',
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
@@ -45,6 +61,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',
@@ -65,4 +82,6 @@ setup(
],
},
test_suite='certbot_apache',
tests_require=["pytest"],
cmdclass={"test": PyTest},
)

View File

@@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.23.0"
LE_AUTO_VERSION="0.32.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -195,7 +195,7 @@ if [ "$1" = "--cb-auto-has-root" ]; then
else
SetRootAuthMechanism
if [ -n "$SUDO" ]; then
echo "Requesting to rerun $0 with root privileges..."
say "Requesting to rerun $0 with root privileges..."
$SUDO "$0" --cb-auto-has-root "$@"
exit 0
fi
@@ -333,63 +333,11 @@ BootstrapDebCommon() {
fi
augeas_pkg="libaugeas0 augeas-lenses"
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"
say "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
sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
apt-get $QUIET_FLAG update
fi
fi
fi
if [ "$add_backports" != 0 ]; then
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
fi
apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \
python \
python-dev \
@@ -573,10 +521,20 @@ BootstrapSuseCommon() {
QUIET_FLAG='-qq'
fi
if zypper search -x python-virtualenv >/dev/null 2>&1; then
OPENSUSE_VIRTUALENV_PACKAGES="python-virtualenv"
else
# Since Leap 15.0 (and associated Tumbleweed version), python-virtualenv
# is a source package, and python2-virtualenv must be used instead.
# Also currently python2-setuptools is not a dependency of python2-virtualenv,
# while it should be. Installing it explicitly until upstreqm fix.
OPENSUSE_VIRTUALENV_PACKAGES="python2-virtualenv python2-setuptools"
fi
zypper $QUIET_FLAG $zypper_flags in $install_flags \
python \
python-devel \
python-virtualenv \
$OPENSUSE_VIRTUALENV_PACKAGES \
gcc \
augeas-lenses \
libopenssl-devel \
@@ -593,8 +551,7 @@ BootstrapArchCommon() {
# - ArchLinux (x86_64)
#
# "python-virtualenv" is Python3, but "python2-virtualenv" provides
# only "virtualenv2" binary, not "virtualenv" necessary in
# ./tools/_venv_common.sh
# only "virtualenv2" binary, not "virtualenv".
deps="
python2
@@ -912,6 +869,35 @@ OldVenvExists() {
[ -n "$OLD_VENV_PATH" -a -f "$OLD_VENV_PATH/bin/letsencrypt" ]
}
# Given python path, version 1 and version 2, check if version 1 is outdated compared to version 2.
# An unofficial version provided as version 1 (eg. 0.28.0.dev0) will be treated
# specifically by printing "UNOFFICIAL". Otherwise, print "OUTDATED" if version 1
# is outdated, and "UP_TO_DATE" if not.
# This function relies only on installed python environment (2.x or 3.x) by certbot-auto.
CompareVersions() {
"$1" - "$2" "$3" << "UNLIKELY_EOF"
import sys
from distutils.version import StrictVersion
try:
current = StrictVersion(sys.argv[1])
except ValueError:
sys.stdout.write('UNOFFICIAL')
sys.exit()
try:
remote = StrictVersion(sys.argv[2])
except ValueError:
sys.stdout.write('UP_TO_DATE')
sys.exit()
if current < remote:
sys.stdout.write('OUTDATED')
else:
sys.stdout.write('UP_TO_DATE')
UNLIKELY_EOF
}
if [ "$1" = "--le-auto-phase2" ]; then
# Phase 2: Create venv, install LE, and run.
@@ -969,10 +955,12 @@ if [ "$1" = "--le-auto-phase2" ]; then
DeterminePythonVersion
rm -rf "$VENV_PATH"
if [ "$PYVER" -le 27 ]; then
# Use an environment variable instead of a flag for compatibility with old versions
if [ "$VERBOSE" = 1 ]; then
virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH"
VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH"
else
virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" > /dev/null
VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages --python "$LE_PYTHON" "$VENV_PATH" \
> /dev/null
fi
else
if [ "$VERBOSE" = 1 ]; then
@@ -1017,79 +1005,66 @@ pycparser==2.14 \
asn1crypto==0.22.0 \
--hash=sha256:d232509fefcfcdb9a331f37e9c9dc20441019ad927c7d2176cf18ed5da0ba097 \
--hash=sha256:cbbadd640d3165ab24b06ef25d1dca09a3441611ac15f6a6b452474fdf0aed1a
cffi==1.10.0 \
--hash=sha256:446699c10f3c390633d0722bc19edbc7ac4b94761918a4a4f7908a24e86ebbd0 \
--hash=sha256:562326fc7f55a59ef3fef5e82908fe938cdc4bbda32d734c424c7cd9ed73e93a \
--hash=sha256:7f732ad4a30db0b39400c3f7011249f7d0701007d511bf09604729aea222871f \
--hash=sha256:94fb8410c6c4fc48e7ea759d3d1d9ca561171a88d00faddd4aa0306f698ad6a0 \
--hash=sha256:587a5043df4b00a2130e09fed42da02a4ed3c688bd9bf07a3ac89d2271f4fb07 \
--hash=sha256:ec08b88bef627ec1cea210e1608c85d3cf44893bcde74e41b7f7dbdfd2c1bad6 \
--hash=sha256:a41406f6d62abcdf3eef9fd998d8dcff04fd2a7746644143045feeebd76352d1 \
--hash=sha256:b560916546b2f209d74b82bdbc3223cee9a165b0242fa00a06dfc48a2054864a \
--hash=sha256:e74896774e437f4715c57edeb5cf3d3a40d7727f541c2c12156617b5a15d1829 \
--hash=sha256:9a31c18ba4881a116e448c52f3f5d3e14401cf7a9c43cc88f06f2a7f5428da0e \
--hash=sha256:80796ea68e11624a0279d3b802f88a7fe7214122b97a15a6c97189934a2cc776 \
--hash=sha256:f4019826a2dec066c909a1f483ef0dcf9325d6740cc0bd15308942b28b0930f7 \
--hash=sha256:7248506981eeba23888b4140a69a53c4c0c0a386abcdca61ed8dd790a73e64b9 \
--hash=sha256:a8955265d146e86fe2ce116394be4eaf0cb40314a79b19f11c4fa574cd639572 \
--hash=sha256:c49187260043bd4c1d6a52186f9774f17d9b1da0a406798ebf4bfc12da166ade \
--hash=sha256:c1d8b3d8dcb5c23ac1a8bf56422036f3f305a3c5a8bc8c354256579a1e2aa2c1 \
--hash=sha256:9e389615bcecb8c782a87939d752340bb0a3a097e90bae54d7f0915bc12f45bd \
--hash=sha256:d09ff358f75a874f69fa7d1c2b4acecf4282a950293fcfcf89aa606da8a9a500 \
--hash=sha256:b69b4557aae7de18b7c174a917fe19873529d927ac592762d9771661875bbd40 \
--hash=sha256:5de52b081a2775e76b971de9d997d85c4457fc0a09079e12d66849548ae60981 \
--hash=sha256:e7d88fecb7b6250a1fd432e6dc64890342c372fce13dbfe4bb6f16348ad00c14 \
--hash=sha256:1426e67e855ef7f5030c9184f4f1a9f4bfa020c31c962cd41fd129ec5aef4a6a \
--hash=sha256:267dd2c66a5760c5f4d47e2ebcf8eeac7ef01e1ae6ae7a6d0d241a290068bc38 \
--hash=sha256:e553eb489511cacf19eda6e52bc9e151316f0d721724997dda2c4d3079b778db \
--hash=sha256:98b89b2c57f97ce2db7aeba60db173c84871d73b40e41a11ea95de1500ddc57e \
--hash=sha256:e2b7e090188833bc58b2ae03fb864c22688654ebd2096bcf38bc860c4f38a3d8 \
--hash=sha256:afa7d8b8d38ad40db8713ee053d41b36d87d6ae5ec5ad36f9210b548a18dc214 \
--hash=sha256:4fc9c2ff7924b3a1fa326e1799e5dd58cac585d7fb25fe53ccaa1333b0453d65 \
--hash=sha256:937db39a1ec5af3003b16357b2042bba67c88d43bc11aaa203fa8a5924524209 \
--hash=sha256:ab22285797631df3b513b2cd3ecdc51cd8e3d36788e3991d93d0759d6883b027 \
--hash=sha256:96e599b924ef009aa867f725b3249ee51d76489f484d3a45b4bd219c5ec6ed59 \
--hash=sha256:bea842a0512be6a8007e585790bccd5d530520fc025ce63b03e139be373b0063 \
--hash=sha256:e7175287f7fe7b1cc203bb958b17db40abd732690c1e18e700f10e0843a58598 \
--hash=sha256:285ab352552f52f1398c912556d4d36d4ea9b8450e5c65d03809bf9886755533 \
--hash=sha256:5576644b859197da7bbd8f8c7c2fb5dcc6cd505cadb42992d5f104c013f8a214 \
--hash=sha256:b3b02911eb1f6ada203b0763ba924234629b51586f72a21faacc638269f4ced5
cffi==1.11.5 \
--hash=sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50 \
--hash=sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596 \
--hash=sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef \
--hash=sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743 \
--hash=sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f \
--hash=sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31 \
--hash=sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04 \
--hash=sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6 \
--hash=sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3 \
--hash=sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6 \
--hash=sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b \
--hash=sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca \
--hash=sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e \
--hash=sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb \
--hash=sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd \
--hash=sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1 \
--hash=sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917 \
--hash=sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359 \
--hash=sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f \
--hash=sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95 \
--hash=sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801 \
--hash=sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257 \
--hash=sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184 \
--hash=sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc \
--hash=sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085 \
--hash=sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93 \
--hash=sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2 \
--hash=sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30 \
--hash=sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5 \
--hash=sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e \
--hash=sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b \
--hash=sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4
ConfigArgParse==0.12.0 \
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339 \
--no-binary ConfigArgParse
configobj==5.0.6 \
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902
cryptography==2.0.2 \
--hash=sha256:187ae17358436d2c760f28c2aeb02fefa3f37647a9c5b6f7f7c3e83cd1c5a972 \
--hash=sha256:19e43a13bbf52028dd1e810c803f2ad8880d0692d772f98d42e1eaf34bdee3d6 \
--hash=sha256:da9291502cbc87dc0284a20c56876e4d2e68deac61cc43df4aec934e44ca97b1 \
--hash=sha256:0954f8813095f581669330e0a2d5e726c33ac7f450c1458fac58bab54595e516 \
--hash=sha256:d68b0cc40a8432ed3fc84876c519de704d6001800ec22b136e75ae841910c45b \
--hash=sha256:2f8ad9580ab4da645cfea52a91d2da99a49a1e76616d8be68441a986fad652b0 \
--hash=sha256:cc00b4511294f5f6b65c4e77a1a9c62f52490a63d2c120f3872176b40a82351e \
--hash=sha256:cf896020f6a9f095a547b3d672c8db1ef2ed71fca11250731fa1d4a4cb8b1590 \
--hash=sha256:e0fdb8322206fa02aa38f71519ff75dce2eb481b7e1110e2936795cb376bb6ee \
--hash=sha256:277538466657ca5d6637f80be100242f9831d75138b788d718edd3aab34621f8 \
--hash=sha256:2c77eb0560f54ce654ab82d6b2a64327a71ee969b29022bf9746ca311c9f5069 \
--hash=sha256:755a7853b679e79d0a799351c092a9b0271f95ff54c8dd8823d8b527a2926a86 \
--hash=sha256:77197a2d525e761cdd4c771180b4bd0d80703654c6385e4311cbbbe2beb56fa1 \
--hash=sha256:eb8bb79d0ab00c931c8333b745f06fec481a51c52d70acd4ee95d6093ba5c386 \
--hash=sha256:131f61de82ef28f3e20beb4bfc24f9692d28cecfd704e20e6c7f070f7793013a \
--hash=sha256:ac35435974b2e27cd4520f29c191d7da36f4189aa3264e52c4c6c6d089ab6142 \
--hash=sha256:04b6ea99daa2a8460728794213d76d45ad58ea247dc7e7ff148d7dd726e87863 \
--hash=sha256:2b9442f8b4c3d575f6cc3db0e856034e0f5a9d55ecd636f52d8c496795b26952 \
--hash=sha256:b3d3b3ecba1fe1bdb6f180770a137f877c8f07571f7b2934bb269475bcf0e5e8 \
--hash=sha256:670a58c0d75cb0e78e73dd003bd96d4440bbb1f2bc041dcf7b81767ca4fb0ce9 \
--hash=sha256:5af84d23bdb86b5e90aca263df1424b43f1748480bfcde3ac2a3cbe622612468 \
--hash=sha256:ba22e8eefabdd7aca37d0c0c00d2274000d2cebb5cce9e5a710cb55bf8797b31 \
--hash=sha256:b798b22fa7e92b439547323b8b719d217f1e1b7677585cfeeedf3b55c70bb7fb \
--hash=sha256:59cff28af8cce96cb7e94a459726e1d88f6f5fa75097f9dcbebd99118d64ea4c \
--hash=sha256:fe859e445abc9ba9e97950ddafb904e23234c4ecb76b0fae6c86e80592ce464a \
--hash=sha256:655f3c474067f1e277430f23cc0549f0b1dc99b82aec6e53f80b9b2db7f76f11 \
--hash=sha256:0ebc2be053c9a03a2f3e20a466e87bf12a51586b3c79bd2a22171b073a805346 \
--hash=sha256:01e6e60654df64cca53733cda39446d67100c819c181d403afb120e0d2a71e1b \
--hash=sha256:d46f4e5d455cb5563685c52ef212696f0a6cc1ea627603218eabbd8a095291d8 \
--hash=sha256:3780b2663ee7ebb37cb83263326e3cd7f8b2ea439c448539d4b87de12c8d06ab
enum34==1.1.2 \
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
--no-binary configobj
cryptography==2.5 \
--hash=sha256:9e29af877c29338f0cab5f049ccc8bd3ead289a557f144376c4fbc7d1b98914f \
--hash=sha256:b13c80b877e73bcb6f012813c6f4a9334fcf4b0e96681c5a15dac578f2eedfa0 \
--hash=sha256:8504661ffe324837f5c4607347eeee4cf0fcad689163c6e9c8d3b18cf1f4a4ad \
--hash=sha256:e091bd424567efa4b9d94287a952597c05d22155a13716bf5f9f746b9dc906d3 \
--hash=sha256:42fad67d7072216a49e34f923d8cbda9edacbf6633b19a79655e88a1b4857063 \
--hash=sha256:9a30384cc402eac099210ab9b8801b2ae21e591831253883decdb4513b77a3cd \
--hash=sha256:08b753df3672b7066e74376f42ce8fc4683e4fd1358d34c80f502e939ee944d2 \
--hash=sha256:6f841c7272645dd7c65b07b7108adfa8af0aaea57f27b7f59e01d41f75444c85 \
--hash=sha256:bfe66b577a7118e05b04141f0f1ed0959552d45672aa7ecb3d91e319d846001e \
--hash=sha256:522fdb2809603ee97a4d0ef2f8d617bc791eb483313ba307cb9c0a773e5e5695 \
--hash=sha256:05b3ded5e88747d28ee3ef493f2b92cbb947c1e45cf98cfef22e6d38bb67d4af \
--hash=sha256:fa2b38c8519c5a3aa6e2b4e1cf1a549b54acda6adb25397ff542068e73d1ed00 \
--hash=sha256:ab50da871bc109b2d9389259aac269dd1b7c7413ee02d06fe4e486ed26882159 \
--hash=sha256:9260b201ce584d7825d900c88700aa0bd6b40d4ebac7b213857bd2babee9dbca \
--hash=sha256:06826e7f72d1770e186e9c90e76b4f84d90cdb917b47ff88d8dc59a7b10e2b1e \
--hash=sha256:2cd29bd1911782baaee890544c653bb03ec7d95ebeb144d714b0f5c33deb55c7 \
--hash=sha256:7d335e35306af5b9bc0560ca39f740dfc8def72749645e193dd35be11fb323b3 \
--hash=sha256:31e5637e9036d966824edaa91bf0aa39dc6f525a1c599f39fd5c50340264e079 \
--hash=sha256:4946b67235b9d2ea7d31307be9d5ad5959d6c4a8f98f900157b47abddf698401
enum34==1.1.2 ; python_version < '3.4' \
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
funcsigs==1.0.2 \
@@ -1101,9 +1076,9 @@ idna==2.5 \
ipaddress==1.0.16 \
--hash=sha256:935712800ce4760701d89ad677666cd52691fd2f6f0b340c8b4239a3c17988a5 \
--hash=sha256:5a3182b322a706525c46282ca6f064d27a02cffbd449f9f47416f1dc96aa71b0
josepy==1.0.1 \
--hash=sha256:354a3513038a38bbcd27c97b7c68a8f3dfaff0a135b20a92c6db4cc4ea72915e \
--hash=sha256:9f48b88ca37f0244238b1cc77723989f7c54f7b90b2eee6294390bacfe870acc
josepy==1.1.0 \
--hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \
--hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086
linecache2==1.0.0 \
--hash=sha256:e78be9c0a0dfcbac712fe04fbf92b96cddae80b1b842f24248214c8496f006ef \
--hash=sha256:4b26ff4e7110db76eeb6f5a7b64a82623839d595c2038eeda662f2a2db78e97c
@@ -1112,7 +1087,8 @@ mock==1.3.0 \
--hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \
--hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6
ordereddict==1.1 \
--hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f
--hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f \
--no-binary ordereddict
packaging==16.8 \
--hash=sha256:99276dc6e3a7851f32027a68f1095cd3f77c148091b092ea867a351811cfe388 \
--hash=sha256:5d50835fdf0a7edf0b55e311b7c887786504efea1177abd7e69329a8e5ea619e
@@ -1122,9 +1098,9 @@ parsedatetime==2.1 \
pbr==1.8.1 \
--hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \
--hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649
pyOpenSSL==16.2.0 \
--hash=sha256:26ca380ddf272f7556e48064bbcd5bd71f83dfc144f3583501c7ddbd9434ee17 \
--hash=sha256:7779a3bbb74e79db234af6a08775568c6769b5821faecf6e2f4143edb227516e
pyOpenSSL==18.0.0 \
--hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \
--hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580
pyparsing==2.1.8 \
--hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \
--hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \
@@ -1138,7 +1114,8 @@ pyRFC3339==1.0 \
--hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 \
--no-binary python-augeas
pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
@@ -1153,9 +1130,9 @@ pytz==2015.7 \
--hash=sha256:fbd26746772c24cb93c8b97cbdad5cb9e46c86bbdb1b9d8a743ee00e2fb1fc5d \
--hash=sha256:99266ef30a37e43932deec2b7ca73e83c8dbc3b9ff703ec73eca6b1dae6befea \
--hash=sha256:8b6ce1c993909783bc96e0b4f34ea223bff7a4df2c90bdb9c4e0f1ac928689e3
requests==2.12.1 \
--hash=sha256:3f3f27a9d0f9092935efc78054ef324eb9f8166718270aefe036dfa1e4f68e1e \
--hash=sha256:2109ecea94df90980be040490ff1d879971b024861539abb00054062388b612e
requests==2.20.0 \
--hash=sha256:99dcfdaaeb17caf6e526f32b6a7b780461512ab3f1d992187801694cba42770c \
--hash=sha256:a84b8c9ab6239b578f22d1c21d51b696dcfe004032bb80ea832398d6909d7279
six==1.10.0 \
--hash=sha256:0ff78c403d9bccf5a425a6d31a12aa6b47f1c21ca4dc2573a7e2f32a97335eb1 \
--hash=sha256:105f8d68616f8248e24bf0e9372ef04d3cc10104f1980f54d57b2ce73a5ad56a
@@ -1166,9 +1143,11 @@ unittest2==1.1.0 \
--hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \
--hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579
zope.component==4.2.2 \
--hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a
--hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a \
--no-binary zope.component
zope.event==4.1.0 \
--hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786
--hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786 \
--no-binary zope.event
zope.interface==4.1.3 \
--hash=sha256:f07b631f7a601cd8cbd3332d54f43142c7088a83299f859356f08d1d4d4259b3 \
--hash=sha256:de5cca083b9439d8002fb76bbe6b4998c5a5a721fab25b84298967f002df4c94 \
@@ -1187,6 +1166,18 @@ zope.interface==4.1.3 \
--hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \
--hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \
--hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392
requests-toolbelt==0.8.0 \
--hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \
--hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5
chardet==3.0.2 \
--hash=sha256:4f7832e7c583348a9eddd927ee8514b3bf717c061f57b21dbe7697211454d9bb \
--hash=sha256:6ebf56457934fdce01fb5ada5582762a84eed94cad43ed877964aebbdd8174c0
urllib3==1.24.1 \
--hash=sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39 \
--hash=sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22
certifi==2017.4.17 \
--hash=sha256:f4318671072f030a33c7ca6acaef720ddd50ff124d1388e50c1bda4cbd6d7010 \
--hash=sha256:f7527ebf7461582ce95f7a9e03dd141ce810d40590834f4ec20cddd54234c10a
# Contains the requirements for the letsencrypt package.
#
@@ -1199,31 +1190,29 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.23.0 \
--hash=sha256:66c42cf780ddbf582ecc52aa6a61242450a2650227b436ad0d260685c4ef8a49 \
--hash=sha256:6cff4c5da1228661ccaf95195064cb29e6cdf80913193bdb2eb20e164c76053e
acme==0.23.0 \
--hash=sha256:02e9b596bd3bf8f0733d6d43ec2464ac8185a000acb58d2b4fd9e19223bbbf0b \
--hash=sha256:08c16635578507f526c338b3418c1147a9f015bf2d366abd51f38918703b4550
certbot-apache==0.23.0 \
--hash=sha256:50077742d2763b7600dfda618eb89c870aeea5e6a4c00f60157877f7a7d81f7c \
--hash=sha256:6b7acec243e224de5268d46c2597277586dffa55e838c252b6931c30d549028e
certbot-nginx==0.23.0 \
--hash=sha256:f12c21bbe3eb955ca533f1da96d28c6310378b138e844d83253562e18b6cbb32 \
--hash=sha256:cadf14e4bd504d9ce5987a5ec6dbd8e136638e55303ad5dc81dcb723ddd64324
certbot==0.32.0 \
--hash=sha256:75fd986ae42cd90bde6400c5f5a0dd936a7f4a42a416146b1e8bb0f92028b443 \
--hash=sha256:c0b94e25a07d83809d98029f09e9b501f86ec97624f45ce86800a7002488c3c8
acme==0.32.0 \
--hash=sha256:88b2d2741e5ea028c590a33b16fb647cb74af6b2db6c7909c738a48f879efdec \
--hash=sha256:0eefce8b7880eb7eccc049a6b8ba262fc624bc34b3a8581d05b82f2bb39f1aec
certbot-apache==0.32.0 \
--hash=sha256:b2c82b7a1c44799ba3a150970513ed4fa9afeee40e326440800b1243f917ddb6 \
--hash=sha256:68072775f1bb4bc9fc64cabe051a761f6dbf296012512eff7819144ac8b9ec97
certbot-nginx==0.32.0 \
--hash=sha256:3fc3664231586565d886ddcb679c95a2fb2494a2ce3e028149f1496dca5b47cf \
--hash=sha256:82c43cd26aacc2eb0ae890be6a2f74d726b6dcb4ee7b63c0e55ec33e576f3e84
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
with which you can install the rest of your dependencies safely. All it assumes
is Python 2.6 or better and *some* version of pip already installed. If
anything goes wrong, it will exit with a non-zero status code.
"""
# This is here so embedded copies are MIT-compliant:
# Copyright (c) 2016 Erik Rose
@@ -1242,7 +1231,6 @@ from distutils.version import StrictVersion
from hashlib import sha256
from os import environ
from os.path import join
from pipes import quote
from shutil import rmtree
try:
from subprocess import check_output
@@ -1262,7 +1250,7 @@ except ImportError:
cmd = popenargs[0]
raise CalledProcessError(retcode, cmd)
return output
from sys import exit, version_info
import sys
from tempfile import mkdtemp
try:
from urllib2 import build_opener, HTTPHandler, HTTPSHandler
@@ -1284,7 +1272,7 @@ maybe_argparse = (
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'argparse-1.4.0.tar.gz',
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
if version_info < (2, 7, 0) else [])
if sys.version_info < (2, 7, 0) else [])
PACKAGES = maybe_argparse + [
@@ -1293,9 +1281,9 @@ PACKAGES = maybe_argparse + [
'pip-{0}.tar.gz'.format(PIP_VERSION),
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
# This version of setuptools has only optional dependencies:
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
'setuptools-29.0.1.tar.gz',
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
('37/1b/b25507861991beeade31473868463dad0e58b1978c209de27384ae541b0b/'
'setuptools-40.6.3.zip',
'3b474dad69c49f0d2d86696b68105f3a6f195f7ab655af12ef9a9c326d2b08f8'),
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'wheel-0.29.0.tar.gz',
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
@@ -1350,10 +1338,8 @@ def hashed_download(url, temp, digest):
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
@@ -1367,11 +1353,9 @@ def get_index_base():
def main():
pip_version = StrictVersion(check_output(['pip', '--version'])
python = sys.executable or 'python'
pip_version = StrictVersion(check_output([python, '-m', 'pip', '--version'])
.decode('utf-8').split()[1])
min_pip_version = StrictVersion(PIP_VERSION)
if pip_version >= min_pip_version:
return 0
has_pip_cache = pip_version >= StrictVersion('6.0')
index_base = get_index_base()
temp = mkdtemp(prefix='pipstrap-')
@@ -1380,12 +1364,12 @@ def main():
temp,
digest)
for path, digest in PACKAGES]
check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings:
('--no-cache-dir ' if has_pip_cache else '') +
' '.join(quote(d) for d in downloads),
shell=True)
# On Windows, pip self-upgrade is not possible, it must be done through python interpreter.
command = [python, '-m', 'pip', 'install', '--no-index', '--no-deps', '-U']
# Disable cache since it is not used and it otherwise sometimes throws permission warnings:
command.extend(['--no-cache-dir'] if has_pip_cache else [])
command.extend(downloads)
check_output(command)
except HashError as exc:
print(exc)
except Exception:
@@ -1398,7 +1382,7 @@ def main():
if __name__ == '__main__':
exit(main())
sys.exit(main())
UNLIKELY_EOF
# -------------------------------------------------------------------------
@@ -1643,7 +1627,12 @@ UNLIKELY_EOF
error "WARNING: couldn't find Python $MIN_PYTHON_VERSION+ to check for updates."
elif ! REMOTE_VERSION=`"$LE_PYTHON" "$TEMP_DIR/fetch.py" --latest-version` ; then
error "WARNING: unable to check for updates."
elif [ "$LE_AUTO_VERSION" != "$REMOTE_VERSION" ]; then
fi
LE_VERSION_STATE=`CompareVersions "$LE_PYTHON" "$LE_AUTO_VERSION" "$REMOTE_VERSION"`
if [ "$LE_VERSION_STATE" = "UNOFFICIAL" ]; then
say "Unofficial certbot-auto version detected, self-upgrade is disabled: $LE_AUTO_VERSION"
elif [ "$LE_VERSION_STATE" = "OUTDATED" ]; then
say "Upgrading certbot-auto $LE_AUTO_VERSION to $REMOTE_VERSION..."
# Now we drop into Python so we don't have to install even more

View File

@@ -0,0 +1,8 @@
[run]
# Avoid false warnings because certbot packages are not installed in the thread that executes
# the coverage: indeed, certbot is launched as a CLI from a subprocess.
disable_warnings = module-not-imported,no-data-collected
[report]
# Exclude unit tests in coverage during integration tests.
omit = **/*_test.py,**/tests/*,**/certbot_nginx/parser_obj.py

View File

@@ -0,0 +1 @@
"""Package certbot_integration_test is for tests that require a live acme ca server instance"""

View File

@@ -0,0 +1,16 @@
class IntegrationTestsContext(object):
"""General fixture describing a certbot integration tests context"""
def __init__(self, request):
self.request = request
if hasattr(request.config, 'slaveinput'): # Worker node
self.worker_id = request.config.slaveinput['slaveid']
self.acme_xdist = request.config.slaveinput['acme_xdist']
else: # Primary node
self.worker_id = 'primary'
self.acme_xdist = request.config.acme_xdist
self.directory_url = self.acme_xdist['directory_url']
self.tls_alpn_01_port = self.acme_xdist['https_port'][self.worker_id]
self.http_01_port = self.acme_xdist['http_port'][self.worker_id]
def cleanup(self):
pass

View File

@@ -0,0 +1,40 @@
import requests
import urllib3
import pytest
from certbot_integration_tests.certbot_tests import context as certbot_context
@pytest.fixture()
def context(request):
# Fixture request is a built-in pytest fixture describing current test request.
integration_test_context = certbot_context.IntegrationTestsContext(request)
try:
yield integration_test_context
finally:
integration_test_context.cleanup()
def test_hello_1(context):
assert context.http_01_port
assert context.tls_alpn_01_port
try:
response = requests.get(context.directory_url, verify=False)
response.raise_for_status()
assert response.json()
response.close()
except urllib3.exceptions.InsecureRequestWarning:
pass
def test_hello_2(context):
assert context.http_01_port
assert context.tls_alpn_01_port
try:
response = requests.get(context.directory_url, verify=False)
response.raise_for_status()
assert response.json()
response.close()
except urllib3.exceptions.InsecureRequestWarning:
pass

View File

@@ -0,0 +1,92 @@
"""
General conftest for pytest execution of all integration tests lying
in the certbot_integration tests package.
As stated by pytest documentation, conftest module is used to set on
for a directory a specific configuration using built-in pytest hooks.
See https://docs.pytest.org/en/latest/reference.html#hook-reference
"""
import contextlib
import sys
import subprocess
from certbot_integration_tests.utils import acme_server as acme_lib
def pytest_addoption(parser):
"""
Standard pytest hook to add options to the pytest parser.
:param parser: current pytest parser that will be used on the CLI
"""
parser.addoption('--acme-server', default='pebble',
choices=['boulder-v1', 'boulder-v2', 'pebble'],
help='select the ACME server to use (boulder-v1, boulder-v2, '
'pebble), defaulting to pebble')
def pytest_configure(config):
"""
Standard pytest hook used to add a configuration logic for each node of a pytest run.
:param config: the current pytest configuration
"""
if not hasattr(config, 'slaveinput'): # If true, this is the primary node
with _print_on_err():
config.acme_xdist = _setup_primary_node(config)
def pytest_configure_node(node):
"""
Standard pytest-xdist hook used to configure a worker node.
:param node: current worker node
"""
node.slaveinput['acme_xdist'] = node.config.acme_xdist
@contextlib.contextmanager
def _print_on_err():
"""
During pytest-xdist setup, stdout is used for nodes communication, so print is useless.
However, stderr is still available. This context manager transfers stdout to stderr
for the duration of the context, allowing to display prints to the user.
"""
old_stdout = sys.stdout
sys.stdout = sys.stderr
try:
yield
finally:
sys.stdout = old_stdout
def _setup_primary_node(config):
"""
Setup the environment for integration tests.
Will:
- check runtime compatiblity (Docker, docker-compose, Nginx)
- create a temporary workspace and the persistent GIT repositories space
- configure and start paralleled ACME CA servers using Docker
- transfer ACME CA servers configurations to pytest nodes using env variables
:param config: Configuration of the pytest primary node
"""
# Check for runtime compatibility: some tools are required to be available in PATH
try:
subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT)
except (subprocess.CalledProcessError, OSError):
raise ValueError('Error: docker is required in PATH to launch the integration tests, '
'but is not installed or not available for current user.')
try:
subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT)
except (subprocess.CalledProcessError, OSError):
raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, '
'but is not installed or not available for current user.')
# Parameter numprocesses is added to option by pytest-xdist
workers = ['primary'] if not config.option.numprocesses\
else ['gw{0}'.format(i) for i in range(config.option.numprocesses)]
# By calling setup_acme_server we ensure that all necessary acme server instances will be
# fully started. This runtime is reflected by the acme_xdist returned.
acme_xdist = acme_lib.setup_acme_server(config.option.acme_server, workers)
print('ACME xdist config:\n{0}'.format(acme_xdist))
return acme_xdist

View File

@@ -0,0 +1,5 @@
from certbot_integration_tests.certbot_tests import context as certbot_context
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
"""General fixture describing a certbot-nginx integration tests context"""

View File

@@ -0,0 +1,17 @@
import pytest
from certbot_integration_tests.nginx_tests import context as nginx_context
@pytest.fixture()
def context(request):
# Fixture request is a built-in pytest fixture describing current test request.
integration_test_context = nginx_context.IntegrationTestsContext(request)
try:
yield integration_test_context
finally:
integration_test_context.cleanup()
def test_hello(context):
print(context.directory_url)

View File

@@ -0,0 +1,194 @@
"""Module to setup an ACME CA server environment able to run multiple tests in parallel"""
from __future__ import print_function
import tempfile
import atexit
import os
import subprocess
import shutil
import sys
from os.path import join
import requests
import json
import yaml
from certbot_integration_tests.utils import misc
# These ports are set implicitly in the docker-compose.yml files of Boulder/Pebble.
CHALLTESTSRV_PORT = 8055
HTTP_01_PORT = 5002
def setup_acme_server(acme_server, nodes):
"""
This method will setup an ACME CA server and an HTTP reverse proxy instance, to allow parallel
execution of integration tests against the unique http-01 port expected by the ACME CA server.
Instances are properly closed and cleaned when the Python process exits using atexit.
Typically all pytest integration tests will be executed in this context.
This method returns an object describing ports and directory url to use for each pytest node
with the relevant pytest xdist node.
:param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble)
:param str[] nodes: list of node names that will be setup by pytest xdist
:return: a dict describing the challenge ports that have been setup for the nodes
:rtype: dict
"""
acme_type = 'pebble' if acme_server == 'pebble' else 'boulder'
acme_xdist = _construct_acme_xdist(acme_server, nodes)
workspace = _construct_workspace(acme_type)
_prepare_traefik_proxy(workspace, acme_xdist)
_prepare_acme_server(workspace, acme_type, acme_xdist)
return acme_xdist
def _construct_acme_xdist(acme_server, nodes):
"""Generate and return the acme_xdist dict"""
acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT}
# Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble.
if acme_server == 'pebble':
acme_xdist['directory_url'] = 'https://localhost:14000/dir'
else: # boulder
port = 4001 if acme_server == 'boulder-v2' else 4000
acme_xdist['directory_url'] = 'http://localhost:{0}/directory'.format(port)
acme_xdist['http_port'] = {node: port for (node, port)
in zip(nodes, range(5200, 5200 + len(nodes)))}
acme_xdist['https_port'] = {node: port for (node, port)
in zip(nodes, range(5100, 5100 + len(nodes)))}
return acme_xdist
def _construct_workspace(acme_type):
"""Create a temporary workspace for integration tests stack"""
workspace = tempfile.mkdtemp()
def cleanup():
"""Cleanup function to call that will teardown relevant dockers and their configuration."""
for instance in [acme_type, 'traefik']:
print('=> Tear down the {0} instance...'.format(instance))
instance_path = join(workspace, instance)
try:
if os.path.isfile(join(instance_path, 'docker-compose.yml')):
_launch_command(['docker-compose', 'down'], cwd=instance_path)
except subprocess.CalledProcessError:
pass
print('=> Finished tear down of {0} instance.'.format(acme_type))
shutil.rmtree(workspace)
# Here with atexit we ensure that clean function is called no matter what.
atexit.register(cleanup)
return workspace
def _prepare_acme_server(workspace, acme_type, acme_xdist):
"""Configure and launch the ACME server, Boulder or Pebble"""
print('=> Starting {0} instance deployment...'.format(acme_type))
instance_path = join(workspace, acme_type)
try:
# Load Boulder/Pebble from git, that includes a docker-compose.yml ready for production.
_launch_command(['git', 'clone', 'https://github.com/letsencrypt/{0}'.format(acme_type),
'--single-branch', '--depth=1', instance_path])
if acme_type == 'boulder':
# Allow Boulder to ignore usual limit rate policies, useful for tests.
os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'),
join(instance_path, 'test/rate-limit-policies.yml'))
if acme_type == 'pebble':
# Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid
# nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment.
with open(os.path.join(instance_path, 'docker-compose.yml'), 'r') as file_handler:
config = yaml.load(file_handler.read())
config['services']['pebble'].setdefault('environment', [])\
.extend(['PEBBLE_VA_NOSLEEP=1', 'PEBBLE_WFE_NONCEREJECT=0'])
with open(os.path.join(instance_path, 'docker-compose.yml'), 'w') as file_handler:
file_handler.write(yaml.dump(config))
# Launch the ACME CA server.
_launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path)
# Wait for the ACME CA server to be up.
print('=> Waiting for {0} instance to respond...'.format(acme_type))
misc.check_until_timeout(acme_xdist['directory_url'])
# Configure challtestsrv to answer any A record request with ip of the docker host.
acme_subnet = '10.77.77' if acme_type == 'boulder' else '10.30.50'
response = requests.post('http://localhost:{0}/set-default-ipv4'
.format(acme_xdist['challtestsrv_port']),
json={'ip': '{0}.1'.format(acme_subnet)})
response.raise_for_status()
print('=> Finished {0} instance deployment.'.format(acme_type))
except BaseException:
print('Error while setting up {0} instance.'.format(acme_type))
raise
def _prepare_traefik_proxy(workspace, acme_xdist):
"""Configure and launch Traefik, the HTTP reverse proxy"""
print('=> Starting traefik instance deployment...')
instance_path = join(workspace, 'traefik')
traefik_subnet = '10.33.33'
traefik_api_port = 8056
try:
os.mkdir(instance_path)
with open(join(instance_path, 'docker-compose.yml'), 'w') as file_h:
file_h.write('''\
version: '3'
services:
traefik:
image: traefik
command: --api --rest
ports:
- {http_01_port}:80
- {traefik_api_port}:8080
networks:
traefiknet:
ipv4_address: {traefik_subnet}.2
networks:
traefiknet:
ipam:
config:
- subnet: {traefik_subnet}.0/24
'''.format(traefik_subnet=traefik_subnet,
traefik_api_port=traefik_api_port,
http_01_port=HTTP_01_PORT))
_launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path)
misc.check_until_timeout('http://localhost:{0}/api'.format(traefik_api_port))
config = {
'backends': {
node: {
'servers': {node: {'url': 'http://{0}.1:{1}'.format(traefik_subnet, port)}}
} for node, port in acme_xdist['http_port'].items()
},
'frontends': {
node: {
'backend': node, 'passHostHeader': True,
'routes': {node: {'rule': 'HostRegexp: {{subdomain:.+}}.{0}.wtf'.format(node)}}
} for node in acme_xdist['http_port'].keys()
}
}
response = requests.put('http://localhost:{0}/api/providers/rest'.format(traefik_api_port),
data=json.dumps(config))
response.raise_for_status()
print('=> Finished traefik instance deployment.')
except BaseException:
print('Error while setting up traefik instance.')
raise
def _launch_command(command, cwd=os.getcwd()):
"""Launch silently an OS command, output will be displayed in case of failure"""
try:
subprocess.check_output(command, stderr=subprocess.STDOUT, cwd=cwd, universal_newlines=True)
except subprocess.CalledProcessError as e:
sys.stderr.write(e.output)
raise

View File

@@ -0,0 +1,45 @@
"""
Misc module contains stateless functions that could be used during pytest execution,
or outside during setup/teardown of the integration tests environment.
"""
import os
import time
import contextlib
import requests
def check_until_timeout(url):
"""
Wait and block until given url responds with status 200, or raise an exception
after 150 attempts.
:param str url: the URL to test
:raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL
"""
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
for _ in range(0, 150):
time.sleep(1)
try:
if requests.get(url, verify=False).status_code == 200:
return
except requests.exceptions.ConnectionError:
pass
raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url))
@contextlib.contextmanager
def execute_in_given_cwd(cwd):
"""
Context manager that will execute any command in the given cwd after entering context,
and restore current cwd when context is destroyed.
:param str cwd: the path to use as the temporary current workspace for python execution
"""
current_cwd = os.getcwd()
try:
os.chdir(cwd)
yield
finally:
os.chdir(current_cwd)

45
certbot-ci/setup.py Normal file
View File

@@ -0,0 +1,45 @@
from setuptools import setup
from setuptools import find_packages
version = '0.32.0.dev0'
install_requires = [
'pytest',
'pytest-cov',
'pytest-xdist',
'pytest-sugar',
'coverage',
'requests',
'pyyaml',
]
setup(
name='certbot-ci',
version=version,
description="Certbot continuous integration framework",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
)

View File

@@ -14,7 +14,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 .pylintrc /opt/certbot/src/
COPY setup.py README.rst CHANGELOG.md MANIFEST.in linter_plugin.py tox.cover.py 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...
@@ -31,11 +31,12 @@ COPY certbot-nginx /opt/certbot/src/certbot-nginx/
COPY certbot-compatibility-test /opt/certbot/src/certbot-compatibility-test/
COPY tools /opt/certbot/src/tools
RUN virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
/opt/certbot/venv/bin/pip install -U setuptools && \
/opt/certbot/venv/bin/pip install -U pip
ENV PATH /opt/certbot/venv/bin:$PATH
RUN /opt/certbot/src/tools/pip_install_editable.sh \
RUN /opt/certbot/venv/bin/python \
/opt/certbot/src/tools/pip_install_editable.py \
/opt/certbot/src/acme \
/opt/certbot/src \
/opt/certbot/src/certbot-apache \

View File

@@ -59,9 +59,6 @@ class Proxy(configurators_common.Proxy):
setattr(self.le_config, "apache_" + k,
entrypoint.ENTRYPOINT.OS_DEFAULTS[k])
# An alias
self.le_config.apache_handle_modules = self.le_config.apache_handle_mods
self._configurator = entrypoint.ENTRYPOINT(
config=configuration.NamespaceConfig(self.le_config),
name="apache")

View File

@@ -23,6 +23,9 @@ class Proxy(object):
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
self._temp_dir = tempfile.mkdtemp()
# tempfile.mkdtemp() creates folders with too restrictive permissions to be accessible
# to an Apache worker, leading to HTTP challenge failures. Let's fix that.
os.chmod(self._temp_dir, 0o755)
self.le_config = util.create_le_config(self._temp_dir)
config_dir = util.extract_configs(args.configs, self._temp_dir)
self._configs = [

View File

@@ -1,5 +1,6 @@
"""Tests Certbot plugins against different server configurations."""
import argparse
import contextlib
import filecmp
import logging
import os
@@ -7,6 +8,7 @@ import shutil
import tempfile
import time
import sys
from urllib3.util import connection
import OpenSSL
@@ -15,6 +17,7 @@ from six.moves import xrange # pylint: disable=import-error,redefined-builtin
from acme import challenges
from acme import crypto_util
from acme import messages
from acme.magic_typing import List, Tuple # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges
from certbot import errors as le_errors
from certbot.tests import acme_util
@@ -52,9 +55,8 @@ def test_authenticator(plugin, config, temp_dir):
try:
responses = plugin.perform(achalls)
except le_errors.Error as error:
logger.error("Performing challenges on %s caused an error:", config)
logger.exception(error)
except le_errors.Error:
logger.error("Performing challenges on %s caused an error:", config, exc_info=True)
return False
success = True
@@ -64,27 +66,27 @@ def test_authenticator(plugin, config, temp_dir):
"Plugin failed to complete %s for %s in %s",
type(achalls[i]), achalls[i].domain, config)
success = False
elif isinstance(responses[i], challenges.TLSSNI01Response):
verified = responses[i].simple_verify(achalls[i].chall,
achalls[i].domain,
util.JWK.public_key(),
host="127.0.0.1",
port=plugin.https_port)
elif isinstance(responses[i], challenges.HTTP01Response):
# We fake the DNS resolution to ensure that any domain is resolved
# to the local HTTP server setup for the compatibility tests
with _fake_dns_resolution("127.0.0.1"):
verified = responses[i].simple_verify(
achalls[i].chall, achalls[i].domain,
util.JWK.public_key(), port=plugin.http_port)
if verified:
logger.info(
"tls-sni-01 verification for %s succeeded", achalls[i].domain)
"http-01 verification for %s succeeded", achalls[i].domain)
else:
logger.error(
"**** tls-sni-01 verification for %s in %s failed",
"**** http-01 verification for %s in %s failed",
achalls[i].domain, config)
success = False
if success:
try:
plugin.cleanup(achalls)
except le_errors.Error as error:
logger.error("Challenge cleanup for %s caused an error:", config)
logger.exception(error)
except le_errors.Error:
logger.error("Challenge cleanup for %s caused an error:", config, exc_info=True)
success = False
if _dirs_are_unequal(config, backup):
@@ -103,9 +105,9 @@ def _create_achalls(plugin):
for domain in names:
prefs = plugin.get_chall_pref(domain)
for chall_type in prefs:
if chall_type == challenges.TLSSNI01:
chall = challenges.TLSSNI01(
token=os.urandom(challenges.TLSSNI01.TOKEN_SIZE))
if chall_type == challenges.HTTP01:
chall = challenges.HTTP01(
token=os.urandom(challenges.HTTP01.TOKEN_SIZE))
challb = acme_util.chall_to_challb(
chall, messages.STATUS_PENDING)
achall = achallenges.KeyAuthorizationAnnotatedChallenge(
@@ -147,9 +149,8 @@ def test_deploy_cert(plugin, temp_dir, domains):
try:
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 certificate for %s:", domain)
logger.exception(error)
except le_errors.Error:
logger.error("**** Plugin failed to deploy certificate for %s:", domain, exc_info=True)
return False
if not _save_and_restart(plugin, "deployed"):
@@ -179,7 +180,7 @@ def test_enhancements(plugin, domains):
"enhancements")
return False
domains_and_info = [(domain, []) for domain in domains]
domains_and_info = [(domain, []) for domain in domains] # type: List[Tuple[str, List[bool]]]
for domain, info in domains_and_info:
try:
@@ -192,10 +193,9 @@ def test_enhancements(plugin, domains):
# Don't immediately fail because a redirect may already be enabled
logger.warning("*** Plugin failed to enable redirect for %s:", domain)
logger.warning("%s", error)
except le_errors.Error as error:
except le_errors.Error:
logger.error("*** An error occurred while enabling redirect for %s:",
domain)
logger.exception(error)
domain, exc_info=True)
if not _save_and_restart(plugin, "enhanced"):
return False
@@ -222,9 +222,8 @@ def _save_and_restart(plugin, title=None):
plugin.save(title)
plugin.restart()
return True
except le_errors.Error as error:
logger.error("*** Plugin failed to save and restart server:")
logger.exception(error)
except le_errors.Error:
logger.error("*** Plugin failed to save and restart server:", exc_info=True)
return False
@@ -232,9 +231,8 @@ def test_rollback(plugin, config, backup):
"""Tests the rollback checkpoints function"""
try:
plugin.rollback_checkpoints(1337)
except le_errors.Error as error:
logger.error("*** Plugin raised an exception during rollback:")
logger.exception(error)
except le_errors.Error:
logger.error("*** Plugin raised an exception during rollback:", exc_info=True)
return False
if _dirs_are_unequal(config, backup):
@@ -263,21 +261,21 @@ def _dirs_are_unequal(dir1, dir2):
logger.error("The following files and directories are only "
"present in one directory")
if dircmp.left_only:
logger.error(dircmp.left_only)
logger.error(str(dircmp.left_only))
else:
logger.error(dircmp.right_only)
logger.error(str(dircmp.right_only))
return True
elif dircmp.common_funny or dircmp.funny_files:
logger.error("The following files and directories could not be "
"compared:")
if dircmp.common_funny:
logger.error(dircmp.common_funny)
logger.error(str(dircmp.common_funny))
else:
logger.error(dircmp.funny_files)
logger.error(str(dircmp.funny_files))
return True
elif dircmp.diff_files:
logger.error("The following files differ:")
logger.error(dircmp.diff_files)
logger.error(str(dircmp.diff_files))
return True
for subdir in dircmp.subdirs.itervalues():
@@ -354,9 +352,8 @@ def main():
success = test_authenticator(plugin, config, temp_dir)
if success and args.install:
success = test_installer(args, plugin, config, temp_dir)
except errors.Error as error:
logger.error("Tests on %s raised:", config)
logger.exception(error)
except errors.Error:
logger.error("Tests on %s raised:", config, exc_info=True)
success = False
if success:
@@ -375,5 +372,21 @@ def main():
sys.exit(1)
@contextlib.contextmanager
def _fake_dns_resolution(resolved_ip):
"""Monkey patch urllib3 to make any hostname be resolved to the provided IP"""
_original_create_connection = connection.create_connection
def _patched_create_connection(address, *args, **kwargs):
_, port = address
return _original_create_connection((resolved_ip, port), *args, **kwargs)
try:
connection.create_connection = _patched_create_connection
yield
finally:
connection.create_connection = _original_create_connection
if __name__ == "__main__":
main()

View File

@@ -26,12 +26,12 @@ def create_le_config(parent_dir):
config = copy.deepcopy(constants.CLI_DEFAULTS)
le_dir = os.path.join(parent_dir, "certbot")
config["config_dir"] = os.path.join(le_dir, "config")
config["work_dir"] = os.path.join(le_dir, "work")
config["logs_dir"] = os.path.join(le_dir, "logs_dir")
os.makedirs(config["config_dir"])
os.mkdir(config["work_dir"])
os.mkdir(config["logs_dir"])
os.mkdir(le_dir)
for dir_name in ("config", "logs", "work"):
full_path = os.path.join(le_dir, dir_name)
os.mkdir(full_path)
full_name = dir_name + "_dir"
config[full_name] = full_path
config["domains"] = None

View File

@@ -2,20 +2,17 @@
import logging
import socket
import requests
import zope.interface
import six
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
from acme import crypto_util
from acme import errors as acme_errors
from certbot import interfaces
logger = logging.getLogger(__name__)
@zope.interface.implementer(interfaces.IValidator)
class Validator(object):
# pylint: disable=no-self-use
"""Collection of functions to test a live webserver's configuration"""
@@ -33,7 +30,7 @@ class Validator(object):
try:
presented_cert = crypto_util.probe_sni(name, host, port)
except acme_errors.Error as error:
logger.exception(error)
logger.exception(str(error))
return False
return presented_cert.digest("sha256") == cert.digest("sha256")
@@ -86,8 +83,7 @@ class Validator(object):
return False
try:
_, max_age_value = max_age[0]
max_age_value = int(max_age_value)
max_age_value = int(max_age[0][1])
except ValueError:
logger.error("Server responded with invalid HSTS header field")
return False

View File

@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.24.0.dev0'
version = '0.33.0.dev0'
install_requires = [
'certbot',
@@ -46,6 +46,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],

View File

@@ -2,4 +2,4 @@ FROM certbot/certbot
COPY . src/certbot-dns-cloudflare
RUN pip install --no-cache-dir --editable src/certbot-dns-cloudflare
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudflare

View File

@@ -122,7 +122,7 @@ class _CloudflareClient(object):
self.cf.zones.dns_records.delete(zone_id, record_id)
logger.debug('Successfully deleted TXT record.')
except CloudFlare.exceptions.CloudFlareAPIError as e:
logger.warn('Encountered CloudFlareAPIError deleting TXT record: %s', e)
logger.warning('Encountered CloudFlareAPIError deleting TXT record: %s', e)
else:
logger.debug('TXT record not found; no cleanup needed.')
else:

View File

@@ -1,10 +1,8 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.24.0.dev0'
version = '0.33.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -44,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -2,4 +2,4 @@ FROM certbot/certbot
COPY . src/certbot-dns-cloudxns
RUN pip install --no-cache-dir --editable src/certbot-dns-cloudxns
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudxns

View File

@@ -69,12 +69,15 @@ class _CloudXNSLexiconClient(dns_common_lexicon.LexiconClient):
def __init__(self, api_key, secret_key, ttl):
super(_CloudXNSLexiconClient, self).__init__()
self.provider = cloudxns.Provider({
config = dns_common_lexicon.build_lexicon_config('cloudxns', {
'ttl': ttl,
}, {
'auth_username': api_key,
'auth_token': secret_key,
'ttl': ttl,
})
self.provider = cloudxns.Provider(config)
def _handle_http_error(self, e, domain_name):
hint = None
if str(e).startswith('400 Client Error:'):

View File

@@ -1,2 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1
acme[dev]==0.31.0
certbot[dev]==0.31.0

View File

@@ -1,17 +1,15 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.24.0.dev0'
version = '0.33.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'acme>=0.31.0',
'certbot>=0.31.0',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',
@@ -44,6 +42,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

View File

@@ -2,4 +2,4 @@ FROM certbot/certbot
COPY . src/certbot-dns-digitalocean
RUN pip install --no-cache-dir --editable src/certbot-dns-digitalocean
RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-digitalocean

View File

@@ -134,7 +134,7 @@ class _DigitalOceanClient(object):
logger.debug('Removing TXT record with id: %s', record.id)
record.destroy()
except digitalocean.Error as e:
logger.warn('Error deleting TXT record %s using the DigitalOcean API: %s',
logger.warning('Error deleting TXT record %s using the DigitalOcean API: %s',
record.id, e)
def _find_domain(self, domain_name):

View File

@@ -1,10 +1,8 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.24.0.dev0'
version = '0.33.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -45,6 +43,7 @@ setup(
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',

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