Compare commits

...

167 Commits

Author SHA1 Message Date
Brad Warren
68bfec5b7e add changelog entry 2020-06-17 16:10:33 -07:00
Brad Warren
4a7bb38cd4 add unit tests 2020-06-17 16:03:28 -07:00
Brad Warren
bf9501911d Fix mypy type annotations 2020-06-17 16:03:28 -07:00
Brad Warren
bb92c256a4 Implement ocsp_response_by_paths. 2020-06-17 16:03:28 -07:00
Brad Warren
60cccb8d22 Implement _CryptographyOCSPResponse 2020-06-17 16:03:28 -07:00
Brad Warren
b958e2a311 add _query_prep 2020-06-17 16:03:28 -07:00
Brad Warren
cbaebc385c Define new interfaces 2020-06-17 16:03:28 -07:00
Brad Warren
3c4b922197 Remove the need for TRAVIS to be set. (#8084)
I initially added this when the script was doing things like migrating all LXD containers to the snap. I think the external side effects are now pretty minimal thought so I think we can remove the need for this environment variable which makes it easier to use outside of CI for manual testing.
2020-06-17 14:41:11 -07:00
Adrien Ferrand
0bb1f0b2ce Drop i386 architecture on snap build (#8083)
This PR remove the i386 architecture in the snap build process, because the base snap `core20` is not available for this architecture.
2020-06-16 15:57:05 -07:00
Brad Warren
ba192f321d Remove on_cancel which isn't recognized by Travis. (#8077) 2020-06-15 13:27:23 -07:00
Cameron Steel
961c573864 dns-cloudflare: Update docs and error messages to reflect new API permissions (#8015)
* Tweaks for improved Cloudflare API

* Update docs for dns-cloudflare

* Update tests and changelog

* Fix bad merge

* Fix error code for record add

* Improve error message

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-06-12 20:38:13 +02:00
Brad Warren
fb39de7d01 add changelog entry (#8067) 2020-06-11 10:19:51 +02:00
Brad Warren
97fcfd40d1 starts not stats (#8066) 2020-06-10 16:58:46 -07:00
Adrien Ferrand
9ac476e87b Certbot snap multiarch build (#8016)
This PR proposes a way to build the certbot snap for several architectures, using a QEMU-base emulation approach, and several optimizations to keep the build time of each snap below 20 minutes.

Most of the reasoning for the approach proposed here is described in the original PR: https://github.com/basak/certbot-snap-build/pull/27

On top of it, I added a docker pull to a pre-compiled snapcraft docker, instead of compiling it during the Travis pipeline, in order to save 5 to 7 minutes more on each snap build. The snap images are compiled and stored here: https://hub.docker.com/repository/docker/adferrand/snapcraft. Depending on the time the PR will be reviewed, we can:
* continue to use `adferrand/snapcraft`
* move its logic to certbot scope and use something like `certbot/snapcraft`
* wait for https://github.com/snapcore/snapcraft/pull/3144 to be merged, and use `snapcore/snapcraft`.

* Backport https://github.com/basak/certbot-snap-build/pull/27 into Certbot project

* Fix build deps

* Integrate proactively #8012 to fix builds on non-amd64 archs

* Configure jobs on Travis

* Focus on snap builds. Disable temporarily some jobs. Disable deploy actions by security.

* Specify TARGET_ARCH during snap build

* Do not do anything if TOXENV is not set

* Various optimizations

* Use recent version of ubuntu for get correct features on snap out of the box

* Add up to date wheels

* Organizing scripts

* Set dest dir

* Get back original configuration for Travis

* Add comments

* Update common_libs.sh

* Use adferrand/snapcraft

* Test build

* Stable snapcraft

* Update build_and_install.sh

* Move back snap builds to the cron/release pipeline

* Update snap/local/compile_native_wheels.sh

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Update snap/local/compile_native_wheels.sh

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Update snap/local/compile_native_wheels.sh

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Update snap/local/build_and_install.sh

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Enable i386 builds, various optimizations

* Update dependencies

* Configure a simple http server to serve the pre compiled wheels

* Fix wheels compilation

* Relax permissions

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-06-10 14:33:02 -07:00
ohemorange
8d776fb7ac Make Certbot find externally snapped plugins (#8054)
* Add snap plugin support

Switch to a PoC branch of certbot that supports the new
CERTBOT_PLUGIN_PATH and wrap the snap to set this variable correctly
based on the content interfaces connected.

(cherry picked from commit 7076a55fd82116d068e2aca7239209b7203917d2)

* Modify certbot.wrapper to append to PYTHONPATH instead of separate CERTBOT_PLUGIN_PATH variable

* Update certbot-wrapper to python3.6 version

* add source field

* Update changelog

* Use bash instead of sh

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Exit if something goes wrong

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* No leading : when modifying empty PYTHONPATH

* Improve bash handling of PYTHONPATH manipulation

Co-authored-by: Robie Basak <robie.basak@canonical.com>
Co-authored-by: Brad Warren <bmw@eff.org>
Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-06-10 13:52:56 -07:00
Adrien Ferrand
50fa04ba0c Implement umask for Windows (#7967)
This PR gets its root from an observation I did on current version of Certbot (1.3.0): the `renewal-hooks` directory in Certbot configuration directory is created on Windows with write permissions to everybody.

I thought it was a critical bug since this directory contains hooks that are executed by Certbot, and you certainly do not want this folder to be open to any malicious hook that could be inserted by everyone, then executed with administrator privileges by Certbot.

Turns out for this specific problem that the bug is not critical for the hooks, because the scripts are expected to be in subdirectories of `renewal-hooks` (namely `pre`, `post` and `deploy`), and these subdirectories have proper permissions because we set them explicitly when Certbot is starting.

Still, there is a divergence here between Linux and Windows: on Linux all Certbot directories without explicit permissions have at maximum `0o755` permissions by default, while on Windows it is a `0o777` equivalent. It is not an immediate security risk, but it is definitly error-prone, not expected, and so a potential breach in the future if we forget about it.

Root cause is that umask is not existing in Windows. Indeed under Linux the umask defines the default permissions when you create a file or a directory. Python takes that into account, with an API for `os.open` and `os.mkdir` that expose a `mode` parameter with default value of `0o777`. In practice it is never `0o777` (either you the the `mode` explictly or left the default one) because the effective mode is masked by the current umask value in the system: on Linux it is `0o022`, so files/directories have a maximum mode of `0o755` if you did not set the umask explicitly, and it is what it is observed for Certbot.

However on Windows, the `mode` value passed (and got from default) to the `open` and `mkdir` of `certbot.compat.filesystem` module is taken verbatim, since umask does not exit, and then is used to calculate the DACL of the newly created file/directory. So if the mode is not set explicitly, we end up with files and directories with `0o777` permissions.

This PR fixes this problem by implementing a umask behavior in the `certbot.compat.filesystem` module, that will be applied to any file or directory created by Certbot since we forbid to use the `os` module directly.

The implementation is quite straight-forward. For Linux the behavior is not changed. On Windows a `mask` parameter is added to the function that calculates the DACL, to be invoked appropriately when file or directory are created. The actual value of the mask is taken from an internal class of the `filesystem` module: its default value is `0o755` to match default umasks on Linux, and can be changed with the new method `umask` that have the same behavior than the original `os.umask`. Of course `os.umask` becomes a forbidden function and `filesystem.umask` must be used instead.

Existing code that is impacted have been updated, and new unit tests are created for this new function.

* Implement umask for Windows

* Set umask at the beginning of tests

* Fix lint, update local oldest requirements

* Update certbot-apache/setup.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Improve tests

* Adapt filesystem.makedirs for Windows

* Fix

* Update certbot-apache/setup.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Changelog entries

* Fix lint

* Update certbot/CHANGELOG.md

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-06-09 17:08:22 -07:00
Brad Warren
e31834a6cd Stop running snapcraft with sudo (#8063)
* Do not use sudo when building the snap.

* add user to lxd

* Run with lxd group.
2020-06-09 14:46:11 -07:00
Brad Warren
340a4280ea Merge pull request #8053 from certbot/upgrade-acmev1
Read acmev1 Let's Encrypt server URL from renewal config as acmev2 URL
2020-06-09 11:43:06 -07:00
Brian Heim
cc07722b3e Fix certbot.compat.filesystem documentation (#8058)
* Fix bad rst docstrings

* AUTHORS.md: add Brian Heim

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2020-06-08 14:00:16 -07:00
Rasesh Patel
67bcf0f6bd Remove documentation for configuring ciphersuites (#8027) (#8056)
Issue #1123 discusses a feature that allows users to set the cipher
security level. That feature wasn't built. It didn't provide enough
user value to justify the corresponding increase in complexity. The
feature request and the associated discussion threads were closed.
However, the proposed API spec and the TODO section remained in the
cipher docs. They're a vestige of that issue from olden days and this PR
removes those last living traces...

Fixes #8027.
2020-06-08 12:15:44 -07:00
Brad Warren
1b2328f18b Add comment about pyca's use of tools script (#8044) 2020-06-08 12:14:02 -07:00
Brian Heim
560b9e5012 AUTHORS.md: fix GH url for Brandon Kreisel (#8059) 2020-06-08 12:13:29 -07:00
Lloyd Parkes
2f6fbe9987 Add support for NetBSD (#8033)
* Add support for NetBSD by telling certbot-nginx where the nginx
configuration directory is.

* Update the CHANGELOG.

* Pass the right type of sequence to "in". Thanks lint.

* Adjust the CHANGELOG.md entry following feedback from ohemorange.

Co-authored-by: Lloyd Parkes <lloyd@must-have-coffee.gen.nz>
2020-06-08 12:06:38 -07:00
Erica Portnoy
bebcad0588 update changelog 2020-06-04 15:57:34 -07:00
Erica Portnoy
92f26367eb Merge remote-tracking branch 'alexzorin/7979_restore_v1_as_v2' into upgrade-acmev1 2020-06-04 14:29:56 -07:00
alexzorin
d135e6140b apache: handle statically linked mod_ssl (#8007)
In #7771, the Apache configurator gained the ability to identify what
version of OpenSSL Apache's ssl_module is linked against. However, the
detection was only functional if the module was built as a DSO (which is
almost always the case).

This commit covers the case where the ssl_module is statically linked
within the Apache binary. It requires the user to specify the path to
the binary (with --apache-bin) and emits a warning if static linking is
detected but no path has been provided.
2020-06-04 10:34:10 -07:00
Adrien Ferrand
010b38fa10 Upgrade Certbot dependencies (#8036)
This PR upgrades Certbot pinned dependencies through `letsencrypt-auto-source/rebuild_dependencies.py` while taking into account the problems detected in https://github.com/certbot/certbot/pull/8035:
* `cryptography` is pinned to `2.8` to continue to support OpenSSL 1.0.1 on non-x86 ancient Linux distributions (RHEL 6 + Debian 8)
* `parsedatetime` is pinned to `2.5` because of an incompatibility with Python 2.7 (see https://github.com/bear/parsedatetime/issues/246)
* `letsencrypt-auto-source/rebuild_dependencies.py` now takes into account the environment markers that are aded to `AUTHORITATIVE_CONSTRAINTS`: this is used for the `enum34` dependency, to not install it on Python 3.6+ and not break the distribution by swapping the built-in `enum` module during the setup of Certbot venv.

Fixes #8030

* Pin cryptography and parsedatetime

* Upgrade dependencies

* Remove authoritative constraint

* Upgrade dependencies

* Rebuild certbot-auto

* Update letsencrypt-auto-source/rebuild_dependencies.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Honor specific requirements in the AUTHORITATIVE_CONSTRAINTS

* Fix injection

* Update dependencies

* Update rebuild_dependencies.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-06-04 08:59:45 -07:00
ohemorange
8c8d3fab91 Merge pull request #8040 from certbot/candidate-1.5.0
Release 1.5.0
2020-06-02 12:19:39 -07:00
Brad Warren
baf69d210b Bump version to 1.6.0 2020-06-02 10:32:41 -07:00
Brad Warren
beea2d2208 Add contents to certbot/CHANGELOG.md for next version 2020-06-02 10:32:40 -07:00
Brad Warren
4938273e0f Release 1.5.0 2020-06-02 10:32:38 -07:00
Brad Warren
466b4fbf71 Update changelog for 1.5.0 release 2020-06-02 10:12:33 -07:00
Brad Warren
95ae5f69f5 Make rebuild_dependencies.py executable (#8039) 2020-06-02 19:11:47 +02:00
ohemorange
2acc1dcc89 Fix TLS-ALPN tests with newer versions of OpenSSL (#8026)
Fixes #7988. As described there, the steps involved are:

1. Update our tests so they fail due to this problem.
2. Update the keys used in the tests so they pass with the new changes.

For 1, see a [failing travis run](https://travis-ci.com/github/certbot/certbot/jobs/340710511) with the included change. And for the full output to confirm that this is what is failing, see a [run on debian 10](https://github.com/certbot/certbot/files/4692350/debian_run_log.txt).

This PR adds `rsa4096_key.pem` and `rsa4096_cert.pem`, updates the `TLS-ALPN` test to use those keys in place of the 1024-bit versions, and fixes the README in that `testdata` folder with correct instructions to generate these files.

* export PIP_NO_BINARY in pip install subshell in test_sdists.sh

* set environment variable on the line that installs most packages

* Generate 4096-bit rsa key and cert, and fix README instructions to do so.

* Update TLS_ALPN test to use 4096-bit key instead of 1024-bit key.

* Update changelog

* Older versions of Python have an error when both VIRTUAL_NO_DOWNLOAD and PIP_NO_BINARY are set, so only apply the latter at the install phase.

* Add enum34 constraint manually, since rebuild_dependencies.py seems to be broken.

* only delete key if it exists

* Check OpenSSL version before trying to set PIP_NO_BINARY

* Add comment explaining why we only set PIP_NO_BINARY at the install step
2020-06-01 15:18:38 -07:00
Brad Warren
fa55b468c8 Revert "Upgrade pinned certbot dependencies (#8012)" (#8035)
This reverts commit 6b97ac3344.
2020-06-01 20:17:26 +02:00
Brad Warren
cd27dcc32c Add the content interface to Certbot (#8009)
* Add the content interface to Certbot

This commit contains a subset of the changes from 7076a55fd82116d068e2aca7239209b7203917d2.

* Normalise slot parameters

(cherry picked from commit 810941979bcf609c1e0be18e9263abf046b90e82)

Co-authored-by: Robie Basak <robie.basak@canonical.com>
2020-05-27 13:59:08 -07:00
Adrien Ferrand
6b97ac3344 Upgrade pinned certbot dependencies (#8012)
* Upgrade certbot dependencies

* Rebuild letsencrypt-auto
2020-05-26 15:19:10 -07:00
ohemorange
332def46da Require explicit confirmation of snap plugin permissions before connecting (#8013)
Fixes #7667.

Implements the plan described in #7667.

Here's a terminal log showing that it does so:

```
# sudo snap connect certbot:plugin certbot-dns-dnsimple
error: cannot perform the following tasks:
- Run hook prepare-plug-plugin of snap "certbot" (run hook "prepare-plug-plugin": 
-----
Only connect this interface if you trust the plugin author to have root on the system
Run `snap set certbot trust-plugin-with-root=ok` to acknowledge this and then run this command again to perform the connection
-----)
# snap set certbot trust-plugin-with-root=ok
# sudo snap connect certbot:plugin certbot-dns-dnsimple
# sudo snap disconnect certbot:plugin certbot-dns-dnsimple:certbot
# sudo snap connect certbot:plugin certbot-dns-dnsimple
error: cannot perform the following tasks:
- Run hook prepare-plug-plugin of snap "certbot" (run hook "prepare-plug-plugin": 
-----
Only connect this interface if you trust the plugin author to have root on the system
Run `snap set certbot trust-plugin-with-root=ok` to acknowledge this and then run this command again to perform the connection
-----)
```

* Add plugin connection hook to accept root trust

* snapctl requires a configure hook to set options

* Add sh notice

* Update changelog
2020-05-26 12:02:33 -07:00
Adrien Ferrand
b42e24178a Consistent directory mode apply when makedirs is called (#8010)
Fixes #7993 

This PR uses `os.umask()` during `certbot.compat.filesystem.makedirs()` call to ensure that all directories, and not only the leaf one, have the provided `mode` when created. This ensures a safe and consistent behavior independently from the Python version, since the behavior of `os.makedirs` changed on that matter with Python 3.7.

* Implement logic to apply the same permission on all dirs created by makedirs

* Add a test

* Add comment

* Update certbot/certbot/compat/filesystem.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-05-21 15:29:06 -07:00
Alex Zorin
8a15bd7927 renewal: disregard acme-v01 in renewal configs
Fixes #7979
2020-05-21 23:01:53 +10:00
ohemorange
3ea5170647 Error out earlier in apache installer when mod_ssl is not available (#7984)
* Error out in apache installer when mod_ssl is not available

* Update to MisconfigurationError and add/fix tests

* Remove error cases we no longer hit and associated test

* mock out function to have consistent error across machines

* improve changelog message

* only check key in modules list, not value
2020-05-19 15:34:21 -07:00
schoen
0b53c0d476 Merge pull request #7952 from ntkme/allow-empty-existing-dir
Allow existing but empty archive and live dir to be used when creating new lineage
2020-05-19 15:25:32 -07:00
Brad Warren
4eb9a71a4c remove quay cruft (#8003)
Our README still has links to our old quay.io builds which we shutdown a while ago. See #4343. This PR simply removes the old stray links.
2020-05-19 14:29:28 -07:00
Brad Warren
96e003d1a3 mention python3-venv in docs (#8006)
The error message from `python3 -m venv` when you don't have `python3-venv` installed is pretty good, but lets skip the failure and make sure it is installed the first time.
2020-05-19 14:28:41 -07:00
schoen
7a7c6737cc Merge pull request #8000 from certbot/make-exit-message-red
Print cause of exit in red text
2020-05-18 17:13:03 -07:00
Brad Warren
0e59c6ba1b handle more cases 2020-05-18 10:17:32 -07:00
Brad Warren
d230dcafeb Print cause of exit in red text. 2020-05-18 09:31:15 -07:00
alexzorin
bcf33c6659 ocsp: add support for public key hash ResponderIDs (#7989)
For both cases where the the response is signed by the issuer, or by a
delegated OCSP signer.

Resolves #7986
2020-05-13 15:55:35 -07:00
Brad Warren
71e3d82e47 Remove passthrough because its no longer needed (#7956) 2020-05-06 14:28:15 -07:00
Adrien Ferrand
bb6a660b21 Platform-agnostic method to copy owner and permissions between files (#7968)
Related to #7649 since @joohoi needs a method to copy owner and permissions together from a source file to a destination file.

This PR creates the method `copy_ownership_and_mode()` in `certbot.compat.filesystem` module to achieve this goal. Its behavior is consistent across Linux and Windows in respect to the security model that have been defined for Certbot on Windows.

The method behaves globally the same than `copy_ownership_and_apply_mode`, but this time the permissions are extracted from the source file. For Windows it means that the DACL is copied from the source to the destination with the same content.

* Create copy_ownership_and_mode to copy both owner and mode from src to dst

* Update certbot/tests/compat/filesystem_test.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>

* Fix docstring

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-05-06 12:33:50 -07:00
Brad Warren
c35054fd11 Merge pull request #7974 from certbot/candidate-1.4.0
Release 1.4.0
2020-05-05 17:06:31 -07:00
Erica Portnoy
b224b49986 Bump version to 1.5.0 2020-05-05 13:44:23 -07:00
Erica Portnoy
1a72cdecf2 Add contents to certbot/CHANGELOG.md for next version 2020-05-05 13:44:23 -07:00
Erica Portnoy
5586ae071a Release 1.4.0 2020-05-05 13:44:21 -07:00
Erica Portnoy
ca60ad52b9 Update changelog for 1.4.0 release 2020-05-05 12:37:33 -07:00
Brad Warren
9154e7965f drop min certbot coverage (#7972)
`tox -e cover` fails for me on macOS. This is due to the differences in the code that is run when on Linux vs. other platforms in `certbot.util` and its tests. Diffing my local coverage with Travis, the only difference in lines missing test coverage is:
```
$ diff travis.txt local.txt
< certbot/certbot/util.py                                        238     24    90%   132-134, 210, 275-281, 318, 330-340, 347, 381-382
> certbot/certbot/util.py                                        239     34    86%   30, 132-134, 210, 275-281, 301, 305, 316-318, 330-340, 347, 367-373, 381-382
< certbot/tests/util_test.py                                     392      0   100%
> certbot/tests/util_test.py                                     375     26    93%   483-487, 492-502, 507-514, 548-550, 555-557
```
I think tests on `master` should not be failing locally for people.

While there would be other ways to fix this by adding `# pragma: no cover` lines or writing mocked out tests for other platforms, I personally just think dropping the minimum coverage one percentage point is fine at least for now.
2020-05-05 09:38:20 -07:00
Brad Warren
ac2d691ade Add warning about ignoring our own warnings (#7971)
Coming out of the conversation at #7863 in the linked Google Doc, we should always have at least 1 release between updating one of our plugins to stop using a deprecated acme/certbot API and removing it from acme/certbot. Doing this gives the plugin changes time to propagate rather than potentially having the plugin break because Certbot was updated before the plugin had made the necessary changes.

This comment here should help ensure this.

* Add pytest warnings warning.

* clarify comment
2020-05-04 16:54:09 -07:00
schoen
5536c91223 Merge pull request #7938 from taixx046/1618-awkward-language-during-email-problems
Fixed #1618 awkward language during email problems
2020-04-27 18:33:07 -07:00
Adrien Ferrand
9cbb13ef04 Run hooks with Powershell on Windows (#7800)
Fixes #7713.

As discussed in #7713, providing a Powershell script as hook for Certbot is not working currently. This is because hooks are run in a `cmd` environment, that recognizes only `.bat` files as valid scripts that can be run from their bare name on command line.

On the other hand, the Powershell both `.bat` and `.ps1` scripts as valid scripts.

This PR makes hooks command be executed by Powershell, instead of `cmd` as `Popen` does by default when `shell=true` is used. It also modifies the tests to handle this new environment, in particular in term of encoding (UTF-16-LE is the default one in Powershell).

* Run hooks in powershell on Windows

* Fix hook test

* Fallback to unittest.mock

* In fact, shell_cmd as a list of str could not work. Declare only str as acceptable input for shell_cmd.

* Added changelog
2020-04-27 09:38:30 -07:00
なつき
db6de76b11 Allow existing but empty archive and live dir 2020-04-25 01:47:30 -07:00
Brad Warren
01dc981a09 Merge pull request #7948 from certbot/snap-build-squashed
Despite this PR (only) being ~200 lines containing mostly code copied from another repo, there is a lot going on here. For the sake of making it both easier to review and to remember some of these things in the future by referring back to this PR, I've documented a lot of noteworthy things with section headers below. With that said, it's probably not necessary to read each section unless you're interested in that topic.

The most noteworthy thing for the reviewer is **this PR should be merged and not squashed** to preserve authorship. To merge this code, once we're happy with this PR, I'll probably open a new PR squashing any commits I make in response in review comments back into a single commit to try to keep history somewhat clean. To help prevent this PR from being accidentally squashed, I'm making this a draft PR for now.

### Git history of https://github.com/basak/certbot-snap-build

I think it is worth preserving the git history of https://github.com/basak/certbot-snap-build that this PR is based on in this repo to help us track why things were done a certain way. To do this while keeping our git history somewhat clean, I took the approach described at https://stackoverflow.com/questions/1425892/how-do-you-merge-two-git-repositories/21495718#21495718 to move all history of https://github.com/basak/certbot-snap-build into a `snap` directory. I then squashed all commits so that sequential commits from the same author are one commit. I probably could have reordered commits to try and squash things a little more, but I personally don't think it's worth the trouble. Finally, I merged this rewritten history into this branch of the Certbot repo.

The contents of the `snap` directory are identical to the current contents of https://github.com/basak/certbot-snap-build before my final commit in this PR which makes the changes to make things work in this repo.

### Travis stages

This is described in general at https://docs.travis-ci.com/user/build-stages/, but I don't think we should deploy the snap if any of our tests are failing. To accomplish this, I created a "Snap" stage that builds, tests, and deploys the snap which is only executed after a "Test" stage that contains all of our other tests. The "Snap" stage will not run until the "Test" stage completes successfully.

### snap/local

This directory is ignored by `snapcraft` which I think makes it a good place to store `snap` specific scripts like `build_and_install.sh`.

See https://bugs.launchpad.net/snapcraft/+bug/1792203 for more info.

### Why remove certbot-compatibility-test from apacheconftest toxenvs?

Because it's not used. In theory, it could go in its own PR, but it'll create merge conflicts with this one so I'd personally prefer to include this simple change in this PR as well.

### Checklist for landing this PR

- [x] Squash all of my commits into one commit
- [x] Update the release instructions to have to move the snap to the beta channel
- [x] Shut down Robie's nightly builds probably by updating his repo to say that the code has moved here and deleting everything
2020-04-24 14:13:09 -07:00
Brad Warren
335894ab3b Merge snap code into the Certbot repo
* merge .gitignore

* Move snapcraft.yml up one level.

* update source

* move test.sh to tox.ini

* use new tox.ini in .travis.yml

* move snap build code

* make script executable

* remove unused python3-dev

* don't use deprecated classic flag

* go back to stable channel

* add nginx in snap addons

* add deploy steps

* Add comments explaining external tox envs.

* error if not in CI

* don't use --depth

* remove old .travis.yml

* Add big comment about SNAP_TOKEN.

* Set all_branches: true.

* Add repo setting.

* run travis on tags

* Add more documenting comments to .travis.yml.
2020-04-24 13:47:36 -07:00
Brad Warren
4cce3458f3 Avoid deleting the workspace twice. (#7923) 2020-04-23 23:29:16 +02:00
Brad Warren
d71d3a1144 dont use venv.py (#7936) 2020-04-23 23:27:05 +02:00
Brad Warren
b06dacdfb5 Add --chain-path to --help install output. (#7937) 2020-04-23 23:26:34 +02:00
taixx046
e5cde2c598 updated changelog.md 2020-04-23 15:29:38 -05:00
taixx046
84b6c3cebb reorganized error message when a user entered an invalid email address 2020-04-23 15:20:32 -05:00
ohemorange
a06d5ac7a1 Deprecate certbot-auto on Gentoo, macOS, and FreeBSD (#7926)
* Deprecate certbot-auto on Gentoo

* Deprecate certbot-auto on macOS

* Deprecate certbot-auto on FreeBSD

* build le-auto

* Update Changelog

* Deprecate certbot-auto on Gentoo, FreeBSD, and macOS
2020-04-23 12:49:35 -07:00
Brad Warren
9d94c6c5ef Remove useless before_install lines (#7920) 2020-04-22 13:10:13 -07:00
Brad Warren
ed9648b4a3 Add snap instructions (#7889)
Part of #7671.

* Add snap instructions.

* Mention Linux+systemd
2020-04-22 11:30:33 -07:00
Adrien Ferrand
2bcabe6626 Fix certbot part build in snap
* Declare properly source-subdir to build the certbot part

* Use snapcraft 3.10+, remove the custom python plugin
2020-04-21 16:36:49 -07:00
Brad Warren
c12baf7d8c Fix path to apache-conf-test 2020-04-21 16:36:49 -07:00
Sergio Schvezov
193b44a0fa Snap plugin
* snap: move snapcraft.yaml to snap directory

Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>

* snap: use a local plugin to get around the delivered plugin

Add a plugin to the project which behaves as expected until a version
of snapcraft satisfies the project needs.

Additional snapcraft.yaml changes were made to accommodate for the snap
to build.

Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>

* snap: compile pycache in the last step for the last part

Signed-off-by: Sergio Schvezov <sergio.schvezov@canonical.com>
2020-04-21 16:36:49 -07:00
Robie Basak
b69f5588f4 Continued improvements
* Remove legacy Store upload credentials

These have not been needed since 5d7969a.

* Work around dev part dependency failure case

Get pip to install certbot from its VCS repository directly during the
build of the nginx and apache plugin parts.

This works around issue #12 when it affects the interaction between the
apache or nginx plugin and certbot itself.

It does not work around the case where the same problem occurs in the
interaction between certbot and acme. This looks harder to work around
because pip's VCS URL handling doesn't appear to include a facility to
install from a subdirectory of a git repository and this is where the
acme source is located.

* Switch to using lxd for the snapcraft run

The Docker image is 16.04 only. Before we can switch to 18.04, we need
to remove Docker, which means using the snapcraft snap using Travis' new
snap support, and then using the lxd functionality in snapcraft so that
it is enabled to build in the appropriate environment.

* Switch build to use core 18

Fixes: #14

* Move constraints into a list

This seems to be a requirement either either newer snapcraft, snapcraft
in lxd or in the move to core18 (it isn't clear to me which). This fixes
the error:

Failed to load plugin: properties failed to load for certbot: The 'constraints' property does not match the required schema: '$SNAPCRAFT_PART_SRC/constraints.txt' is not of type 'array'

* version-script -> snapcraftctl set-version

The use of version-script seems to break with either newer snapcraft,
snapcraft with lxd or core18 (it's not clear to me which). The breakage
is related to "parts/certbot/src" not being found. This can be fixed
with $SNAPCRAFT_PART_SRC, but this doesn't seem to be defined at
"version-script time".

However https://snapcraft.io/docs/deprecation-notices/dn10 deprecates
the use of version-script, and if we convert to the recommended new way,
then we use override-pull instead and $SNAPCRAFT_PART_SRC is defined
there, so this conveniently fixes both problems at once.

* Do not explicitly install snapd

Since this is now handled by the Travis addon, we do not need to do it
explicitly.
2020-04-21 16:36:49 -07:00
Adrien Ferrand
2c622944dd Various optimizations part 2
* Revert logic of building against a tag
* Fix schema, add nginx
* Update snapcraft.yaml
* Update snapcraft.yaml
* Update snapcraft.yaml
* Update test.sh
* Update test.sh
* Update test.sh
* Update test.sh
* Config tests
* Add an apache test
* Relaunch CI
* Clean config
* Install venv
* Decompose steps
* Update test.sh
* Use virtual environment
* Update python-augeas
* Add fork python-augeas
* Update .travis.yml
* Exclusion rule
* Try with after
2020-04-21 16:36:49 -07:00
Robie Basak
e0b72d9a62 Travis improvements
* Add Travis notifications

* Adjust automatic snap deployment configuration

Travis now has a documented[1] "snap" provider and the previous
experimental mechanism seems to have stopped working, presumably because
it was deprecated in favour of this new mechanism.

[1] https://docs.travis-ci.com/user/deployment/snaps/
2020-04-21 16:36:49 -07:00
Adrien Ferrand
d63be466a8 Various optimizations part 1
* Configure for python3
* Update tests
* Use appropriate virtualenv
* Install nginx for the integration tests
* Try use LD_LIBRARY_PATH to find augeas shared library in snap when python-augeas is invoked
* Update travis to use build-in setup capabilities
* Update .travis.yml
* Add acme build
* Update tests
* Try more recent dist
* Update command
* Clean tests
* Add back augeas
* Add env
* Revert to last working snapcraft config
* Add a gitignore
* Reintegrate acme. Declare augeas in certbot parts
* Use release version of certbot
* Try new approach
* Fix config
* Directly install version of python-augeas from pypi
* Restart from basic
* Clone only once certbot repository. Use pinned versions of dependencies from certbot-auto.
* Try relatively to source
* Use snapcraft env variables
* Strip hashes
* Fix path
* Redefine path
* Continue to prepare the runtime
* Fix command line
* Update .travis.yml
* Add back certbot-apache
* Update snapcraft.yaml
* Build snap against the latest release of certbot
2020-04-21 16:36:49 -07:00
Robie Basak
0f6486ec7f Initial commit
* Add renewal timer

* Install libaugeas0 in python-augeas part build

This part needs libaugeas0 to build.

* Bump to 0.26.1

* Always act directly on upstream master

I want to keep this always working, so move to master. We can
reintroduce upstream stable releases when we are ready for general use.

Closes: #5

That particular issue seems to no longer happen. Presumably something
changed in upstream git or in PyPI. If it happens again, hopefully I'll
have CI against upstream master up by then and I'll be able to pin it
down.

* Add empty Travis build

* Add Travis automatic snap edge publication

* Add integration test

This uses upstream's test suite from their source tree to check the
built snap to make sure it behaves as expected, before attempting upload
to the store.

* Point Augeas to its lens library

Augeas defaults to looking in /usr/share/augeas/lenses, which in a snap
isn't found at this path, but inside $SNAP. So set AUGEAS_LENS_LIB to
where the lenses can be found within the snap.

This fixes the Apache plugin that uses Augeas.
2020-04-21 16:36:49 -07:00
Brad Warren
06e68cce44 flip condition (#7927)
In #7925 we accidently changed the logic here. Before it was:

type = cron OR (type = push AND branch NOT IN (apache-parser-v2, master))

now it's

type = cron OR (type = push AND branch = master)

We want to be able to run our full test suite on things like test-* branches. The reason we had been excluding master is it has the full test suite run on it (through what Travis calls cron) nightly and not running on every commit helps prevent us on waiting on CI since our nightly tests spin up so many jobs.

This PR changes things back to the intended behavior.

(We could talk about changing the condition to just type = cron OR type = push if we want, but I'd rather do that in a separate PR once things like test- branches are fixed.)
2020-04-21 16:34:50 -07:00
ohemorange
eed45827ad Remove references to the apache-parser-v2 branch (#7925)
Fixes #7786.
2020-04-21 13:06:30 -07:00
ohemorange
751d836746 Update using.rst to accurately describe acmev2 behavior (#7924)
Fixes #7268

I removed the reference to automatically selecting which ACME protocol we use, since at some point we'll want to rip out the non-spec-compliant ACMEv1 code.
2020-04-21 12:47:59 -07:00
Brad Warren
6693e87500 Update dev docs to reflect Python 2 EOL. (#7914)
Python 2 is going to get harder and harder to install locally so I don't think we should assume/require devs to have it installed.

This PR builds on #7905 so our developer guide only has people use Python 3.
2020-04-21 10:27:48 -07:00
schoen
08cea381c8 Merge pull request #7917 from certbot/fedora31
Test on fedora 31
2020-04-20 19:36:24 -07:00
schoen
84b5c571c0 Merge pull request #7916 from certbot/ubuntu-1910
Test on Ubuntu 19.10
2020-04-20 19:35:41 -07:00
schoen
0f35836deb Merge pull request #7919 from certbot/rate-limits-url
Update rate limits url
2020-04-20 19:29:18 -07:00
Brad Warren
5b749ff8f7 Use Python 3 in the release script. (#7918)
Fixes #7902.
2020-04-20 14:44:53 -07:00
Brad Warren
41306e1e37 update rate limits url 2020-04-20 09:20:31 -07:00
Brad Warren
864ea08341 test on fedora 31 2020-04-16 15:00:28 -07:00
Brad Warren
74eea40905 test on ubuntu 19.10 2020-04-16 14:57:36 -07:00
Brad Warren
859dc38cb9 Consolidate cover envs and default to py3-cover (#7905)
* Consolidate cover envs and default to py3-cover

* use py38 for code coverage in Travis

* Disable coverage on Python < 3.6 line.
2020-04-16 08:59:40 -07:00
April King
f66314926a Update URL for Mozilla SSL Configuration Generator (#7912) 2020-04-15 13:54:17 -07:00
ohemorange
9c345ac301 Do not require mock in Python 3 in certbot-dns modules (#7900)
Part of #7886.

This PR conditionally installs `mock` in `certbot-dns-*/setup.py` based on setuptools version and python version, when possible. It then updates the tests to use `unittest.mock` when `mock` isn't available.

* Do not require mock in Python 3 in certbot-dns modules

* update changelog

* error when trying to build wheels with old setuptools

* add type: ignores
2020-04-15 11:54:44 -07:00
ohemorange
af21d1d56e Do not require mock in Python 3 in certbot-compatibility-test module (#7899)
* Do not require mock in Python 3 in certbot-compatibility-test module

* error when trying to build wheels with old setuptools

* add type: ignores
2020-04-15 11:40:03 -07:00
ohemorange
49912732ac Do not require mock in Python 3 in nginx module (#7898)
* Do not require mock in Python 3 in nginx module

* error when trying to build wheels with old setuptools

* add type: ignores
2020-04-15 11:39:44 -07:00
ohemorange
8fb9a395ab Do not require mock in Python 3 in apache module (#7896)
Part of #7886.

This PR conditionally installs mock in `apache/setup.py` based on setuptools version and python version, when possible. It then updates `apache` tests to use `unittest.mock` when `mock` isn't available.

* Conditionally install mock in apache

* error out on newer python and older setuptools

* error when trying to build wheels with old setuptools

* use unittest.mock when third-party mock isn't available in apache, with no cover and type ignore
2020-04-15 11:30:08 -07:00
ohemorange
35fb99b86f Do not require mock in Python 3 in certbot module (#7911)
This PR is exactly the same as #7895, but know we know a little bit more about what was going on with `mypy`.

Part of #7886.

This PR conditionally installs mock in `certbot/setup.py` based on setuptools version and python version, when possible. It then updates `certbot` tests to use `unittest.mock` when `mock` isn't available.

* Conditionally install mock in certbot

* use unittest.mock when third-party mock isn't available in certbot

* Add type:ignores because of https://github.com/python/mypy/issues/1153

* error out on newer python and older setuptools

* error when trying to build wheels with old setuptools
2020-04-15 11:28:47 -07:00
ohemorange
127d2dc307 Do not require mock in Python 3 in acme module (#7910)
Part of #7886.

This PR conditionally installs mock in `acme/setup.py` based on setuptools version and python version, when possible. It then updates `acme` tests to use `unittest.mock` when `mock` isn't available.

Now with `type: ignore` as appropriate. Once the "future steps" of #7886 are finished, and mypy is on Python 3, the `pragma no cover`s and `type ignore`s will be gone.

* Conditionally install mock in acme

* error out on newer python and older setuptools

* error when trying to build wheels with old setuptools

* use unittest.mock when third-party mock isn't available in acme, with no cover and type ignore
2020-04-15 11:27:55 -07:00
Brad Warren
569df2d37a Remove Ubuntu 19.04 tests. (#7906)
This PR fixes the Travis failures that can be seen https://travis-ci.com/certbot/certbot/builds/160258644. Running the tests locally, it looks like Ubuntu has started shutting down the 19.04 repos which makes sense as this release has been EOL'd. See https://wiki.ubuntu.com/Releases.

I have the full suite including the test farm tests running at https://travis-ci.com/github/certbot/certbot/builds/160269969 with this change.

The issue of adding 19.10 to our test farm tests is tracked by #7851. I think that issue is important and it's in our current milestone, but I'd personally rather get our tests passing for now and try to expand them to run on other systems later.
2020-04-14 17:01:59 -07:00
ohemorange
ff732bf975 Revert the last two mock PRs (#7903)
* Revert "Do not require mock in Python 3 in certbot module (#7895)"

This reverts commit 77871ba71c.

* Revert "Do not require mock in Python 3 in acme module (#7894)"

This reverts commit cd0acf5dcc.
2020-04-13 17:09:24 -07:00
ohemorange
77871ba71c Do not require mock in Python 3 in certbot module (#7895)
Part of #7886.

This PR conditionally installs mock in `certbot/setup.py` based on setuptools version and python version, when possible. It then updates `certbot` tests to use `unittest.mock` when `mock` isn't available.

* Conditionally install mock in certbot

* use unittest.mock when third-party mock isn't available in certbot

* Add type:ignores because of https://github.com/python/mypy/issues/1153

* error when trying to build wheels with old setuptools
2020-04-13 14:33:23 -07:00
ohemorange
cd0acf5dcc Do not require mock in Python 3 in acme module (#7894)
Part of #7886.

This PR conditionally installs mock in acme/setup.py based on setuptools version and python version, when possible. It then updates acme tests to use unittest.mock when mock isn't available.

* Conditionally install mock in acme

* use unittest.mock when third-party mock isn't available in acme

* error when trying to build wheels with old setuptools
2020-04-13 14:32:22 -07:00
Karan Suthar
8e4dc0a48c Minor bugfixes (#7891)
* Fix dangerous default argument

* Remove unused imports

* Remove unnecessary comprehension

* Use literal syntax to create data structure

* Use literal syntax instead of function calls to create data structure

Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2020-04-13 10:41:39 -07:00
ohemorange
316e4640f8 Upgrade the test farm tests to use Python 3 (#7876)
Fixes #7857.

* stop using urllib2 in test farm tests

* use six for urllib instead

* remove fabric lcd usage

* correct lcd removal

* remove fabric cd

* convert some remote calls to v2

* move more cxns to v2

* get run working with prefix

* get sudo commands working

* remove final fabric v1 references including local

* update requirements and README

* add new venv to gitignore

* update version used in travis

* remove deploy_script unused kwargs

* fix killboulder implementation so I can test creating a new boulder server

* hardcode the gopath due to broken env manamagement in fabric2

* Update letstest readme

* move the comment about hardcoding the ggopath

* catch BaseException instead of Exception

* work around fabric #2007

* use connections as context managers to ensure they're closed

* remove reference to virtualenv
2020-04-09 14:35:47 -07:00
inejge
537bee0994 Add minimal proxy support for OCSP verification (#7892)
Translate a proxy specified by an environment variable ("http_proxy"
or "HTTP_PROXY") into options recognized by "openssl ocsp". Support
is limited to HTTP proxies which don't require authentication.

Fixes #6150
2020-04-09 11:25:39 -07:00
alexzorin
e9895d2ec6 Fix fullchain parsing for CRLF chains (#7877)
Fixes #7875 .

After [this comment](https://github.com/certbot/certbot/issues/7875#issuecomment-608145208) and evaluating the options, I opted to go with `stricttextualmsg`, as required by RFC 8555. Reasoning is that the ACME v1 code path (via OpenSSL) produces a `fullchain_pem` which satisfies `stricttextualmsg`, so we don't need to be more generous than that.

One downside of the `re` approach is that it doesn't seem capable of capturing repeating group matches. As a result, it matches each certificate individually, silently passing over any data in between the encapsulation boundaries, such as explanatory text, which is prohibited by RFC 8555.

It would be ideal to raise an error when encountering such a non-conformant chain, but we'd need to create a mini-parser to do it, I think.

* Fix fullchain parsing for CRLF chains.

fullchain parsing now works in two passes:

1. A first pass which is generous with what it accepts - basically
   preeb(CERTIFICATE)+anything+posteb(CERTIFICATE). This determines
   the boundaries for each certificate.
2. A second pass which normalizes (by parsing and re-encoding) each
   certificate found in the first pass.

* typo in docstring

* remove redundant group in regex

* can't use assertRaisesRegex until py27 is gone
2020-04-07 16:19:13 -07:00
Brad Warren
5992d521e2 Print boulder logs when boulder setup fails (#7885)
This is part of https://github.com/certbot/certbot/issues/7303.

* Print boulder logs if boulder fails to start

* Print description and fix command.

* Change output to stderr.
2020-04-07 15:26:19 -07:00
alexzorin
4ca86d9482 acme: socket timeout for HTTP standalone servers (#7388)
* acme: socket timeout for HTTP standalone servers

Adds a default 30 second timeout to the StreamRequestHandler for clients
connecting to standalone HTTP-01 servers. This should prevent most cases
of an idle client connection from preventing the standalone server from
shutting down.

Fixes #7386

* use idiomatic kwargs default value

* move HTTP01Server lower to fix mypy forward ref.

* fix test crash on macOS due to socket double-close

* maybe its not an OSError?

* disable coverage check on useless branch
2020-04-01 23:53:58 +02:00
Adrien Ferrand
bc3088121b Add a step to check powershell version in vs2017-win2016 (#7870)
Following discussion at https://github.com/certbot/certbot/pull/7539#issuecomment-572318805, this PR adds a check for Powershell version: we expect that the `vs2017-win2016` node that will test the installer has Powershell 5.x, and nothing else.

This ensure that at least one node of the pipeline is testing the installer with the lowest Powershell version supported by Certbot.

One full pipeline success can be seen here: https://dev.azure.com/adferrand/certbot/_build/results?buildId=713

I also create on purpose a failing pipeline, that would check that Powershell 6.x is installed. Its result can be seen here: https://dev.azure.com/adferrand/certbot/_build/results?buildId=714
2020-03-31 13:12:42 -07:00
ohemorange
dbda499b08 Remove interactive redirect ask (#7861)
Fixes #7594.

Removes the code asking interactively if the user would like to add a redirect.

* Remove interactive redirect ask

* display.enhancements is no longer used, so remove it.

* update changelog

* remove references to removed display.enhancements

* add redirect_default flag to enhance_config to conditionally set default for redirect value

* Update default in help text.
2020-03-31 12:12:14 -07:00
Brad Warren
6df90d17ae Wait 5 minutes for boulder to start. (#7864) 2020-03-25 14:50:13 -07:00
alexzorin
e4a0edc7af Add a 10-second timeout to OCSP queries. (#7860)
* Add a 10-second timeout to OCSP queries.

Closes #7859

* Update CHANGELOG

* Fix test
2020-03-24 15:02:53 +01:00
m0namon
1285297b23 [Apache v2] Load apacheconfig tree and gate related tests (#7710)
* Load apacheconfig dependency, gate behind flag

* Bump apacheconfig dependency to latest version and install dev version of apache for coverage tests

* Move augeasnode_test tests to more generic parsernode_test

* Revert "Move augeasnode_test tests to more generic parsernode_test"

This reverts commit 6bb986ef786b9d68bb72776bde66e6572cf505a9.

* Mock AugeasNode into DualNode's place, and run augeasnode tests exclusively on AugeasNode

* Don't calculate coverage for skeleton functions

* clean up helper function in augeasnode_test
2020-03-23 17:05:22 -07:00
ohemorange
9e3c348dff Disable TLS session tickets in Apache (#7771)
Fixes #7350.

This PR changes the parsed modules from a `set` to a `dict`, with the filepath argument as the value. Accordingly, after calling `enable_mod` to enable `ssl_module`, modules now need to be re-parsed, so call `reset_modules`.

* Add mechanism for selecting apache config file, based on work done in #7191.

* Check OpenSSL version

* Remove os imports

* debian override still needs os

* Reformat remaining apache tests with modules dict syntax

* Clean up more apache tests

* Switch from property to method for openssl and add tests for coverage.

* Sometimes the dict location will be None in which case we should in fact return None

* warn thoroughly and consistently in openssl_version function

* update tests for new warnings

* read file as bytes, and factor out the open for testing

* normalize ssl_module_location path to account for being relative to server root

* Use byte literals in a python 2 and 3 compatible way

* string does need to be a literal

* patch builtins open

* add debug, remove space

* Add test to check if OpenSSL detection is working on different systems

* fix relative test location for cwd

* put </IfModule> on its own line in test case

* Revert test file to status in master.

* Call augeas load before reparsing modules to pick up the changes

* fix grep, tail, and mod_ssl location on centos

* strip the trailing whitespace from fedora

* just use LooseVersion in test

* call apache2ctl on debian systems

* Use sudo for apache2ctl command

* add check to make sure we're getting a version

* Add boolean so we don't warn on debian/ubuntu before trying to enable mod_ssl

* Reduce warnings while testing by setting mock _openssl_version.

* Make sure we're not throwing away any unwritten changes to the config

* test last warning case for coverage

* text changes for clarity
2020-03-23 16:49:52 -07:00
schoen
3bfaf41d3d Merge pull request #7849 from TechplexEngineer/patch-1
Fix plugin links
2020-03-16 11:51:45 -07:00
Brad Warren
06599a1e18 Cleanup more pylint issues (#7848)
This PR builds on #7657 and cleans up additional unnecessary pylint comments and some stray comments referring to pylint: disable comments that have been deleted that I didn't notice in my review of that PR.

* Remove stray pylint link.

* Cleanup more pylint comments

* Cleanup magic_typing imports

* Remove unneeded pylint: enable comments
2020-03-16 09:43:48 -07:00
schoen
30ec4cafe1 Merge pull request #7797 from g6123/nginx-utf8
Use UTF-8 encoding for nginx plugin
2020-03-13 17:07:40 -07:00
schoen
c6d35549d6 Merge branch 'master' into nginx-utf8 2020-03-13 16:28:24 -07:00
Blake Bourque
9a256ca4fe Fix plugin links 2020-03-13 15:26:15 -04:00
Adrien Ferrand
809cb516c9 Fix acme compliance to RFC 8555 (#7176)
This PR is an alternative to #7125.

Instead of disabling the strict mode on Pebble, this PR fixes the JWS payloads regarding RFC 8555 to be compliant, and allow certbot to work with Pebble v2.1.0+.

* Fix acme compliance to RFC 8555.

* Working mixin

* Activate back pebble strict mode

* Use mixin for type

* Update dependencies

* Fix also in fields_to_partial_json

* Update pebble

* Add changelog
2020-03-13 09:56:35 -07:00
Adrien Ferrand
07abe7a8d6 Reimplement tls-alpn-01 in acme (#6886)
This PR is the first part of work described in #6724.

It reintroduces the tls-alpn-01 challenge in `acme` module, that was introduced by #5894 and reverted by #6100. The reason it was removed in the past is because some tests showed that with `1.0.2` branch of OpenSSL, the self-signed certificate containing the authorization key is sent to the requester even if the ALPN protocol `acme-tls/1` was not declared as supported by the requester during the TLS handshake.

However recent discussions lead to the conclusion that this behavior was not a security issue, because first it is coherent with the behavior with servers that do not support ALPN at all, and second it cannot make a tls-alpn-01 challenge be validated in this kind of corner case.

On top of the original modifications given by #5894, I merged the code to be up-to-date with our `master`, and fixed tests to match recent evolution about not displaying the `keyAuthorization` in the deserialized JSON form of an ACME challenge.

I also move the logic to verify if ALPN is available on the current system, and so that the tls-alpn-01 challenge can be used, to a dedicated static function `is_available` in `acme.challenge.TLSALPN01`. This function is used in the related tests to skip them, and will be used in the future from Certbot plugins to trigger or not the logic related to tls-alpn-01, depending on the OpenSSL version available to Python.

* Reimplement TLS-ALPN-01 challenge and standalone TLS-ALPN server from #5894.

* Setup a class method to check if tls-alpn-01 is supported.

* Add potential missing parameter in validation for tls-alpn

* Improve comments

* Make a class private

* Handle old versions of openssl that do not terminate the handshake when they should do.

* Add changelog

* Explicitly close the TLS connection by the book.

* Remove unused exception

* Fix lint
2020-03-12 13:53:19 -07:00
osirisinferi
2fd85a4f36 Add serial number to certificates output (#7842)
Fixes #7835

I had to mock out `get_serial_from_cert` to keep a test from failing, because `cert_path` was mocked itself in `test_report_human_readable`. 

Also, I kept the same style for the serial number as the recent Let's Encrypt e-mail: lowercase hexadecimal without a `0x` prefix and without colons every 2 chars. Shouldn't be a problem to change the format if required.
2020-03-12 09:37:49 -07:00
Adrien Ferrand
44b97df4e9 Exposes environment variable to let hooks scripts know when the last challenge is handled (#7837)
Fixes #5484 

This PRs makes Certbot expose two new environment variables in the auth and cleanup hooks of the `manual` plugin:
* `CERTBOT_REMAINING_CHALLENGES` contains the number of challenges that remain after the current one (so it equals to 0 when the script is called for the last challenge)
* `CERTBOT_ALL_DOMAINS` contains a comma-separated list of all domains concerned by a challenge for the current certificate

With these variables, an hook script can know when it is run for the last time, and then trigger appropriate finalizers for all challenges that have been executed. This will be particularly useful for certificates with a lot of domains validated with DNS-01 challenges: instead of waiting on each hook execution to check that the relevant DNS TXT entry has been inserted, these waits can be avoided thanks to the latest hook verifying all domains in one run.

* Inject environment variables in manual scripts about remaining challenges

* Adapt tests

* Less variables and less lines

* Update manual.py

* Update manual_test.py

* Add documentation

* Add changelog
2020-03-12 09:29:03 -07:00
radek-sprta
78168a5248 Add CloudDNS to third-party plugins (#7840) 2020-03-11 13:27:19 -07:00
Brad Warren
69aec55ead Remove --no-site-packages outside of certbot-auto. (#7832) 2020-03-09 13:05:35 -07:00
Brad Warren
7f63141e41 Add changes to the correct changelog entry (#7833)
https://github.com/certbot/certbot/pull/7742 and https://github.com/certbot/certbot/pull/7738 landed after our 1.2.0 release, but the 1.2.0 changelog entry was modified instead of the one for master/1.3.0.

This PR moves the changelog entries to the 1.3.0 section.
2020-03-06 09:46:30 -08:00
Brad Warren
d72a1a71d2 Fix issues with Azure Pipelines (#7838)
This PR fixes two issues.

First, it fixes #7814 by removing our tests on Windows Server 2012. I also added the sentence "Certbot supports Windows Server 2016 and Windows Server 2019." to https://community.letsencrypt.org/t/beta-phase-of-certbot-for-windows/105822.

Second, it fixes the test failures which can be seen at https://dev.azure.com/certbot/certbot/_build/results?buildId=1309&view=results by no longer manually installing our own version of Python and instead using the one provided by Azure.

These small changes are in the same PR because I wanted to fix test failures ASAP and `UsePythonVersion` is not available on Windows 2012. See https://github.com/certbot/certbot/pull/7641#discussion_r358510854.

You can see tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=1311&view=results.

* stop testing on win2012

* switch to UsePythonVersion
2020-03-05 11:50:52 -08:00
ohemorange
68f4ae12be Merge pull request #7831 from certbot/candidate-1.3.0
Update files from 1.3.0 release
2020-03-03 17:34:31 -08:00
Brad Warren
144d4f2b44 Bump version to 1.4.0 2020-03-03 12:43:04 -08:00
Brad Warren
e362948d45 Add contents to certbot/CHANGELOG.md for next version 2020-03-03 12:43:03 -08:00
Brad Warren
6edb4e1a39 Release 1.3.0 2020-03-03 12:43:02 -08:00
Brad Warren
b1fb3296e9 Update changelog for 1.3.0 release 2020-03-03 12:36:36 -08:00
Brad Warren
3147026211 Check OCSP as part of determining if the certificate is due for renewal (#7829)
Fixes #1028.

Doing this now because of https://community.letsencrypt.org/t/revoking-certain-certificates-on-march-4/.

The new `ocsp_revoked_by_paths` function  is taken from https://github.com/certbot/certbot/pull/7649 with the optional argument removed for now because it is unused.

This function was added in this PR because `storage.py` uses `self.latest_common_version()` to determine which certificate should be looked at for determining renewal status at 9f8e4507ad/certbot/certbot/_internal/storage.py (L939-L947)

I think this is unnecessary and you can just look at the currently linked certificate, but I don't think we should be changing the logic that code has always had now.

* Check OCSP status as part of determining to renew

* add integration tests

* add ocsp_revoked_by_paths
2020-03-03 11:07:15 -08:00
Michael Brown
9f8e4507ad Document safe and simple usage by services without root privileges (#7821)
Certificates are public information by design: they are provided by
web servers without any prior authentication required.  In a public
key cryptographic system, only the private key is secret information.

The private key file is already created as accessible only to the root
user with mode 0600, and these file permissions are set before any key
content is written to the file.  There is no window within which an
attacker with access to the containing directory would be able to read
the private key content.

Older versions of Certbot (prior to 0.29.0) would create private key
files with mode 0644 and rely solely on the containing directory
permissions to restrict access.  We therefore cannot (yet) set the
relevant default directory permissions to 0755, since it is possible
that a user could install Certbot, obtain a certificate, then
downgrade to a pre-0.29.0 version of Certbot, then obtain another
certificate.  This chain of events would leave the second
certificate's private key file exposed.

As a compromise solution, document the fact that it is safe for the
common case of non-downgrading users to change the permissions of
/etc/letsencrypt/{live,archive} to 0755, and explain how to use chgrp
and chmod to make the private key file readable by a non-root service
user.

This provides guidance on the simplest way to solve the common problem
of making keys and certificates usable by services that run without
root privileges, with no requirement to create a custom (and hence
error-prone) executable hook.

Remove the existing custom executable hook example, so that the
documentation contains only the simplest and safest way to solve this
very common problem.

Signed-off-by: Michael Brown <mbrown@fensystems.co.uk>
2020-02-27 16:44:23 -08:00
Brad Warren
50ea608553 Don't run advanced tests on PRs. (#7820)
When I wrote https://github.com/certbot/certbot/pull/7813, I didn't understand the default behavior for pull requests if you don't specify `pr` in the yaml file. According to https://docs.microsoft.com/en-us/azure/devops/pipelines/build/triggers?view=azure-devops&tabs=yaml#pr-triggers:

> If no pr triggers appear in your YAML file, pull request builds are automatically enabled for all branches...

This is not the behavior we want. This PR fixes the problem by disabling builds on PRs.

You should be able to see this working because the advanced tests should not run on this PR but they did run on https://github.com/certbot/certbot/pull/7811.
2020-02-27 15:07:33 -08:00
Brad Warren
fa67b7ba0f Remove codecov (#7811)
After getting a +1 from everyone on the team, this PR removes the use of `codecov` from the Certbot repo because we keep having problems with it.

Two noteworthy things about this PR are:

1. I left the text at 4ea98d830b/.azure-pipelines/INSTALL.md (add-a-secret-variable-to-a-pipeline-like-codecov_token) because I think it's useful to document how to set up a secret variable in general.
2. I'm not sure what the text "Option -e makes sure we fail fast and don't submit to codecov." in `tox.cover.py` refers to but it seems incorrect since `-e` isn't accepted or used by the script so I just deleted the line.

As part of this, I said I'd open an issue to track setting up coveralls (which seems to be the only real alternative to codecov) which is at https://github.com/certbot/certbot/issues/7810.

With my change, failure output looks something like:
```
$ tox -e py27-cover
...
Name                                                         Stmts   Miss  Cover   Missing
------------------------------------------------------------------------------------------
certbot/certbot/__init__.py                                      1      0   100%
certbot/certbot/_internal/__init__.py                            0      0   100%
certbot/certbot/_internal/account.py                           191      4    98%   62-63, 206, 337
...
certbot/tests/storage_test.py                                  530      0   100%
certbot/tests/util_test.py                                     374     29    92%   211-213, 480-484, 489-499, 504-511, 545-547, 552-554
------------------------------------------------------------------------------------------
TOTAL                                                        14451    647    96%
Command '['/path/to/certbot/dir/.tox/py27-cover/bin/python', '-m', 'coverage', 'report', '--fail-under', '100', '--include', 'certbot/*', '--show-missing']' returned non-zero exit status 2
Test coverage on certbot did not meet threshold of 100%.
ERROR: InvocationError for command /Users/bmw/Development/certbot/certbot/.tox/py27-cover/bin/python tox.cover.py (exited with code 1)
_________________________________________________________________________________________________________________________________________________________ summary _________________________________________________________________________________________________________________________________________________________
ERROR:   py27-cover: commands failed
```
I printed the exception just so we're not throwing away information.

I think it's also possible we fail for a reason other than the threshold not meeting the percentage, but I've personally never seen this, `coverage report` output is not being captured so hopefully that would inform devs if something else is going on, and saying something like "Test coverage probably did not..." seems like overkill to me personally.

* remove codecov

* remove unused variable group

* remove codecov.yml

* Improve tox.cover.py failure output.
2020-02-27 14:44:39 -08:00
Brad Warren
6309ded92f Remove references to deprecated flags in Certbot. (#7509)
Related to https://github.com/certbot/certbot/pull/7482, this removes some references to deprecated options in Certbot.

The only references I didn't remove were:

* In `certbot/tests/testdata/sample-renewal*` which contains a lot of old values and I think there's even some value in keeping them so we know if we make a change that suddenly causes old renewal configuration files to error.
* In the Apache and Nginx plugins and I created https://github.com/certbot/certbot/issues/7508 to resolve that issue.
2020-02-27 14:43:28 -08:00
m0namon
5a4f158c55 Merge pull request #7541 from certbot/no-client-plugins
Fix docstring
2020-02-27 14:36:59 -08:00
Brad Warren
a2be8e1956 Fix tests on macOS Catalina (#7794)
This PR fixes the failures that can be seen at https://dev.azure.com/certbot/certbot/_build/results?buildId=1184&view=results.

You can see this code running on macOS Catalina at https://dev.azure.com/certbot/certbot/_build/results?buildId=1192&view=results.
2020-02-27 10:50:20 -08:00
Brad Warren
2f737ee292 Change how _USE_DISTRO is set for mypy (#7804)
If you run `mypy --platform darwin certbot/certbot/util.py` you'll get:
```
certbot/certbot/util.py:303: error: Name 'distro' is not defined
certbot/certbot/util.py:319: error: Name 'distro' is not defined
certbot/certbot/util.py:369: error: Name 'distro' is not defined
```
This is because mypy's logic for handling platform specific code is pretty simple and can't figure out what we're doing with `_USE_DISTRO` here. See https://mypy.readthedocs.io/en/stable/common_issues.html#python-version-and-system-platform-checks for more info.

Setting `_USE_DISTRO` to the result of `sys.platform.startswith('linux')` solves the problem without changing the overall behavior of our code here though.

This fixes part of https://github.com/certbot/certbot/issues/7803, but there's more work to be done on Windows.
2020-02-27 10:49:50 -08:00
Brad Warren
8c75a9de9f Remove unused notify code. (#7805)
This code is unused and hasn't been modified since 2015 except for various times our files have been renamed. Let's remove it.
2020-02-27 10:47:56 -08:00
Brad Warren
24aa1e9127 update letstest reqs (#7809)
I don't fully understand why, but since I updated my macbook to macOS Catalina, the test script currently fails to run for me with the versions of our dependencies we have pinned. Updating the dependencies solves the problem though and you can see Travis also successfully running tests with these new dependencies at https://travis-ci.com/certbot/certbot/builds/150573696.
2020-02-27 10:47:43 -08:00
Brad Warren
f4c0a9fd63 Split advanced pipeline (#7813)
I want to do what I did in https://github.com/certbot/certbot/pull/7733 to our Azure Pipelines setup, but unfortunately this isn't currently possible. The only filters available for service hooks for the "build completed" trigger are the pipeline and build status. See 
![Screen Shot 2020-02-26 at 3 04 56 PM](https://user-images.githubusercontent.com/6504915/75396464-64ad0780-58a9-11ea-97a1-3454a9754675.png)

To accomplish this, I propose splitting the "advanced" pipeline into two cases. One is for builds on protected branches where we want to be notified if they fail while the other is just used to manually run tests on certain branches.
2020-02-27 10:43:41 -08:00
m0namon
f169c37153 Merge pull request #7742 from osirisinferi/force-non-restrictive-umask
Force non restrictive umask when creating challenge directory in Apache plugin
2020-02-26 17:09:20 -08:00
cumul0529
a489079208 Update parser test to better assert logging output 2020-02-25 13:26:36 +09:00
cumul0529
ddf68aea80 Update comment in testdata file 2020-02-25 13:21:10 +09:00
cumul0529
2ae090529e Fixed typo & some trivial documentation change 2020-02-25 13:18:03 +09:00
cumul0529
5b29e4616c Add simple comments 2020-02-24 05:30:54 +09:00
cumul0529
32904d8c9e Add TestCase.assertLogs() backport for Python 2.7 2020-02-24 05:13:34 +09:00
cumul0529
d68f37ae88 Add this change to CHANGELOG.md 2020-02-24 02:56:43 +09:00
cumul0529
b3071aab29 Add my name to AUTHORS.md
:)
2020-02-24 02:52:41 +09:00
cumul0529
2aac24c982 Trivial code clean-up 2020-02-24 02:49:08 +09:00
cumul0529
20df5507ae Add logging test for _parse_files() 2020-02-24 02:48:55 +09:00
cumul0529
36311a276b Add test case for _parse_ssl_options() 2020-02-24 02:46:27 +09:00
cumul0529
22685ef86f Remove unicode_support/ path in test case 2020-02-24 02:23:46 +09:00
cumul0529
c3cfd412c9 Relpace deprecated logger.warn() with logger.warning() 2020-02-24 01:47:12 +09:00
Seth Schoen
0b21e716ca Fix lint problems with long lines 2020-02-24 01:45:23 +09:00
cumul
8b90b55518 Added test for valid/invalid unicode characters 2020-02-24 01:35:00 +09:00
cumul
247d9cd887 Use io module instead of codecs
See https://mail.python.org/pipermail/python-list/2015-March/687124.html
2020-02-24 01:29:37 +09:00
cumul
d6ef34a03e Use UTF-8 encoding for nginx plugin 2020-02-24 01:25:16 +09:00
osirisinferi
9819443440 Add test 2020-02-22 15:22:27 +01:00
Brad Warren
df584a3b90 Remove _internal from docstring. 2020-02-12 13:12:03 -08:00
OsirisInferi
d3a4b8fd8c Missing import 2020-02-05 22:27:12 +01:00
OsirisInferi
f3ed133744 Wrap makedirs() within exception handelrs 2020-02-05 22:17:29 +01:00
OsirisInferi
601a114d1b Update changelog 2020-02-04 19:47:27 +01:00
OsirisInferi
86926dff92 Use unrestrictive umask for challenge directory 2020-02-04 19:27:27 +01:00
Brad Warren
097c76f512 Merge branch 'master' into no-client-plugins 2020-01-16 11:56:41 -08:00
Brad Warren
2bc64183a8 fix docstring 2019-11-11 17:11:47 -08:00
248 changed files with 4324 additions and 2044 deletions

View File

@@ -0,0 +1,13 @@
# Advanced pipeline for running our full test suite on demand.
trigger:
# When changing these triggers, please ensure the documentation under
# "Running tests in CI" is still correct.
- azure-test-*
- test-*
pr: none
jobs:
# Any addition here should be reflected in the advanced and release pipelines.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml

View File

@@ -1,12 +1,7 @@
# Advanced pipeline for isolated checks and release purpose
# Advanced pipeline for running our full test suite on protected branches.
trigger:
# When changing these triggers, please ensure the documentation under
# "Running tests in CI" is still correct.
- azure-test-*
- test-*
- '*.x'
pr:
- test-*
pr: none
# This pipeline is also nightly run on master
schedules:
- cron: "0 4 * * *"
@@ -17,7 +12,7 @@ schedules:
always: true
jobs:
# Any addition here should be reflected in the release pipeline.
# Any addition here should be reflected in the advanced-test and release pipelines.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml

View File

@@ -1,10 +1,6 @@
trigger:
# apache-parser-v2 is a temporary branch for doing work related to
# rewriting the parser in the Apache plugin.
- apache-parser-v2
- master
pr:
- apache-parser-v2
- master
- '*.x'

View File

@@ -6,7 +6,7 @@ trigger:
pr: none
jobs:
# Any addition here should be reflected in the advanced pipeline.
# Any addition here should be reflected in the advanced and advanced-test pipelines.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml

View File

@@ -28,15 +28,20 @@ jobs:
imageName: windows-2019
win2016:
imageName: vs2017-win2016
win2012r2:
imageName: vs2015-win2012r2
pool:
vmImage: $(imageName)
steps:
- powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe
displayName: Get Python
- script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3
displayName: Install Python
- powershell: |
$currentVersion = $PSVersionTable.PSVersion
if ($currentVersion.Major -ne 5) {
throw "Powershell version is not 5.x"
}
condition: eq(variables['imageName'], 'vs2017-win2016')
displayName: Check Powershell 5.x is used in vs2017-win2016
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:
artifact: windows-installer

View File

@@ -25,8 +25,6 @@ jobs:
PYTEST_ADDOPTS: --numprocesses 4
pool:
vmImage: $(IMAGE_NAME)
variables:
- group: certbot-common
steps:
- bash: brew install augeas
condition: startswith(variables['IMAGE_NAME'], 'macOS')
@@ -39,14 +37,3 @@ jobs:
displayName: Install dependencies
- script: python -m tox
displayName: Run tox
# We do not require codecov report upload to succeed. So to avoid to break the pipeline if
# something goes wrong, each command is suffixed with a command that hides any non zero exit
# codes and echoes an informative message instead.
- bash: |
curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash"
chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash"
./codecov-bash -F windows || echo "Codecov did not collect coverage reports"
condition: in(variables['TOXENV'], 'py37-cover', 'integration-certbot')
env:
CODECOV_TOKEN: $(codecov_token)
displayName: Publish coverage

View File

@@ -1,18 +0,0 @@
coverage:
status:
project:
default: off
linux:
flags: linux
# Fixed target instead of auto set by #7173, can
# be removed when flags in Codecov are added back.
target: 97.4
threshold: 0.1
base: auto
windows:
flags: windows
# Fixed target instead of auto set by #7173, can
# be removed when flags in Codecov are added back.
target: 97.4
threshold: 0.1
base: auto

10
.gitignore vendored
View File

@@ -35,6 +35,7 @@ tags
tests/letstest/letest-*/
tests/letstest/*.pem
tests/letstest/venv/
tests/letstest/venv3/
.venv
@@ -50,3 +51,12 @@ tests/letstest/venv/
.certbot_test_workspace
**/assets/pebble*
**/assets/challtestsrv*
# snap files
.snapcraft
parts
prime
stage
*.snap
snap-constraints.txt
qemu-*

View File

@@ -11,20 +11,23 @@ before_script:
# Use Travis retry feature for farm tests since they are flaky
- 'if [[ "$TOXENV" == "travis-test-farm"* ]]; then export TRAVIS_RETRY=travis_retry; fi'
- export TOX_TESTENV_PASSENV=TRAVIS
- 'if [[ "$SNAP" == true ]]; then snap/local/build_and_install.sh $TARGET_ARCH; fi'
# Only build pushes to the master branch, PRs, and branches beginning with
# `test-`, `travis-test-`, or of the form `digit(s).digit(s).x`. This reduces
# the number of simultaneous Travis runs, which speeds turnaround time on
# review since there is a cap of on the number of simultaneous runs.
# `test-`, `travis-test-`, or of the form `digit(s).digit(s).x` or
# `vdigit(s).digit(s).digit(s)`. As documented at
# https://docs.travis-ci.com/user/customizing-the-build/#safelisting-or-blocklisting-branches,
# this includes tags so pushing tags of the form `vdigit(s).digit(s).digit(s)`
# will also trigger tests. This reduces the number of simultaneous Travis runs,
# which speeds turnaround time on review since there is a cap of on the number
# of simultaneous runs.
branches:
# When changing these branches, please ensure the documentation under
# "Running tests in CI" is still correct.
only:
# apache-parser-v2 is a temporary branch for doing work related to
# rewriting the parser in the Apache plugin.
- apache-parser-v2
- master
- /^\d+\.\d+\.x$/
- /^\d+\.\d+\.x$/ # this matches our point release branches
- /^v\d+\.\d+\.\d+$/ # this matches our release tags
- /^(travis-)?test-.*$/
# Jobs for the main test suite are always executed (including on PRs) except for pushes on master.
@@ -32,20 +35,65 @@ 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 to
# non-development branches. See the explanation for apache-parser-v2 above.
# non-development branches.
extended-test-suite: &extended-test-suite
if: type = cron OR (type = push AND branch NOT IN (apache-parser-v2, master))
if: type = cron OR (type = push AND branch != master)
# Common configuration for all snap tasks
snap-config: &snap-config
dist: bionic
addons:
apt:
packages:
- nginx-light
git:
# By default, Travis clones the repo to a depth of 50 commits which can
# break the ability to use `git describe` to set the version of the
# snap. This setting removes the --depth flag from git commands solving
# this problem. See
# https://docs.travis-ci.com/user/customizing-the-build#git-clone-depth
# for more info.
depth: false
deploy:
# This section relies on credentials stored in a SNAP_TOKEN environment
# variable in Travis. See
# https://docs.travis-ci.com/user/deployment/snaps/ for more info.
# This credential has a maximum lifetime of 1 year and the current
# credential will expire on 4/22/2021. The value of SNAP_TOKEN will
# need to be updated to use a new credential before then to prevent
# automated deploys from breaking. Remembering to do this is also
# tracked by https://github.com/certbot/certbot/issues/7931.
'on':
# Deploy on release tags or nightly runs from any branch. We only try
# to deploy from the certbot/certbot repo to prevent errors if forks
# of this repo try to run tests.
all_branches: true
condition: -n $TRAVIS_TAG || $TRAVIS_EVENT_TYPE = cron
repo: certbot/certbot
provider: snap
snap: certbot_*.snap
channel: edge
# skip_cleanup is needed to prevent Travis from deleting the snaps we
# just built and tested. See
# https://docs.travis-ci.com/user/deployment#uploading-files-and-skip_cleanup.
skip_cleanup: true
matrix:
include:
# Main test suite
- python: "2.7"
- stage: "Test"
python: "2.7"
env: ACME_SERVER=pebble TOXENV=integration
<<: *not-on-master
# As documented at
# https://docs.travis-ci.com/user/build-stages/#how-to-define-build-stages,
# the previous stage will be automatically applied to all subsequent jobs
# until a new stage is defined.
# This job is always executed, including on master
- python: "2.7"
env: TOXENV=py27-cover FYI="py27 tests + code coverage"
- python: "3.8"
env: TOXENV=py38-cover FYI="py38 tests + code coverage"
- python: "3.7"
env: TOXENV=lint
@@ -60,16 +108,15 @@ matrix:
dist: trusty
env: TOXENV='py27-{acme,apache,apache-v2,certbot,dns,nginx}-oldest'
<<: *not-on-master
- python: "2.7"
env: TOXENV=py27
<<: *not-on-master
- python: "3.5"
env: TOXENV=py35
<<: *not-on-master
- python: "3.8"
env: TOXENV=py38
<<: *not-on-master
- sudo: required
env: TOXENV=apache_compat
services: docker
before_install:
addons:
<<: *not-on-master
- sudo: required
@@ -87,27 +134,26 @@ matrix:
- sudo: required
env: TOXENV=nginx_compat
services: docker
before_install:
addons:
<<: *extended-test-suite
- python: "2.7"
- python: "3.7"
env:
- TOXENV=travis-test-farm-apache2
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
<<: *extended-test-suite
- python: "2.7"
- python: "3.7"
env:
- TOXENV=travis-test-farm-leauto-upgrades
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
git:
depth: false # This is needed to have the history to checkout old versions of certbot-auto.
<<: *extended-test-suite
- python: "2.7"
- python: "3.7"
env:
- TOXENV=travis-test-farm-certonly-standalone
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
<<: *extended-test-suite
- python: "2.7"
- python: "3.7"
env:
- TOXENV=travis-test-farm-sdists
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
@@ -223,6 +269,16 @@ matrix:
packages: # don't install nginx and apache
- libaugeas0
<<: *extended-test-suite
- stage: "Snap"
env: SNAP=true TOXENV=integration-external,apacheconftest-external-with-pebble TARGET_ARCH=amd64
<<: *snap-config
<<: *extended-test-suite
- env: SNAP=true TARGET_ARCH=arm64
<<: *snap-config
<<: *extended-test-suite
- env: SNAP=true TARGET_ARCH=armhf
<<: *snap-config
<<: *extended-test-suite
# container-based infrastructure
sudo: false
@@ -247,14 +303,12 @@ addons:
# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also
# set, pip updates dependencies it thinks are already satisfied to avoid some
# problems with its lack of real dependency resolution.
install: 'tools/pip_install.py -I codecov tox virtualenv'
install: 'tools/pip_install.py -I tox virtualenv'
# Most of the time TRAVIS_RETRY is an empty string, and has no effect on the
# script command. It is set only to `travis_retry` during farm tests, in
# order to trigger the Travis retry feature, and compensate the inherent
# flakiness of these specific tests.
script: '$TRAVIS_RETRY tox'
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
script: 'if [[ ! -z "$TOXENV" ]]; then $TRAVIS_RETRY tox; fi'
notifications:
email: false
@@ -266,6 +320,5 @@ notifications:
# https://github.com/travis-ci/travis.rb and running
# `travis encrypt "chat.freenode.net#certbot-devel"`.
- secure: "EWW66E2+KVPZyIPR8ViENZwfcup4Gx3/dlimmAZE0WuLwxDCshBBOd3O8Rf6pBokEoZlXM5eDT6XdyJj8n0DLslgjO62pExdunXpbcMwdY7l1ELxX2/UbnDTE6UnPYa09qVBHNG7156Z6yE0x2lH4M9Ykvp0G0cubjPQHylAwo0="
on_cancel: never
on_success: never
on_failure: always

View File

@@ -21,6 +21,7 @@ Authors
* [Andrzej Górski](https://github.com/andrzej3393)
* [Anselm Levskaya](https://github.com/levskaya)
* [Antoine Jacoutot](https://github.com/ajacoutot)
* [April King](https://github.com/april)
* [asaph](https://github.com/asaph)
* [Axel Beckert](https://github.com/xtaran)
* [Bas](https://github.com/Mechazawa)
@@ -35,7 +36,8 @@ Authors
* [Blake Griffith](https://github.com/cowlicks)
* [Brad Warren](https://github.com/bmw)
* [Brandon Kraft](https://github.com/kraftbj)
* [Brandon Kreisel](https://github.com/kraftbj)
* [Brandon Kreisel](https://github.com/BKreisel)
* [Brian Heim](https://github.com/brianlheim)
* [Cameron Steel](https://github.com/Tugzrida)
* [Ceesjan Luiten](https://github.com/quinox)
* [Chad Whitacre](https://github.com/whit537)
@@ -103,6 +105,7 @@ Authors
* [Henry Chen](https://github.com/henrychen95)
* [Hugo van Kemenade](https://github.com/hugovk)
* [Ingolf Becker](https://github.com/watercrossing)
* [Ivan Nejgebauer](https://github.com/inejge)
* [Jaap Eldering](https://github.com/eldering)
* [Jacob Hoffman-Andrews](https://github.com/jsha)
* [Jacob Sachs](https://github.com/jsachs)
@@ -200,6 +203,7 @@ Authors
* [Pierre Jaury](https://github.com/kaiyou)
* [Piotr Kasprzyk](https://github.com/kwadrat)
* [Prayag Verma](https://github.com/pra85)
* [Rasesh Patel](https://github.com/raspat1)
* [Reinaldo de Souza Jr](https://github.com/juniorz)
* [Remi Rampin](https://github.com/remram44)
* [Rémy HUBSCHER](https://github.com/Natim)
@@ -235,6 +239,7 @@ Authors
* [Stefan Weil](https://github.com/stweil)
* [Steve Desmond](https://github.com/stevedesmond-ca)
* [sydneyli](https://github.com/sydneyli)
* [taixx046](https://github.com/taixx046)
* [Tan Jay Jun](https://github.com/jayjun)
* [Tapple Gao](https://github.com/tapple)
* [Telepenin Nikolay](https://github.com/telepenin)
@@ -266,5 +271,6 @@ Authors
* [Yomna](https://github.com/ynasser)
* [Yoni Jah](https://github.com/yonjah)
* [YourDaddyIsHere](https://github.com/YourDaddyIsHere)
* [Yuseong Cho](https://github.com/g6123)
* [Zach Shepherd](https://github.com/zjs)
* [陈三](https://github.com/chenxsan)

View File

@@ -1,15 +1,22 @@
"""ACME Identifier Validation Challenges."""
import abc
import codecs
import functools
import hashlib
import logging
import socket
from cryptography.hazmat.primitives import hashes # type: ignore
import josepy as jose
import requests
import six
from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
from OpenSSL import crypto
from acme import crypto_util
from acme import errors
from acme import fields
from acme.mixins import ResourceMixin, TypeMixin
logger = logging.getLogger(__name__)
@@ -28,7 +35,7 @@ class Challenge(jose.TypedJSONObjectWithFields):
return UnrecognizedChallenge.from_json(jobj)
class ChallengeResponse(jose.TypedJSONObjectWithFields):
class ChallengeResponse(ResourceMixin, TypeMixin, jose.TypedJSONObjectWithFields):
# _fields_to_partial_json
"""ACME challenge response."""
TYPES = {} # type: dict
@@ -362,29 +369,163 @@ class HTTP01(KeyAuthorizationChallenge):
@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.
"""
"""ACME tls-alpn-01 challenge response."""
typ = "tls-alpn-01"
PORT = 443
"""Verification port as defined by the protocol.
@Challenge.register
You can override it (e.g. for testing) by passing ``port`` to
`simple_verify`.
"""
ID_PE_ACME_IDENTIFIER_V1 = b"1.3.6.1.5.5.7.1.30.1"
ACME_TLS_1_PROTOCOL = "acme-tls/1"
@property
def h(self):
"""Hash value stored in challenge certificate"""
return hashlib.sha256(self.key_authorization.encode('utf-8')).digest()
def gen_cert(self, domain, key=None, bits=2048):
"""Generate tls-alpn-01 certificate.
:param unicode domain: Domain verified by the challenge.
:param OpenSSL.crypto.PKey key: Optional private key used in
certificate generation. If not provided (``None``), then
fresh key will be generated.
:param int bits: Number of bits for newly generated key.
:rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
"""
if key is None:
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, bits)
der_value = b"DER:" + codecs.encode(self.h, 'hex')
acme_extension = crypto.X509Extension(self.ID_PE_ACME_IDENTIFIER_V1,
critical=True, value=der_value)
return crypto_util.gen_ss_cert(key, [domain], force_san=True,
extensions=[acme_extension]), key
def probe_cert(self, domain, host=None, port=None):
"""Probe tls-alpn-01 challenge certificate.
:param unicode domain: domain being validated, required.
:param string host: IP address used to probe the certificate.
:param int port: Port used to probe the certificate.
"""
if host is None:
host = socket.gethostbyname(domain)
logger.debug('%s resolved to %s', domain, host)
if port is None:
port = self.PORT
return crypto_util.probe_sni(host=host, port=port, name=domain,
alpn_protocols=[self.ACME_TLS_1_PROTOCOL])
def verify_cert(self, domain, cert):
"""Verify tls-alpn-01 challenge certificate.
:param unicode domain: Domain name being validated.
:param OpensSSL.crypto.X509 cert: Challenge certificate.
:returns: Whether the certificate was successfully verified.
:rtype: bool
"""
# pylint: disable=protected-access
names = crypto_util._pyopenssl_cert_or_req_all_names(cert)
logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), names)
if len(names) != 1 or names[0].lower() != domain.lower():
return False
for i in range(cert.get_extension_count()):
ext = cert.get_extension(i)
# FIXME: assume this is the ACME extension. Currently there is no
# way to get full OID of an unknown extension from pyopenssl.
if ext.get_short_name() == b'UNDEF':
data = ext.get_data()
return data == self.h
return False
# pylint: disable=too-many-arguments
def simple_verify(self, chall, domain, account_public_key,
cert=None, host=None, port=None):
"""Simple verify.
Verify ``validation`` using ``account_public_key``, optionally
probe tls-alpn-01 certificate and check using `verify_cert`.
:param .challenges.TLSALPN01 chall: Corresponding challenge.
:param str domain: Domain name being validated.
:param JWK account_public_key:
:param OpenSSL.crypto.X509 cert: Optional certificate. If not
provided (``None``) certificate will be retrieved using
`probe_cert`.
:param string host: IP address used to probe the certificate.
:param int port: Port used to probe the certificate.
:returns: ``True`` if and only if client's control of the domain has been verified.
:rtype: bool
"""
if not self.verify(chall, account_public_key):
logger.debug("Verification of key authorization in response failed")
return False
if cert is None:
try:
cert = self.probe_cert(domain=domain, host=host, port=port)
except errors.Error as error:
logger.debug(str(error), exc_info=True)
return False
return self.verify_cert(domain, cert)
@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"
"""ACME tls-alpn-01 challenge."""
response_cls = TLSALPN01Response
typ = response_cls.typ
def validation(self, account_key, **kwargs):
"""Generate validation for the challenge."""
raise NotImplementedError()
"""Generate validation.
:param JWK account_key:
:param unicode domain: Domain verified by the challenge.
:param OpenSSL.crypto.PKey cert_key: Optional private key used
in certificate generation. If not provided (``None``), then
fresh key will be generated.
:rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey`
"""
return self.response(account_key).gen_cert(
key=kwargs.get('cert_key'),
domain=kwargs.get('domain'))
@staticmethod
def is_supported():
"""
Check if TLS-ALPN-01 challenge is supported on this machine.
This implies that a recent version of OpenSSL is installed (>= 1.0.2),
or a recent cryptography version shipped with the OpenSSL library is installed.
:returns: ``True`` if TLS-ALPN-01 is supported on this machine, ``False`` otherwise.
:rtype: bool
"""
return (hasattr(SSL.Connection, "set_alpn_protos")
and hasattr(SSL.Context, "set_alpn_select_callback"))
@Challenge.register

View File

@@ -25,6 +25,7 @@ from acme.magic_typing import Dict
from acme.magic_typing import List
from acme.magic_typing import Set
from acme.magic_typing import Text
from acme.mixins import VersionedLEACMEMixin
logger = logging.getLogger(__name__)
@@ -987,6 +988,8 @@ class ClientNetwork(object):
:rtype: `josepy.JWS`
"""
if isinstance(obj, VersionedLEACMEMixin):
obj.le_acme_version = acme_version
jobj = obj.json_dumps(indent=2).encode() if obj else b''
logger.debug('JWS payload:\n%s', jobj)
kwargs = {
@@ -1120,8 +1123,8 @@ class ClientNetwork(object):
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)
for k, v in response.headers.items()]),
"\n".join("{0}: {1}".format(k, v)
for k, v in response.headers.items()),
debug_content)
return response

View File

@@ -27,19 +27,41 @@ logger = logging.getLogger(__name__)
_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
class SSLSocket(object):
class _DefaultCertSelection(object):
def __init__(self, certs):
self.certs = certs
def __call__(self, connection):
server_name = connection.get_servername()
return self.certs.get(server_name, None)
class SSLSocket(object): # pylint: disable=too-few-public-methods
"""SSL wrapper for sockets.
:ivar socket sock: Original wrapped socket.
:ivar dict certs: Mapping from domain names (`bytes`) to
`OpenSSL.crypto.X509`.
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
:ivar alpn_selection: Hook to select negotiated ALPN protocol for
connection.
:ivar cert_selection: Hook to select certificate for connection. If given,
`certs` parameter would be ignored, and therefore must be empty.
"""
def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD):
def __init__(self, sock, certs=None,
method=_DEFAULT_SSL_METHOD, alpn_selection=None,
cert_selection=None):
self.sock = sock
self.certs = certs
self.alpn_selection = alpn_selection
self.method = method
if not cert_selection and not certs:
raise ValueError("Neither cert_selection or certs specified.")
if cert_selection and certs:
raise ValueError("Both cert_selection and certs specified.")
if cert_selection is None:
cert_selection = _DefaultCertSelection(certs)
self.cert_selection = cert_selection
def __getattr__(self, name):
return getattr(self.sock, name)
@@ -56,18 +78,19 @@ class SSLSocket(object):
:type connection: :class:`OpenSSL.Connection`
"""
server_name = connection.get_servername()
try:
key, cert = self.certs[server_name]
except KeyError:
logger.debug("Server name (%s) not recognized, dropping SSL",
server_name)
pair = self.cert_selection(connection)
if pair is None:
logger.debug("Certificate selection for server name %s failed, dropping SSL",
connection.get_servername())
return
key, cert = pair
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)
if self.alpn_selection is not None:
new_context.set_alpn_select_callback(self.alpn_selection)
connection.set_context(new_context)
class FakeConnection(object):
@@ -92,6 +115,8 @@ class SSLSocket(object):
context.set_options(SSL.OP_NO_SSLv2)
context.set_options(SSL.OP_NO_SSLv3)
context.set_tlsext_servername_callback(self._pick_certificate_cb)
if self.alpn_selection is not None:
context.set_alpn_select_callback(self.alpn_selection)
ssl_sock = self.FakeConnection(SSL.Connection(context, sock))
ssl_sock.set_accept_state()
@@ -107,8 +132,9 @@ class SSLSocket(object):
return ssl_sock, addr
def probe_sni(name, host, port=443, timeout=300,
method=_DEFAULT_SSL_METHOD, source_address=('', 0)):
def probe_sni(name, host, port=443, timeout=300, # pylint: disable=too-many-arguments
method=_DEFAULT_SSL_METHOD, source_address=('', 0),
alpn_protocols=None):
"""Probe SNI server for SSL certificate.
:param bytes name: Byte string to send as the server name in the
@@ -120,6 +146,8 @@ def probe_sni(name, host, port=443, timeout=300,
:param tuple source_address: Enables multi-path probing (selection
of source interface). See `socket.creation_connection` for more
info. Available only in Python 2.7+.
:param alpn_protocols: Protocols to request using ALPN.
:type alpn_protocols: `list` of `bytes`
:raises acme.errors.Error: In case of any problems.
@@ -149,6 +177,8 @@ def probe_sni(name, host, port=443, timeout=300,
client_ssl = SSL.Connection(context, client)
client_ssl.set_connect_state()
client_ssl.set_tlsext_host_name(name) # pyOpenSSL>=0.13
if alpn_protocols is not None:
client_ssl.set_alpn_protos(alpn_protocols)
try:
client_ssl.do_handshake()
client_ssl.shutdown()
@@ -239,12 +269,14 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
def gen_ss_cert(key, domains, not_before=None,
validity=(7 * 24 * 60 * 60), force_san=True):
validity=(7 * 24 * 60 * 60), force_san=True, extensions=None):
"""Generate new self-signed certificate.
:type domains: `list` of `unicode`
:param OpenSSL.crypto.PKey key:
:param bool force_san:
:param extensions: List of additional extensions to include in the cert.
:type extensions: `list` of `OpenSSL.crypto.X509Extension`
If more than one domain is provided, all of the domains are put into
``subjectAltName`` X.509 extension and first domain is set as the
@@ -257,10 +289,13 @@ def gen_ss_cert(key, domains, not_before=None,
cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16))
cert.set_version(2)
extensions = [
if extensions is None:
extensions = []
extensions.append(
crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
]
)
cert.get_subject().CN = domains[0]
# TODO: what to put into cert.get_subject()?

View File

@@ -11,6 +11,5 @@ try:
# mypy doesn't respect modifying sys.modules
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
from typing import Collection, IO # type: ignore
# pylint: enable=unused-import
except ImportError:
sys.modules[__name__] = TypingClass()

View File

@@ -9,6 +9,7 @@ from acme import errors
from acme import fields
from acme import jws
from acme import util
from acme.mixins import ResourceMixin
try:
from collections.abc import Hashable
@@ -356,13 +357,13 @@ class Registration(ResourceBody):
@Directory.register
class NewRegistration(Registration):
class NewRegistration(ResourceMixin, Registration):
"""New registration."""
resource_type = 'new-reg'
resource = fields.Resource(resource_type)
class UpdateRegistration(Registration):
class UpdateRegistration(ResourceMixin, Registration):
"""Update registration."""
resource_type = 'reg'
resource = fields.Resource(resource_type)
@@ -498,13 +499,13 @@ class Authorization(ResourceBody):
@Directory.register
class NewAuthorization(Authorization):
class NewAuthorization(ResourceMixin, Authorization):
"""New authorization."""
resource_type = 'new-authz'
resource = fields.Resource(resource_type)
class UpdateAuthorization(Authorization):
class UpdateAuthorization(ResourceMixin, Authorization):
"""Update authorization."""
resource_type = 'authz'
resource = fields.Resource(resource_type)
@@ -522,7 +523,7 @@ class AuthorizationResource(ResourceWithURI):
@Directory.register
class CertificateRequest(jose.JSONObjectWithFields):
class CertificateRequest(ResourceMixin, jose.JSONObjectWithFields):
"""ACME new-cert request.
:ivar josepy.util.ComparableX509 csr:
@@ -548,7 +549,7 @@ class CertificateResource(ResourceWithURI):
@Directory.register
class Revocation(jose.JSONObjectWithFields):
class Revocation(ResourceMixin, jose.JSONObjectWithFields):
"""Revocation message.
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in

65
acme/acme/mixins.py Normal file
View File

@@ -0,0 +1,65 @@
"""Useful mixins for Challenge and Resource objects"""
class VersionedLEACMEMixin(object):
"""This mixin stores the version of Let's Encrypt's endpoint being used."""
@property
def le_acme_version(self):
"""Define the version of ACME protocol to use"""
return getattr(self, '_le_acme_version', 1)
@le_acme_version.setter
def le_acme_version(self, version):
# We need to use object.__setattr__ to not depend on the specific implementation of
# __setattr__ in current class (eg. jose.TypedJSONObjectWithFields raises AttributeError
# for any attempt to set an attribute to make objects immutable).
object.__setattr__(self, '_le_acme_version', version)
def __setattr__(self, key, value):
if key == 'le_acme_version':
# Required for @property to operate properly. See comment above.
object.__setattr__(self, key, value)
else:
super(VersionedLEACMEMixin, self).__setattr__(key, value) # pragma: no cover
class ResourceMixin(VersionedLEACMEMixin):
"""
This mixin generates a RFC8555 compliant JWS payload
by removing the `resource` field if needed (eg. ACME v2 protocol).
"""
def to_partial_json(self):
"""See josepy.JSONDeserializable.to_partial_json()"""
return _safe_jobj_compliance(super(ResourceMixin, self),
'to_partial_json', 'resource')
def fields_to_partial_json(self):
"""See josepy.JSONObjectWithFields.fields_to_partial_json()"""
return _safe_jobj_compliance(super(ResourceMixin, self),
'fields_to_partial_json', 'resource')
class TypeMixin(VersionedLEACMEMixin):
"""
This mixin allows generation of a RFC8555 compliant JWS payload
by removing the `type` field if needed (eg. ACME v2 protocol).
"""
def to_partial_json(self):
"""See josepy.JSONDeserializable.to_partial_json()"""
return _safe_jobj_compliance(super(TypeMixin, self),
'to_partial_json', 'type')
def fields_to_partial_json(self):
"""See josepy.JSONObjectWithFields.fields_to_partial_json()"""
return _safe_jobj_compliance(super(TypeMixin, self),
'fields_to_partial_json', 'type')
def _safe_jobj_compliance(instance, jobj_method, uncompliant_field):
if hasattr(instance, jobj_method):
jobj = getattr(instance, jobj_method)()
if instance.le_acme_version == 2:
jobj.pop(uncompliant_field, None)
return jobj
raise AttributeError('Method {0}() is not implemented.'.format(jobj_method)) # pragma: no cover

View File

@@ -33,7 +33,14 @@ class TLSServer(socketserver.TCPServer):
def _wrap_sock(self):
self.socket = crypto_util.SSLSocket(
self.socket, certs=self.certs, method=self.method)
self.socket, cert_selection=self._cert_selection,
alpn_selection=getattr(self, '_alpn_selection', None),
method=self.method)
def _cert_selection(self, connection): # pragma: no cover
"""Callback selecting certificate for connection."""
server_name = connection.get_servername()
return self.certs.get(server_name, None)
def server_bind(self):
self._wrap_sock()
@@ -120,6 +127,40 @@ class BaseDualNetworkedServers(object):
self.threads = []
class TLSALPN01Server(TLSServer, ACMEServerMixin):
"""TLSALPN01 Server."""
ACME_TLS_1_PROTOCOL = b"acme-tls/1"
def __init__(self, server_address, certs, challenge_certs, ipv6=False):
TLSServer.__init__(
self, server_address, _BaseRequestHandlerWithLogging, certs=certs,
ipv6=ipv6)
self.challenge_certs = challenge_certs
def _cert_selection(self, connection):
# TODO: We would like to serve challenge cert only if asked for it via
# ALPN. To do this, we need to retrieve the list of protos from client
# hello, but this is currently impossible with openssl [0], and ALPN
# negotiation is done after cert selection.
# Therefore, currently we always return challenge cert, and terminate
# handshake in alpn_selection() if ALPN protos are not what we expect.
# [0] https://github.com/openssl/openssl/issues/4952
server_name = connection.get_servername()
logger.debug("Serving challenge cert for server name %s", server_name)
return self.challenge_certs.get(server_name, None)
def _alpn_selection(self, _connection, alpn_protos):
"""Callback to select alpn protocol."""
if len(alpn_protos) == 1 and alpn_protos[0] == self.ACME_TLS_1_PROTOCOL:
logger.debug("Agreed on %s ALPN", self.ACME_TLS_1_PROTOCOL)
return self.ACME_TLS_1_PROTOCOL
logger.debug("Cannot agree on ALPN proto. Got: %s", str(alpn_protos))
# Explicitly close the connection now, by returning an empty string.
# See https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_alpn_select_callback # pylint: disable=line-too-long
return b""
class HTTPServer(BaseHTTPServer.HTTPServer):
"""Generic HTTP Server."""
@@ -135,10 +176,10 @@ class HTTPServer(BaseHTTPServer.HTTPServer):
class HTTP01Server(HTTPServer, ACMEServerMixin):
"""HTTP01 Server."""
def __init__(self, server_address, resources, ipv6=False):
def __init__(self, server_address, resources, ipv6=False, timeout=30):
HTTPServer.__init__(
self, server_address, HTTP01RequestHandler.partial_init(
simple_http_resources=resources), ipv6=ipv6)
simple_http_resources=resources, timeout=timeout), ipv6=ipv6)
class HTTP01DualNetworkedServers(BaseDualNetworkedServers):
@@ -163,6 +204,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.simple_http_resources = kwargs.pop("simple_http_resources", set())
self.timeout = kwargs.pop('timeout', 30)
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def log_message(self, format, *args): # pylint: disable=redefined-builtin
@@ -212,7 +254,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.path)
@classmethod
def partial_init(cls, simple_http_resources):
def partial_init(cls, simple_http_resources, timeout):
"""Partially initialize this handler.
This is useful because `socketserver.BaseServer` takes
@@ -221,4 +263,18 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""
return functools.partial(
cls, simple_http_resources=simple_http_resources)
cls, simple_http_resources=simple_http_resources,
timeout=timeout)
class _BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler):
"""BaseRequestHandler with logging."""
def log_message(self, format, *args): # pylint: disable=redefined-builtin
"""Log arbitrary message."""
logger.debug("%s - - %s", self.client_address[0], format % args)
def handle(self):
"""Handle request."""
self.log_message("Incoming request")
socketserver.BaseRequestHandler.handle(self)

View File

@@ -13,7 +13,6 @@
# serve to show the default.
import os
import shlex
import sys
here = os.path.abspath(os.path.dirname(__file__))

View File

@@ -1,10 +1,12 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@@ -15,7 +17,6 @@ install_requires = [
# 1.1.0+ is required to avoid the warnings described at
# https://github.com/certbot/josepy/issues/13.
'josepy>=1.1.0',
'mock',
# Connection.set_tlsext_host_name (>=0.13)
'PyOpenSSL>=0.13.1',
'pyrfc3339',
@@ -26,6 +27,15 @@ install_requires = [
'six>=1.9.0', # needed for python_2_unicode_compatible
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
dev_extras = [
'pytest',
'pytest-xdist',

View File

@@ -2,10 +2,16 @@
import unittest
import josepy as jose
import mock
import OpenSSL
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import requests
from six.moves.urllib import parse as urllib_parse
from acme import errors
import test_util
CERT = test_util.load_comparable_cert('cert.pem')
@@ -256,30 +262,87 @@ class HTTP01Test(unittest.TestCase):
class TLSALPN01ResponseTest(unittest.TestCase):
def setUp(self):
from acme.challenges import TLSALPN01Response
self.msg = TLSALPN01Response(key_authorization=u'foo')
from acme.challenges import TLSALPN01
self.chall = TLSALPN01(
token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e'))
self.domain = u'example.com'
self.domain2 = u'example2.com'
self.response = self.chall.response(KEY)
self.jmsg = {
'resource': 'challenge',
'type': 'tls-alpn-01',
'keyAuthorization': u'foo',
'keyAuthorization': self.response.key_authorization,
}
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.response.to_partial_json())
def test_from_json(self):
from acme.challenges import TLSALPN01Response
self.assertEqual(self.msg, TLSALPN01Response.from_json(self.jmsg))
self.assertEqual(self.response, TLSALPN01Response.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import TLSALPN01Response
hash(TLSALPN01Response.from_json(self.jmsg))
def test_gen_verify_cert(self):
key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
cert, key2 = self.response.gen_cert(self.domain, key1)
self.assertEqual(key1, key2)
self.assertTrue(self.response.verify_cert(self.domain, cert))
def test_gen_verify_cert_gen_key(self):
cert, key = self.response.gen_cert(self.domain)
self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
self.assertTrue(self.response.verify_cert(self.domain, cert))
def test_verify_bad_cert(self):
self.assertFalse(self.response.verify_cert(self.domain,
test_util.load_cert('cert.pem')))
def test_verify_bad_domain(self):
key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem')
cert, key2 = self.response.gen_cert(self.domain, key1)
self.assertEqual(key1, key2)
self.assertFalse(self.response.verify_cert(self.domain2, cert))
def test_simple_verify_bad_key_authorization(self):
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
self.response.simple_verify(self.chall, "local", key2.public_key())
@mock.patch('acme.challenges.TLSALPN01Response.verify_cert', autospec=True)
def test_simple_verify(self, mock_verify_cert):
mock_verify_cert.return_value = mock.sentinel.verification
self.assertEqual(
mock.sentinel.verification, self.response.simple_verify(
self.chall, self.domain, KEY.public_key(),
cert=mock.sentinel.cert))
mock_verify_cert.assert_called_once_with(
self.response, self.domain, mock.sentinel.cert)
@mock.patch('acme.challenges.socket.gethostbyname')
@mock.patch('acme.challenges.crypto_util.probe_sni')
def test_probe_cert(self, mock_probe_sni, mock_gethostbyname):
mock_gethostbyname.return_value = '127.0.0.1'
self.response.probe_cert('foo.com')
mock_gethostbyname.assert_called_once_with('foo.com')
mock_probe_sni.assert_called_once_with(
host='127.0.0.1', port=self.response.PORT, name='foo.com',
alpn_protocols=['acme-tls/1'])
self.response.probe_cert('foo.com', host='8.8.8.8')
mock_probe_sni.assert_called_with(
host='8.8.8.8', port=mock.ANY, name='foo.com',
alpn_protocols=['acme-tls/1'])
@mock.patch('acme.challenges.TLSALPN01Response.probe_cert')
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
mock_probe_cert.side_effect = errors.Error
self.assertFalse(self.response.simple_verify(
self.chall, self.domain, KEY.public_key()))
class TLSALPN01Test(unittest.TestCase):
@@ -309,8 +372,13 @@ class TLSALPN01Test(unittest.TestCase):
self.assertRaises(
jose.DeserializationError, TLSALPN01.from_json, self.jmsg)
def test_validation(self):
self.assertRaises(NotImplementedError, self.msg.validation, KEY)
@mock.patch('acme.challenges.TLSALPN01Response.gen_cert')
def test_validation(self, mock_gen_cert):
mock_gen_cert.return_value = ('cert', 'key')
self.assertEqual(('cert', 'key'), self.msg.validation(
KEY, cert_key=mock.sentinel.cert_key, domain=mock.sentinel.domain))
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key,
domain=mock.sentinel.domain)
class DNSTest(unittest.TestCase):
@@ -413,5 +481,18 @@ class DNSResponseTest(unittest.TestCase):
self.msg.check_validation(self.chall, KEY.public_key()))
class JWSPayloadRFC8555Compliant(unittest.TestCase):
"""Test for RFC8555 compliance of JWS generated from resources/challenges"""
def test_challenge_payload(self):
from acme.challenges import HTTP01Response
challenge_body = HTTP01Response()
challenge_body.le_acme_version = 2
jobj = challenge_body.json_dumps(indent=2).encode()
# RFC8555 states that challenge responses must have an empty payload.
self.assertEqual(jobj, b'{}')
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -6,7 +6,10 @@ import json
import unittest
import josepy as jose
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import OpenSSL
import requests
from six.moves import http_client # pylint: disable=import-error
@@ -15,7 +18,7 @@ from acme import challenges
from acme import errors
from acme import jws as acme_jws
from acme import messages
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
from acme.mixins import VersionedLEACMEMixin
import messages_test
import test_util
@@ -886,7 +889,7 @@ class ClientV2Test(ClientTestBase):
self.client.net.get.assert_not_called()
class MockJSONDeSerializable(jose.JSONDeSerializable):
class MockJSONDeSerializable(VersionedLEACMEMixin, jose.JSONDeSerializable):
# pylint: disable=missing-docstring
def __init__(self, value):
self.value = value

View File

@@ -11,14 +11,12 @@ import six
from six.moves import socketserver # type: ignore # pylint: disable=import-error
from acme import errors
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
import test_util
class SSLSocketAndProbeSNITest(unittest.TestCase):
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
def setUp(self):
self.cert = test_util.load_comparable_cert('rsa2048_cert.pem')
key = test_util.load_pyopenssl_private_key('rsa2048_key.pem')
@@ -32,7 +30,8 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
# six.moves.* | pylint: disable=attribute-defined-outside-init,no-init
def server_bind(self): # pylint: disable=missing-docstring
self.socket = SSLSocket(socket.socket(), certs=certs)
self.socket = SSLSocket(socket.socket(),
certs)
socketserver.TCPServer.server_bind(self)
self.server = _TestServer(('', 0), socketserver.BaseRequestHandler)
@@ -73,6 +72,18 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
socket.setdefaulttimeout(original_timeout)
class SSLSocketTest(unittest.TestCase):
"""Tests for acme.crypto_util.SSLSocket."""
def test_ssl_socket_invalid_arguments(self):
from acme.crypto_util import SSLSocket
with self.assertRaises(ValueError):
_ = SSLSocket(None, {'sni': ('key', 'cert')},
cert_selection=lambda _: None)
with self.assertRaises(ValueError):
_ = SSLSocket(None)
class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
"""Test for acme.crypto_util._pyopenssl_cert_or_req_all_names."""

View File

@@ -1,7 +1,10 @@
"""Tests for acme.errors."""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
class BadNonceTest(unittest.TestCase):
@@ -35,7 +38,7 @@ class PollErrorTest(unittest.TestCase):
def setUp(self):
from acme.errors import PollError
self.timeout = PollError(
exhausted=set([mock.sentinel.AR]),
exhausted={mock.sentinel.AR},
updated={})
self.invalid = PollError(exhausted=set(), updated={
mock.sentinel.AR: mock.sentinel.AR2})

View File

@@ -2,7 +2,10 @@
import sys
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
class MagicTypingTest(unittest.TestCase):
@@ -18,7 +21,7 @@ class MagicTypingTest(unittest.TestCase):
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
from acme.magic_typing import Text
self.assertEqual(Text, text_mock)
del sys.modules['acme.magic_typing']
sys.modules['typing'] = temp_typing
@@ -31,7 +34,7 @@ class MagicTypingTest(unittest.TestCase):
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
from acme.magic_typing import Text
self.assertTrue(Text is None)
del sys.modules['acme.magic_typing']
sys.modules['typing'] = temp_typing

View File

@@ -2,10 +2,12 @@
import unittest
import josepy as jose
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from acme import challenges
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
import test_util
CERT = test_util.load_comparable_cert('cert.der')
@@ -453,6 +455,7 @@ class OrderResourceTest(unittest.TestCase):
'authorizations': None,
})
class NewOrderTest(unittest.TestCase):
"""Tests for acme.messages.NewOrder."""
@@ -467,5 +470,18 @@ class NewOrderTest(unittest.TestCase):
})
class JWSPayloadRFC8555Compliant(unittest.TestCase):
"""Test for RFC8555 compliance of JWS generated from resources/challenges"""
def test_message_payload(self):
from acme.messages import NewAuthorization
new_order = NewAuthorization()
new_order.le_acme_version = 2
jobj = new_order.json_dumps(indent=2).encode()
# RFC8555 states that JWS bodies must not have a resource field.
self.assertEqual(jobj, b'{}')
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -4,13 +4,18 @@ import threading
import unittest
import josepy as jose
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import requests
from six.moves import http_client # pylint: disable=import-error
from six.moves import socketserver # type: ignore # pylint: disable=import-error
from acme import challenges
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from acme import crypto_util
from acme import errors
import test_util
@@ -83,6 +88,81 @@ class HTTP01ServerTest(unittest.TestCase):
def test_http01_not_found(self):
self.assertFalse(self._test_http01(add=False))
def test_timely_shutdown(self):
from acme.standalone import HTTP01Server
server = HTTP01Server(('', 0), resources=set(), timeout=0.05)
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
client = socket.socket()
client.connect(('localhost', server.socket.getsockname()[1]))
stop_thread = threading.Thread(target=server.shutdown)
stop_thread.start()
server_thread.join(5.)
is_hung = server_thread.is_alive()
try:
client.shutdown(socket.SHUT_RDWR)
except: # pragma: no cover, pylint: disable=bare-except
# may raise error because socket could already be closed
pass
self.assertFalse(is_hung, msg='Server shutdown should not be hung')
@unittest.skipIf(not challenges.TLSALPN01.is_supported(), "pyOpenSSL too old")
class TLSALPN01ServerTest(unittest.TestCase):
"""Test for acme.standalone.TLSALPN01Server."""
def setUp(self):
self.certs = {b'localhost': (
test_util.load_pyopenssl_private_key('rsa2048_key.pem'),
test_util.load_cert('rsa2048_cert.pem'),
)}
# Use different certificate for challenge.
self.challenge_certs = {b'localhost': (
test_util.load_pyopenssl_private_key('rsa4096_key.pem'),
test_util.load_cert('rsa4096_cert.pem'),
)}
from acme.standalone import TLSALPN01Server
self.server = TLSALPN01Server(("localhost", 0), certs=self.certs,
challenge_certs=self.challenge_certs)
# pylint: disable=no-member
self.thread = threading.Thread(target=self.server.serve_forever)
self.thread.start()
def tearDown(self):
self.server.shutdown() # pylint: disable=no-member
self.thread.join()
# TODO: This is not implemented yet, see comments in standalone.py
# def test_certs(self):
# host, port = self.server.socket.getsockname()[:2]
# cert = crypto_util.probe_sni(
# b'localhost', host=host, port=port, timeout=1)
# # Expect normal cert when connecting without ALPN.
# self.assertEqual(jose.ComparableX509(cert),
# jose.ComparableX509(self.certs[b'localhost'][1]))
def test_challenge_certs(self):
host, port = self.server.socket.getsockname()[:2]
cert = crypto_util.probe_sni(
b'localhost', host=host, port=port, timeout=1,
alpn_protocols=[b"acme-tls/1"])
# Expect challenge cert when connecting with ALPN.
self.assertEqual(
jose.ComparableX509(cert),
jose.ComparableX509(self.challenge_certs[b'localhost'][1])
)
def test_bad_alpn(self):
host, port = self.server.socket.getsockname()[:2]
with self.assertRaises(errors.Error):
crypto_util.probe_sni(
b'localhost', host=host, port=port, timeout=1,
alpn_protocols=[b"bad-alpn"])
class BaseDualNetworkedServersTest(unittest.TestCase):
"""Test for acme.standalone.BaseDualNetworkedServers."""
@@ -138,7 +218,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
class HTTP01DualNetworkedServersTest(unittest.TestCase):
"""Tests for acme.standalone.HTTP01DualNetworkedServers."""
def setUp(self):
self.account_key = jose.JWK.load(
test_util.load_vector('rsa1024_key.pem'))

View File

@@ -4,12 +4,14 @@ to use appropriate extension for vector filenames: .pem for PEM and
The following command has been used to generate test keys:
for x in 256 512 1024 2048; do openssl genrsa -out rsa${k}_key.pem $k; done
for k in 256 512 1024 2048 4096; do openssl genrsa -out rsa${k}_key.pem $k; done
and for the CSR:
openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -outform DER > csr.der
and for the certificate:
and for the certificates:
openssl req -key rsa2047_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der
openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -x509 -outform DER > cert.der
openssl req -key rsa2048_key.pem -new -subj '/CN=example.com' -x509 > rsa2048_cert.pem
openssl req -key rsa1024_key.pem -new -subj '/CN=example.com' -x509 > rsa1024_cert.pem

13
acme/tests/testdata/rsa1024_cert.pem vendored Normal file
View File

@@ -0,0 +1,13 @@
-----BEGIN CERTIFICATE-----
MIIB/TCCAWagAwIBAgIJAOyRIBs3QT8QMA0GCSqGSIb3DQEBCwUAMBYxFDASBgNV
BAMMC2V4YW1wbGUuY29tMB4XDTE4MDQyMzEwMzE0NFoXDTE4MDUyMzEwMzE0NFow
FjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
AoGBAJqJ87R8aVwByONxgQA9hwgvQd/QqI1r1UInXhEF2VnEtZGtUWLi100IpIqr
Mq4qusDwNZ3g8cUPtSkvJGs89djoajMDIJP7lQUEKUYnYrI0q755Tr/DgLWSk7iW
l5ezym0VzWUD0/xXUz8yRbNMTjTac80rS5SZk2ja2wWkYlRJAgMBAAGjUzBRMB0G
A1UdDgQWBBSsaX0IVZ4XXwdeffVAbG7gnxSYjTAfBgNVHSMEGDAWgBSsaX0IVZ4X
XwdeffVAbG7gnxSYjTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4GB
ADe7SVmvGH2nkwVfONk8TauRUDkePN1CJZKFb2zW1uO9ANJ2v5Arm/OQp0BG/xnI
Djw/aLTNVESF89oe15dkrUErtcaF413MC1Ld5lTCaJLHLGqDKY69e02YwRuxW7jY
qarpt7k7aR5FbcfO5r4V/FK/Gvp4Dmoky8uap7SJIW6x
-----END CERTIFICATE-----

30
acme/tests/testdata/rsa4096_cert.pem vendored Normal file
View File

@@ -0,0 +1,30 @@
-----BEGIN CERTIFICATE-----
MIIFDTCCAvWgAwIBAgIUImqDrP53V69vFROsjP/gL0YtoA4wDQYJKoZIhvcNAQEL
BQAwFjEUMBIGA1UEAwwLZXhhbXBsZS5jb20wHhcNMjAwNTI3MjMyNDE0WhcNMjAw
NjI2MjMyNDE0WjAWMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCAiIwDQYJKoZIhvcN
AQEBBQADggIPADCCAgoCggIBANY9LKLk9Dxn0MUMQFHwBoTN4ehDSWBws2KcytpF
mc8m9Mfk1wmb4fQSKYtK3wIFMfIyo9HQu0nKqMkkUw52o3ZXyOv+oWwF5qNy2BKu
lh5OMSkaZ0o13zoPpW42e+IUnyxvg70+0urD+sUue4cyTHh/nBIUjrM/05ZJ/ac8
HR0RK3H41YoqBjq69JjMZczZZhbNFit3s6p0R1TbVAgc3ckqbtX5BDyQMQQCP4Ed
m4DgbAFVqdcPUCC5W3F3fmuQiPKHiADzONZnXpy6lUvLDWqcd6loKp+nKHM6OkXX
8hmD7pE1PYMQo4hqOfhBR2IgMjAShwd5qUFjl1m2oo0Qm3PFXOk6i2ZQdS6AA/yd
B5/mX0RnM2oIdFZPb6UZFSmtEgs9sTzn+hMUyNSZQRE54px1ur1xws2R+vbsCyM5
+KoFVxDjVjU9TlZx3GvDvnqz/tbHjji6l8VHZYOBMBUXbKHu2U6pJFZ5Zp7k68/z
a3Fb9Pjtn3iRkXEyC0N5kLgqO4QTlExnxebV8aMvQpWd/qefnMn9qPYIZPEXSQAR
mEBIahkcACb60s+acG0WFFluwBPtBqEr8Q67XlSF0Ibf4iBiRzpPobhlWta1nrFg
4IWHMSoZ0PE75bhIGBEkhrpcXQCAxXmAfxfjKDH7jdJ1fRdnZ/9+OzwYGVX5GH/l
0QDtAgMBAAGjUzBRMB0GA1UdDgQWBBQh3xiz/o1nEU2ySylZ9gxCXvIPGzAfBgNV
HSMEGDAWgBQh3xiz/o1nEU2ySylZ9gxCXvIPGzAPBgNVHRMBAf8EBTADAQH/MA0G
CSqGSIb3DQEBCwUAA4ICAQAELoXz31oR9pdAwidlv9ZBOKiC7KBWy8VMqXNVkfTn
bVRxAUex7zleLFIOkWnqadsMesU9sIwrbLzBcZ8Q/vBY+z2xOPdXcgcAoAmdKWoq
YBQNiqng9r54sqlzB/77QZCf5fdktESe7NTxhCifgx5SAWq7IUQs/lm3tnMUSAfE
5ctuN6M+w8K54y3WDprcfMHpnc3ZHeSPhVQApHM0h/bDvXq0bRS7kmq27Hb153Qm
nH3TwYB5pPSWW38NbUc+s/a7mItO7S8ly8yGbA0j9c/IbN5lM+OCdk06asz3+c8E
uo8nuCBoYO5+6AqC2N7WJ3Tdr/pFA8jTbd6VNVlgCWTIR8ZosL5Fgkfv+4fUBrHt
zdVUqMUzvga5rvZnwnJ5Qfu/drHeAAo9MTNFQNe2QgDlYfWBh5GweolgmFSwrpkY
v/5wLtIyv/ASHKswybbqMIlpttcLTXjx5yuh8swttT6Wh+FQqqQ32KSRB3StiwyK
oH0ZhrwYHiFYNlPxecGX6XUta6rFtTlEdkBGSnXzgiTzL2l+Nc0as0V5B9RninZG
qJ+VOChSQ0OFvg1riSXv7tMvbLdGQnxwTRL3t6BMS8I4LA2m3ZfWUcuXT783ODTH
16f1Q1AgXd2csstTWO9cv+N/0fpX31nqrm6+CrGduSr2u4HjYYnlLIUhmdTvK3fX
Fg==
-----END CERTIFICATE-----

51
acme/tests/testdata/rsa4096_key.pem vendored Normal file
View File

@@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
MIIJKgIBAAKCAgEA1j0souT0PGfQxQxAUfAGhM3h6ENJYHCzYpzK2kWZzyb0x+TX
CZvh9BIpi0rfAgUx8jKj0dC7ScqoySRTDnajdlfI6/6hbAXmo3LYEq6WHk4xKRpn
SjXfOg+lbjZ74hSfLG+DvT7S6sP6xS57hzJMeH+cEhSOsz/Tlkn9pzwdHRErcfjV
iioGOrr0mMxlzNlmFs0WK3ezqnRHVNtUCBzdySpu1fkEPJAxBAI/gR2bgOBsAVWp
1w9QILlbcXd+a5CI8oeIAPM41mdenLqVS8sNapx3qWgqn6coczo6RdfyGYPukTU9
gxCjiGo5+EFHYiAyMBKHB3mpQWOXWbaijRCbc8Vc6TqLZlB1LoAD/J0Hn+ZfRGcz
agh0Vk9vpRkVKa0SCz2xPOf6ExTI1JlBETninHW6vXHCzZH69uwLIzn4qgVXEONW
NT1OVnHca8O+erP+1seOOLqXxUdlg4EwFRdsoe7ZTqkkVnlmnuTrz/NrcVv0+O2f
eJGRcTILQ3mQuCo7hBOUTGfF5tXxoy9ClZ3+p5+cyf2o9ghk8RdJABGYQEhqGRwA
JvrSz5pwbRYUWW7AE+0GoSvxDrteVIXQht/iIGJHOk+huGVa1rWesWDghYcxKhnQ
8TvluEgYESSGulxdAIDFeYB/F+MoMfuN0nV9F2dn/347PBgZVfkYf+XRAO0CAwEA
AQKCAgEA0hZdTkQtCYtYm9LexDsXeWYX8VcCfrMmBj7xYcg9A3oVMmzDPuYBVwH0
gWbjd6y2hOaJ5TfGYZ99kvmvBRDsTSHaoyopC7BhssjtAKz6Ay/0X3VH8usPQ3WS
aZi+NT65tK6KRqtz08ppgLGLa1G00bl5x/Um1rpxeACI4FU/y4BJ1VMJvJpnT3KE
Z86Qyagqx5NH+UpCApZSWPFX3zjHePzGgcfXErjniCHYOnpZQrFQ2KIzkfSvQ9fg
x01ByKOM2CB2C1B33TCzBAioXRH6zyAu7A59NeCK9ywTduhDvie1a+oEryFC7IQW
4s7I/H3MGX4hsf/pLXlHMy+5CZJOjRaC2h+pypfbbcuiXu6Sn64kHNpiI7SxI5DI
MIRjyG7MdUcrzq0Rt8ogwwpbCoRqrl/w3bhxtqmeZaEZtyxbjlm7reK2YkIFDgyz
JMqiJK5ZAi+9L/8c0xhjjAQQ0sIzrjmjA8U+6YnWL9jU5qXTVnBB8XQucyeeZGgk
yRHyMur71qOXN8z3UEva7MHkDTUBlj8DgTz6sEjqCipaWl0CXfDNa4IhHIXD5qiF
wplhq7OeS0v6EGG/UFa3Q/lFntxtrayxJX7uvvSccGzjPKXTjpWUELLi/FdnIsum
eXT3RgIEYozj4BibDXaBLfHTCVzxOr7AAEvKM9XWSUgLA0paSWECggEBAO9ZBeE1
GWzd1ejTTkcxBC9AK2rNsYG8PdNqiof/iTbuJWNeRqpG+KB/0CNIpjZ2X5xZd0tM
FDpHTFehlP26Roxuq50iRAFc+SN5KoiO0A3JuJAidreIgRTia1saUUrypHqWrYEA
VZVj2AI8Pyg3s1OkR2frFskY7hXBVb/pJNDP/m9xTXXIYiIXYkHYe+4RIJCnAxRv
q5YHKaX+0Ull9YCZJCxmwvcHat8sgu8qkiwUMEM6QSNEkrEbdnWYBABvC1AR6sws
7MP1h9+j22n4Zc/3D6kpFZEL9Erx8nNyhbOZ6q2Tdnf6YKVVjZdyVa8VyNnR0ROl
3BjkFaHb/bg4e4kCggEBAOUk8ZJS3qBeGCOjug384zbHGcnhUBYtYJiOz+RXBtP+
PRksbFtTkgk1sHuSGO8YRddU4Qv7Av1xL8o+DEsLBSD0YQ7pmLrR/LK+iDQ5N63O
Fve9uJH0ybxAOkiua7G24+lTsVUP//KWToL4Wh5zbHBBjL5D2Z9zoeVbcE87xhva
lImMVr4Ex252DqNP9wkZxBjudFyJ/C/TnXrjPcgwhxWTC7sLQMhE5p+490G7c4hX
PywkIKrANbu37KDiAvVS+dC66ZgpL/NUDkeloAmGNO08LGzbV6YKchlvDyWU/AvW
0hYjbL0FUq7K/wp1G9fumolB+fbI25K9c13X93STzUUCggEBAJDsNFUyk5yJjbYW
C/WrRj9d+WwH9Az77+uNPSgvn+O0usq6EMuVgYGdImfa21lqv2Wp/kOHY1AOT7lX
yyD+oyzw7dSNJOQ2aVwDR6+72Vof5DLRy1RBwPbmSd61xrc8yD658YCEtU1pUSe5
VvyBDYH9nIbdn8RP5gkiMUusXXBaIFNWJXLFzDWcNxBrhk6V7EPp/EFphFmpKJyr
+AkbRVWCZJbF+hMdWKadCwLJogwyhS6PnVU/dhrq6AU38GRa2Fy5HJRYN1xH1Oej
DX3Su8L6c28Xw0k6FcczTHx+wVoIPkKvYTIwVkiFzt/+iMckx6KsGo5tBSHFKRwC
WlQrTxECggEBALjUruLnY1oZ7AC7bTUhOimSOfQEgTQSUCtebsRxijlvhtsKYTDd
XRt+qidStjgN7S/+8DRYuZWzOeg5WnMhpXZqiOudcyume922IGl3ibjxVsdoyjs5
J4xohlrgDlBgBMDNWGoTqNGFejjcmNydH+gAh8VlN2INxJYbxqCyx17qVgwJHmLR
uggYxD/pHYvCs9GkbknCp5/wYsOgDtKuihfV741lS1D/esN1UEQ+LrfYIEW7snno
5q7Pcdhn1hkKYCWEzy2Ec4Aj2gzixQ9JqOF/OxpnZvCw1k47rg0TeqcWFYnz8x8Y
7xO8/DH0OoxXk2GJzVXJuItJs4gLzzfCjL0CggEAJFHfC9jisdy7CoWiOpNCSF1B
S0/CWDz77cZdlWkpTdaXGGp1MA/UKUFPIH8sOHfvpKS660+X4G/1ZBHmFb4P5kFF
Qy8UyUMKtSOEdZS6KFlRlfSCAMd5aSTmCvq4OSjYEpMRwUhU/iEJNkn9Z1Soehe0
U3dxJ8KiT1071geO6rRquSHoSJs6Y0WQKriYYQJOhh4Axs3PQihER2eyh+WGk8YJ
02m0mMsjntqnXtdc6IcdKaHp9ko+OpM9QZLsvt19fxBcrXj/i21uUXrzuNtKfO6M
JqGhsOrO2dh8lMhvodENvgKA0DmYDC9N7ogo7bxTNSedcjBF46FhJoqii8m70Q==
-----END RSA PRIVATE KEY-----

View File

@@ -1,7 +1,7 @@
include LICENSE.txt
include README.rst
recursive-include tests *
include certbot_apache/_internal/options-ssl-apache.conf
recursive-include certbot_apache/_internal/augeas_lens *.aug
recursive-include certbot_apache/_internal/tls_configs *.conf
global-exclude __pycache__
global-exclude *.py[cod]

View File

@@ -5,6 +5,8 @@ import logging
import re
import subprocess
import pkg_resources
from certbot import errors
from certbot import util
@@ -128,7 +130,7 @@ def included_in_paths(filepath, paths):
:rtype: bool
"""
return any([fnmatch.fnmatch(filepath, path) for path in paths])
return any(fnmatch.fnmatch(filepath, path) for path in paths)
def parse_defines(apachectl):
@@ -142,7 +144,7 @@ def parse_defines(apachectl):
:rtype: dict
"""
variables = dict()
variables = {}
define_cmd = [apachectl, "-t", "-D",
"DUMP_RUN_CFG"]
matches = parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
@@ -241,3 +243,14 @@ def _get_runtime_cfg(command):
"loaded because Apache is misconfigured.")
return stdout
def find_ssl_apache_conf(prefix):
"""
Find a TLS Apache config file in the dedicated storage.
:param str prefix: prefix of the TLS Apache config file to find
:return: the path the TLS Apache config file
:rtype: str
"""
return pkg_resources.resource_filename(
"certbot_apache",
os.path.join("_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix)))

View File

@@ -73,7 +73,7 @@ class ApacheDirectiveNode(ApacheParserNode):
self.metadata == other.metadata)
return False
def set_parameters(self, _parameters):
def set_parameters(self, _parameters): # pragma: no cover
"""Sets the parameters for DirectiveNode"""
return
@@ -97,7 +97,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
self.metadata == other.metadata)
return False
def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument
# pylint: disable=unused-argument
def add_child_block(self, name, parameters=None, position=None): # pragma: no cover
"""Adds a new BlockNode to the sequence of children"""
new_block = ApacheBlockNode(name=assertions.PASS,
parameters=assertions.PASS,
@@ -107,7 +108,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
self.children += (new_block,)
return new_block
def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument
# pylint: disable=unused-argument
def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover
"""Adds a new DirectiveNode to the sequence of children"""
new_dir = ApacheDirectiveNode(name=assertions.PASS,
parameters=assertions.PASS,
@@ -144,7 +146,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
filepath=assertions.PASS,
metadata=self.metadata)]
def find_comments(self, comment, exact=False): # pylint: disable=unused-argument
# pylint: disable=unused-argument
def find_comments(self, comment, exact=False): # pragma: no cover
"""Recursive search of DirectiveNodes from the sequence of children"""
return [ApacheCommentNode(comment=assertions.PASS,
ancestor=self,

View File

@@ -132,9 +132,9 @@ def assertEqualPathsList(first, second): # pragma: no cover
Checks that the two lists of file paths match. This assertion allows for wildcard
paths.
"""
if any([isPass(path) for path in first]):
if any(isPass(path) for path in first):
return
if any([isPass(path) for path in second]):
if any(isPass(path) for path in second):
return
for fpath in first:
assert any([fnmatch.fnmatch(fpath, spath) for spath in second])

View File

@@ -64,7 +64,7 @@ Translates over to:
"/files/etc/apache2/apache2.conf/bLoCk[1]",
]
"""
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Set
from certbot import errors
from certbot.compat import os
@@ -339,7 +339,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
def find_blocks(self, name, exclude=True):
"""Recursive search of BlockNodes from the sequence of children"""
nodes = list()
nodes = []
paths = self._aug_find_blocks(name)
if exclude:
paths = self.parser.exclude_dirs(paths)
@@ -351,7 +351,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
def find_directives(self, name, exclude=True):
"""Recursive search of DirectiveNodes from the sequence of children"""
nodes = list()
nodes = []
ownpath = self.metadata.get("augeaspath")
directives = self.parser.find_dir(name, start=ownpath, exclude=exclude)
@@ -374,7 +374,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
:param str comment: Comment content to search for.
"""
nodes = list()
nodes = []
ownpath = self.metadata.get("augeaspath")
comments = self.parser.find_comments(comment, start=ownpath)

View File

@@ -1,6 +1,7 @@
"""Apache Configurator."""
# pylint: disable=too-many-lines
from collections import defaultdict
from distutils.version import LooseVersion
import copy
import fnmatch
import logging
@@ -8,10 +9,14 @@ import re
import socket
import time
import pkg_resources
import six
import zope.component
import zope.interface
try:
import apacheconfig
HAS_APACHECONFIG = True
except ImportError: # pragma: no cover
HAS_APACHECONFIG = False
from acme import challenges
from acme.magic_typing import DefaultDict
@@ -110,14 +115,30 @@ class ApacheConfigurator(common.Installer):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None
)
def option(self, key):
"""Get a value from options"""
return self.options.get(key)
def pick_apache_config(self, warn_on_no_mod_ssl=True):
"""
Pick the appropriate TLS Apache configuration file for current version of Apache and OS.
:param bool warn_on_no_mod_ssl: True if we should warn if mod_ssl is not found.
:return: the path to the TLS Apache configuration file to use
:rtype: str
"""
# Disabling TLS session tickets is supported by Apache 2.4.11+ and OpenSSL 1.0.2l+.
# So for old versions of Apache we pick a configuration without this option.
openssl_version = self.openssl_version(warn_on_no_mod_ssl)
if self.version < (2, 4, 11) or not openssl_version or\
LooseVersion(openssl_version) < LooseVersion('1.0.2l'):
return apache_util.find_ssl_apache_conf("old")
return apache_util.find_ssl_apache_conf("current")
def _prepare_options(self):
"""
Set the values possibly changed by command line parameters to
@@ -125,7 +146,7 @@ class ApacheConfigurator(common.Installer):
"""
opts = ["enmod", "dismod", "le_vhost_ext", "server_root", "vhost_root",
"logs_root", "challenge_location", "handle_modules", "handle_sites",
"ctl"]
"ctl", "bin"]
for o in opts:
# Config options use dashes instead of underscores
if self.conf(o.replace("_", "-")) is not None:
@@ -174,6 +195,8 @@ class ApacheConfigurator(common.Installer):
"(Only Ubuntu/Debian currently)")
add("ctl", default=DEFAULTS["ctl"],
help="Full path to Apache control script")
add("bin", default=DEFAULTS["bin"],
help="Full path to apache2/httpd binary")
def __init__(self, *args, **kwargs):
"""Initialize an Apache Configurator.
@@ -184,15 +207,16 @@ class ApacheConfigurator(common.Installer):
"""
version = kwargs.pop("version", None)
use_parsernode = kwargs.pop("use_parsernode", False)
openssl_version = kwargs.pop("openssl_version", None)
super(ApacheConfigurator, self).__init__(*args, **kwargs)
# Add name_server association dict
self.assoc = dict() # type: Dict[str, obj.VirtualHost]
self.assoc = {} # type: Dict[str, obj.VirtualHost]
# Outstanding challenges
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() # type: Dict[str, List[obj.VirtualHost]]
self._wildcard_vhosts = {} # type: Dict[str, List[obj.VirtualHost]]
# Maps enhancements to vhosts we've enabled the enhancement for
self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]]
# Temporary state for AutoHSTS enhancement
@@ -209,6 +233,7 @@ class ApacheConfigurator(common.Installer):
self.parser = None
self.parser_root = None
self.version = version
self._openssl_version = openssl_version
self.vhosts = None
self.options = copy.deepcopy(self.OS_DEFAULTS)
self._enhance_func = {"redirect": self._enable_redirect,
@@ -225,6 +250,59 @@ class ApacheConfigurator(common.Installer):
"""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 _open_module_file(self, ssl_module_location):
"""Extract the open lines of openssl_version for testing purposes"""
try:
with open(ssl_module_location, mode="rb") as f:
contents = f.read()
except IOError as error:
logger.debug(str(error), exc_info=True)
return None
return contents
def openssl_version(self, warn_on_no_mod_ssl=True):
"""Lazily retrieve openssl version
:param bool warn_on_no_mod_ssl: `True` if we should warn if mod_ssl is not found. Set to
`False` when we know we'll try to enable mod_ssl later. This is currently debian/ubuntu,
when called from `prepare`.
:return: the OpenSSL version as a string, or None.
:rtype: str or None
"""
if self._openssl_version:
return self._openssl_version
# Step 1. Determine the location of ssl_module
try:
ssl_module_location = self.parser.modules['ssl_module']
except KeyError:
if warn_on_no_mod_ssl:
logger.warning("Could not find ssl_module; not disabling session tickets.")
return None
if ssl_module_location:
# Possibility A: ssl_module is a DSO
ssl_module_location = self.parser.standard_path_from_server_root(ssl_module_location)
else:
# Possibility B: ssl_module is statically linked into Apache
if self.option("bin"):
ssl_module_location = self.option("bin")
else:
logger.warning("ssl_module is statically linked but --apache-bin is "
"missing; not disabling session tickets.")
return None
# Step 2. Grep in the binary for openssl version
contents = self._open_module_file(ssl_module_location)
if not contents:
logger.warning("Unable to read ssl_module file; not disabling session tickets.")
return None
# looks like: OpenSSL 1.0.2s 28 May 2019
matches = re.findall(br"OpenSSL ([0-9]\.[^ ]+) ", contents)
if not matches:
logger.warning("Could not find OpenSSL version; not disabling session tickets.")
return None
self._openssl_version = matches[0].decode('UTF-8')
return self._openssl_version
def prepare(self):
"""Prepare the authenticator/installer.
@@ -271,8 +349,12 @@ class ApacheConfigurator(common.Installer):
# Get all of the available vhosts
self.vhosts = self.get_virtual_hosts()
# We may try to enable mod_ssl later. If so, we shouldn't warn if we can't find it now.
# This is currently only true for debian/ubuntu.
warn_on_no_mod_ssl = not self.option("handle_modules")
self.install_ssl_options_conf(self.mod_ssl_conf,
self.updated_mod_ssl_conf_digest)
self.updated_mod_ssl_conf_digest,
warn_on_no_mod_ssl)
# Prevent two Apache plugins from modifying a config at once
try:
@@ -363,11 +445,17 @@ class ApacheConfigurator(common.Installer):
def get_parsernode_root(self, metadata):
"""Initializes the ParserNode parser root instance."""
apache_vars = dict()
apache_vars["defines"] = apache_util.parse_defines(self.option("ctl"))
apache_vars["includes"] = apache_util.parse_includes(self.option("ctl"))
apache_vars["modules"] = apache_util.parse_modules(self.option("ctl"))
metadata["apache_vars"] = apache_vars
if HAS_APACHECONFIG:
apache_vars = {}
apache_vars["defines"] = apache_util.parse_defines(self.option("ctl"))
apache_vars["includes"] = apache_util.parse_includes(self.option("ctl"))
apache_vars["modules"] = apache_util.parse_modules(self.option("ctl"))
metadata["apache_vars"] = apache_vars
with open(self.parser.loc["root"]) as f:
with apacheconfig.make_loader(writable=True,
**apacheconfig.flavors.NATIVE_APACHE) as loader:
metadata["ac_ast"] = loader.loads(f.read())
return dualparser.DualBlockNode(
name=assertions.PASS,
@@ -470,7 +558,7 @@ class ApacheConfigurator(common.Installer):
# Go through the vhosts, making sure that we cover all the names
# present, but preferring the SSL vhosts
filtered_vhosts = dict()
filtered_vhosts = {}
for vhost in vhosts:
for name in vhost.get_names():
if vhost.ssl:
@@ -496,7 +584,7 @@ class ApacheConfigurator(common.Installer):
# Make sure we create SSL vhosts for the ones that are HTTP only
# if requested.
return_vhosts = list()
return_vhosts = []
for vhost in dialog_output:
if not vhost.ssl:
return_vhosts.append(self.make_vhost_ssl(vhost))
@@ -517,6 +605,11 @@ class ApacheConfigurator(common.Installer):
# cert_key... can all be parsed appropriately
self.prepare_server_https("443")
# If we haven't managed to enable mod_ssl by this point, error out
if "ssl_module" not in self.parser.modules:
raise errors.MisconfigurationError("Could not find ssl_module; "
"not installing certificate.")
# Add directives and remove duplicates
self._add_dummy_ssl_directives(vhost.path)
self._clean_vhost(vhost)
@@ -531,21 +624,6 @@ class ApacheConfigurator(common.Installer):
path["chain_path"] = self.parser.find_dir(
"SSLCertificateChainFile", None, vhost.path)
# Handle errors when certificate/key directives cannot be found
if not path["cert_path"]:
logger.warning(
"Cannot find an SSLCertificateFile directive in %s. "
"VirtualHost was not modified", vhost.path)
raise errors.PluginError(
"Unable to find an SSLCertificateFile directive")
elif not path["cert_key"]:
logger.warning(
"Cannot find an SSLCertificateKeyFile directive for "
"certificate in %s. VirtualHost was not modified", vhost.path)
raise errors.PluginError(
"Unable to find an SSLCertificateKeyFile directive for "
"certificate")
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
if self.version < (2, 4, 8) or (chain_path and not fullchain_path):
@@ -907,7 +985,7 @@ class ApacheConfigurator(common.Installer):
"""
v1_vhosts = self.get_virtual_hosts_v1()
if self.USE_PARSERNODE:
if self.USE_PARSERNODE and HAS_APACHECONFIG:
v2_vhosts = self.get_virtual_hosts_v2()
for v1_vh in v1_vhosts:
@@ -1241,6 +1319,14 @@ class ApacheConfigurator(common.Installer):
self.enable_mod("socache_shmcb", temp=temp)
if "ssl_module" not in self.parser.modules:
self.enable_mod("ssl", temp=temp)
# Make sure we're not throwing away any unwritten changes to the config
self.parser.ensure_augeas_state()
self.parser.aug.load()
self.parser.reset_modules() # Reset to load the new ssl_module path
# Call again because now we can gate on openssl version
self.install_ssl_options_conf(self.mod_ssl_conf,
self.updated_mod_ssl_conf_digest,
warn_on_no_mod_ssl=True)
def make_vhost_ssl(self, nonssl_vhost):
"""Makes an ssl_vhost version of a nonssl_vhost.
@@ -1493,7 +1579,7 @@ class ApacheConfigurator(common.Installer):
result.append(comment)
sift = True
result.append('\n'.join(['# ' + l for l in chunk]))
result.append('\n'.join('# ' + l for l in chunk))
else:
result.append('\n'.join(chunk))
return result, sift
@@ -1633,7 +1719,7 @@ class ApacheConfigurator(common.Installer):
for addr in vhost.addrs:
# In Apache 2.2, when a NameVirtualHost directive is not
# set, "*" and "_default_" will conflict when sharing a port
addrs = set((addr,))
addrs = {addr,}
if addr.get_addr() in ("*", "_default_"):
addrs.update(obj.Addr((a, addr.get_port(),))
for a in ("*", "_default_"))
@@ -1824,7 +1910,7 @@ class ApacheConfigurator(common.Installer):
try:
self._autohsts = self.storage.fetch("autohsts")
except KeyError:
self._autohsts = dict()
self._autohsts = {}
def _autohsts_save_state(self):
"""
@@ -2385,7 +2471,7 @@ class ApacheConfigurator(common.Installer):
if len(matches) != 1:
raise errors.PluginError("Unable to find Apache version")
return tuple([int(i) for i in matches[0].split(".")])
return tuple(int(i) for i in matches[0].split("."))
def more_info(self):
"""Human-readable string to help understand the module"""
@@ -2454,14 +2540,19 @@ class ApacheConfigurator(common.Installer):
self.restart()
self.parser.reset_modules()
def install_ssl_options_conf(self, options_ssl, options_ssl_digest):
"""Copy Certbot's SSL options file into the system's config dir if required."""
def install_ssl_options_conf(self, options_ssl, options_ssl_digest, warn_on_no_mod_ssl=True):
"""Copy Certbot's SSL options file into the system's config dir if required.
:param bool warn_on_no_mod_ssl: True if we should warn if mod_ssl is not found.
"""
# XXX if we ever try to enforce a local privilege boundary (eg, running
# 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.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
apache_config_path = self.pick_apache_config(warn_on_no_mod_ssl)
return common.install_version_controlled_file(
options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES)
def enable_autohsts(self, _unused_lineage, domains):
"""

View File

@@ -26,6 +26,7 @@ ALL_SSL_OPTIONS_HASHES = [
'06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7',
'5cc003edd93fb9cd03d40c7686495f8f058f485f75b5e764b789245a386e6daf',
'007cd497a56a3bb8b6a2c1aeb4997789e7e38992f74e44cc5d13a625a738ac73',
'34783b9e2210f5c4a23bced2dfd7ec289834716673354ed7c7abf69fe30192a3',
]
"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC"""

View File

@@ -21,7 +21,7 @@ def select_vhost_multiple(vhosts):
:rtype: :class:`list`of type `~obj.Vhost`
"""
if not vhosts:
return list()
return []
tags_list = [vhost.display_repr()+"\n" for vhost in vhosts]
# Remove the extra newline from the last entry
if tags_list:
@@ -37,7 +37,7 @@ def select_vhost_multiple(vhosts):
def _reversemap_vhosts(names, vhosts):
"""Helper function for select_vhost_multiple for mapping string
representations back to actual vhost objects"""
return_vhosts = list()
return_vhosts = []
for selection in names:
for vhost in vhosts:

View File

@@ -49,7 +49,7 @@ class DualNodeBase(object):
pass_primary = assertions.isPassNodeList(primary_res)
pass_secondary = assertions.isPassNodeList(secondary_res)
new_nodes = list()
new_nodes = []
if pass_primary and pass_secondary:
# Both unimplemented
@@ -221,7 +221,7 @@ class DualBlockNode(DualNodeBase):
implementations to a list of tuples.
"""
matched = list()
matched = []
for p in primary_list:
match = None
for s in secondary_list:

View File

@@ -1,6 +1,4 @@
""" Entry point for Apache Plugin """
# Pylint does not like disutils.version when running inside a venv.
# See: https://github.com/PyCQA/pylint/issues/73
from distutils.version import LooseVersion
from certbot import util

View File

@@ -1,5 +1,6 @@
"""A class that performs HTTP-01 challenges for Apache"""
import logging
import errno
from acme.magic_typing import List
from acme.magic_typing import Set
@@ -168,7 +169,15 @@ class ApacheHttp01(common.ChallengePerformer):
def _set_up_challenges(self):
if not os.path.isdir(self.challenge_dir):
filesystem.makedirs(self.challenge_dir, 0o755)
old_umask = filesystem.umask(0o022)
try:
filesystem.makedirs(self.challenge_dir, 0o755)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for http-01 challenge")
finally:
filesystem.umask(old_umask)
responses = []
for achall in self.achalls:

View File

@@ -102,7 +102,6 @@ For this reason the internal representation of data should not ignore the case.
import abc
import six
from acme.magic_typing import Any, Dict, Optional, Tuple # pylint: disable=unused-import, no-name-in-module
@six.add_metaclass(abc.ABCMeta)

View File

@@ -1,9 +1,7 @@
""" Distribution specific override class for Arch Linux """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import configurator
@@ -26,6 +24,5 @@ class ArchConfigurator(configurator.ApacheConfigurator):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)

View File

@@ -1,14 +1,12 @@
""" Distribution specific override class for CentOS family (RHEL, Fedora) """
import logging
import pkg_resources
import zope.interface
from acme.magic_typing import List
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import os
from certbot.errors import MisconfigurationError
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
@@ -37,8 +35,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)
def config_test(self):

View File

@@ -1,9 +1,7 @@
""" Distribution specific override class for macOS """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import configurator
@@ -26,6 +24,5 @@ class DarwinConfigurator(configurator.ApacheConfigurator):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/other",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)

View File

@@ -1,7 +1,6 @@
""" Distribution specific override class for Debian family (Ubuntu/Debian) """
import logging
import pkg_resources
import zope.interface
from certbot import errors
@@ -34,8 +33,7 @@ class DebianConfigurator(configurator.ApacheConfigurator):
handle_modules=True,
handle_sites=True,
challenge_location="/etc/apache2",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)
def enable_site(self, vhost):

View File

@@ -1,11 +1,9 @@
""" Distribution specific override class for Fedora 29+ """
import pkg_resources
import zope.interface
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
@@ -31,9 +29,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
# TODO: eventually newest version of Fedora will need their own config
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)
def config_test(self):

View File

@@ -1,9 +1,7 @@
""" Distribution specific override class for Gentoo Linux """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
@@ -29,8 +27,7 @@ class GentooConfigurator(configurator.ApacheConfigurator):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)
def _prepare_options(self):

View File

@@ -1,9 +1,7 @@
""" Distribution specific override class for OpenSUSE """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import configurator
@@ -26,6 +24,5 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator):
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
bin=None,
)

View File

@@ -9,7 +9,6 @@ import six
from acme.magic_typing import Dict
from acme.magic_typing import List
from acme.magic_typing import Set
from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
@@ -31,7 +30,7 @@ class ApacheParser(object):
"""
arg_var_interpreter = re.compile(r"\$\{[^ \}]*}")
fnmatch_chars = set(["*", "?", "\\", "[", "]"])
fnmatch_chars = {"*", "?", "\\", "[", "]"}
def __init__(self, root, vhostroot=None, version=(2, 4),
configurator=None):
@@ -52,7 +51,7 @@ class ApacheParser(object):
"version 1.2.0 or higher, please make sure you have you have "
"those installed.")
self.modules = set() # type: Set[str]
self.modules = {} # type: Dict[str, str]
self.parser_paths = {} # type: Dict[str, List[str]]
self.variables = {} # type: Dict[str, str]
@@ -249,14 +248,14 @@ class ApacheParser(object):
def add_mod(self, mod_name):
"""Shortcut for updating parser modules."""
if mod_name + "_module" not in self.modules:
self.modules.add(mod_name + "_module")
self.modules[mod_name + "_module"] = None
if "mod_" + mod_name + ".c" not in self.modules:
self.modules.add("mod_" + mod_name + ".c")
self.modules["mod_" + mod_name + ".c"] = None
def reset_modules(self):
"""Reset the loaded modules list. This is called from cleanup to clear
temporarily loaded modules."""
self.modules = set()
self.modules = {}
self.update_modules()
self.parse_modules()
@@ -267,7 +266,7 @@ class ApacheParser(object):
the iteration issue. Else... parse and enable mods at same time.
"""
mods = set() # type: Set[str]
mods = {} # type: Dict[str, str]
matches = self.find_dir("LoadModule")
iterator = iter(matches)
# Make sure prev_size != cur_size for do: while: iteration
@@ -281,8 +280,8 @@ class ApacheParser(object):
mod_name = self.get_arg(match_name)
mod_filename = self.get_arg(match_filename)
if mod_name and mod_filename:
mods.add(mod_name)
mods.add(os.path.basename(mod_filename)[:-2] + "c")
mods[mod_name] = mod_filename
mods[os.path.basename(mod_filename)[:-2] + "c"] = mod_filename
else:
logger.debug("Could not read LoadModule directive from Augeas path: %s",
match_name[6:])
@@ -621,7 +620,7 @@ class ApacheParser(object):
def exclude_dirs(self, matches):
"""Exclude directives that are not loaded into the configuration."""
filters = [("ifmodule", self.modules), ("ifdefine", self.variables)]
filters = [("ifmodule", self.modules.keys()), ("ifdefine", self.variables)]
valid_matches = []
@@ -662,6 +661,25 @@ class ApacheParser(object):
return True
def standard_path_from_server_root(self, arg):
"""Ensure paths are consistent and absolute
:param str arg: Argument of directive
:returns: Standardized argument path
:rtype: str
"""
# Remove beginning and ending quotes
arg = arg.strip("'\"")
# Standardize the include argument based on server root
if not arg.startswith("/"):
# Normpath will condense ../
arg = os.path.normpath(os.path.join(self.root, arg))
else:
arg = os.path.normpath(arg)
return arg
def _get_include_path(self, arg):
"""Converts an Apache Include directive into Augeas path.
@@ -682,16 +700,7 @@ class ApacheParser(object):
# if matchObj.group() != arg:
# logger.error("Error: Invalid regexp characters in %s", arg)
# return []
# Remove beginning and ending quotes
arg = arg.strip("'\"")
# Standardize the include argument based on server root
if not arg.startswith("/"):
# Normpath will condense ../
arg = os.path.normpath(os.path.join(self.root, arg))
else:
arg = os.path.normpath(arg)
arg = self.standard_path_from_server_root(arg)
# Attempts to add a transform to the file if one does not already exist
if os.path.isdir(arg):
@@ -732,7 +741,7 @@ class ApacheParser(object):
"""
if sys.version_info < (3, 6):
# This strips off final /Z(?ms)
return fnmatch.translate(clean_fn_match)[:-7]
return fnmatch.translate(clean_fn_match)[:-7] # pragma: no cover
# Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z
return fnmatch.translate(clean_fn_match)[4:-3] # pragma: no cover
@@ -936,8 +945,8 @@ def case_i(string):
:param str string: string to make case i regex
"""
return "".join(["[" + c.upper() + c.lower() + "]"
if c.isalpha() else c for c in re.escape(string)])
return "".join("[" + c.upper() + c.lower() + "]"
if c.isalpha() else c for c in re.escape(string))
def get_aug_path(file_path):

View File

@@ -11,7 +11,7 @@ def validate_kwargs(kwargs, required_names):
:param list required_names: List of required parameter names.
"""
validated_kwargs = dict()
validated_kwargs = {}
for name in required_names:
try:
validated_kwargs[name] = kwargs.pop(name)

View File

@@ -0,0 +1,19 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
SSLEngine on
# Intermediate configuration, tweak to your needs
SSLProtocol all -SSLv2 -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
SSLSessionTickets off
SSLOptions +StrictRequire
# Add vhost name to log entries:
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"" vhost_combined
LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_common

View File

@@ -1,3 +1,3 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==1.1.0
-e certbot[dev]

View File

@@ -1,25 +1,35 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.29.0',
'certbot>=1.1.0',
'mock',
'certbot>=1.6.0.dev0',
'python-augeas',
'setuptools',
'zope.component',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
dev_extras = [
'apacheconfig>=0.3.1',
'apacheconfig>=0.3.2',
]
class PyTest(TestCommand):

View File

@@ -1,14 +1,27 @@
"""Tests for AugeasParserNode classes"""
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import os
import util
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import errors
from certbot_apache._internal import assertions
from certbot_apache._internal import augeasparser
def _get_augeasnode_mock(filepath):
""" Helper function for mocking out DualNode instance with an AugeasNode """
def augeasnode_mock(metadata):
return augeasparser.AugeasBlockNode(
name=assertions.PASS,
ancestor=None,
filepath=filepath,
metadata=metadata)
return augeasnode_mock
class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods
"""Test AugeasParserNode using available test configurations"""
@@ -16,8 +29,11 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
def setUp(self): # pylint: disable=arguments-differ
super(AugeasParserNodeTest, self).setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir, use_parsernode=True)
with mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root") as mock_parsernode:
mock_parsernode.side_effect = _get_augeasnode_mock(
os.path.join(self.config_path, "apache2.conf"))
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir, use_parsernode=True)
self.vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
@@ -111,7 +127,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
name=servernames[0].name,
parameters=["test", "setting", "these"],
ancestor=assertions.PASS,
metadata=servernames[0].primary.metadata
metadata=servernames[0].metadata
)
self.assertTrue(mock_set.called)
self.assertEqual(
@@ -146,26 +162,26 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
self.assertTrue(found)
def test_add_child_comment(self):
newc = self.config.parser_root.primary.add_child_comment("The content")
newc = self.config.parser_root.add_child_comment("The content")
comments = self.config.parser_root.find_comments("The content")
self.assertEqual(len(comments), 1)
self.assertEqual(
newc.metadata["augeaspath"],
comments[0].primary.metadata["augeaspath"]
comments[0].metadata["augeaspath"]
)
self.assertEqual(newc.comment, comments[0].comment)
def test_delete_child(self):
listens = self.config.parser_root.primary.find_directives("Listen")
listens = self.config.parser_root.find_directives("Listen")
self.assertEqual(len(listens), 1)
self.config.parser_root.primary.delete_child(listens[0])
self.config.parser_root.delete_child(listens[0])
listens = self.config.parser_root.primary.find_directives("Listen")
listens = self.config.parser_root.find_directives("Listen")
self.assertEqual(len(listens), 0)
def test_delete_child_not_found(self):
listen = self.config.parser_root.find_directives("Listen")[0]
listen.primary.metadata["augeaspath"] = "/files/something/nonexistent"
listen.metadata["augeaspath"] = "/files/something/nonexistent"
self.assertRaises(
errors.PluginError,
@@ -178,10 +194,10 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
"NewBlock",
["first", "second"]
)
rpath, _, directive = nb.primary.metadata["augeaspath"].rpartition("/")
rpath, _, directive = nb.metadata["augeaspath"].rpartition("/")
self.assertEqual(
rpath,
self.config.parser_root.primary.metadata["augeaspath"]
self.config.parser_root.metadata["augeaspath"]
)
self.assertTrue(directive.startswith("NewBlock"))
@@ -190,8 +206,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
"Beginning",
position=0
)
parser = self.config.parser_root.primary.parser
root_path = self.config.parser_root.primary.metadata["augeaspath"]
parser = self.config.parser_root.parser
root_path = self.config.parser_root.metadata["augeaspath"]
# Get first child
first = parser.aug.match("{}/*[1]".format(root_path))
self.assertTrue(first[0].endswith("Beginning"))
@@ -200,8 +216,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
self.config.parser_root.add_child_block(
"VeryLast",
)
parser = self.config.parser_root.primary.parser
root_path = self.config.parser_root.primary.metadata["augeaspath"]
parser = self.config.parser_root.parser
root_path = self.config.parser_root.metadata["augeaspath"]
# Get last child
last = parser.aug.match("{}/*[last()]".format(root_path))
self.assertTrue(last[0].endswith("VeryLast"))
@@ -211,8 +227,8 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
"VeryLastAlt",
position=99999
)
parser = self.config.parser_root.primary.parser
root_path = self.config.parser_root.primary.metadata["augeaspath"]
parser = self.config.parser_root.parser
root_path = self.config.parser_root.metadata["augeaspath"]
# Get last child
last = parser.aug.match("{}/*[last()]".format(root_path))
self.assertTrue(last[0].endswith("VeryLastAlt"))
@@ -222,15 +238,15 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
"Middle",
position=5
)
parser = self.config.parser_root.primary.parser
root_path = self.config.parser_root.primary.metadata["augeaspath"]
parser = self.config.parser_root.parser
root_path = self.config.parser_root.metadata["augeaspath"]
# Augeas indices start at 1 :(
middle = parser.aug.match("{}/*[6]".format(root_path))
self.assertTrue(middle[0].endswith("Middle"))
def test_add_child_block_existing_name(self):
parser = self.config.parser_root.primary.parser
root_path = self.config.parser_root.primary.metadata["augeaspath"]
parser = self.config.parser_root.parser
root_path = self.config.parser_root.metadata["augeaspath"]
# There already exists a single VirtualHost in the base config
new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path))
self.assertEqual(len(new_block), 0)
@@ -239,7 +255,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
)
new_block = parser.aug.match("{}/VirtualHost[2]".format(root_path))
self.assertEqual(len(new_block), 1)
self.assertTrue(vh.primary.metadata["augeaspath"].endswith("VirtualHost[2]"))
self.assertTrue(vh.metadata["augeaspath"].endswith("VirtualHost[2]"))
def test_node_init_error_bad_augeaspath(self):
from certbot_apache._internal.augeasparser import AugeasBlockNode
@@ -284,7 +300,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
self.assertEqual(len(dirs), 1)
self.assertEqual(dirs[0].parameters, ("with", "parameters"))
# The new directive was added to the very first line of the config
self.assertTrue(dirs[0].primary.metadata["augeaspath"].endswith("[1]"))
self.assertTrue(dirs[0].metadata["augeaspath"].endswith("[1]"))
def test_add_child_directive_exception(self):
self.assertRaises(
@@ -314,6 +330,6 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
self.assertTrue(nonmacro_test)
def test_find_ancestors_bad_path(self):
self.config.parser_root.primary.metadata["augeaspath"] = ""
ancs = self.config.parser_root.primary.find_ancestors("Anything")
self.config.parser_root.metadata["augeaspath"] = ""
ancs = self.config.parser_root.find_ancestors("Anything")
self.assertEqual(len(ancs), 0)

View File

@@ -3,7 +3,10 @@
import re
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import six # pylint: disable=unused-import # six is used in mock.patch()
from certbot import errors
@@ -20,10 +23,10 @@ class AutoHSTSTest(util.ApacheTest):
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.config.parser.modules["headers_module"] = None
self.config.parser.modules["mod_headers.c"] = None
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
self.vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
@@ -42,8 +45,8 @@ class AutoHSTSTest(util.ApacheTest):
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache._internal.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.parser.modules.pop("headers_module", None)
self.config.parser.modules.pop("mod_header.c", None)
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
self.assertTrue(mock_enable.called)

View File

@@ -19,12 +19,12 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "test.example.com.conf"),
os.path.join(aug_pre, "test.example.com.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "test.example.com"),
obj.VirtualHost(
os.path.join(prefix, "ssl.conf"),
os.path.join(aug_pre, "ssl.conf/VirtualHost"),
set([obj.Addr.fromstring("_default_:443")]),
{obj.Addr.fromstring("_default_:443")},
True, True, None)
]
return vh_truth
@@ -104,7 +104,7 @@ class CentOS6Tests(util.ApacheTest):
pre_loadmods = self.config.parser.find_dir(
"LoadModule", "ssl_module", exclude=False)
# LoadModules are not within IfModule blocks
self.assertFalse(any(["ifmodule" in m.lower() for m in pre_loadmods]))
self.assertFalse(any("ifmodule" in m.lower() for m in pre_loadmods))
self.config.assoc["test.example.com"] = self.vh_truth[0]
self.config.deploy_cert(
"random.demo", "example/cert.pem", "example/key.pem",

View File

@@ -1,7 +1,10 @@
"""Test for certbot_apache._internal.configurator for Centos overrides"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import filesystem
@@ -21,12 +24,12 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "centos.example.com.conf"),
os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "centos.example.com"),
obj.VirtualHost(
os.path.join(prefix, "ssl.conf"),
os.path.join(aug_pre, "ssl.conf/VirtualHost"),
set([obj.Addr.fromstring("_default_:443")]),
{obj.Addr.fromstring("_default_:443")},
True, True, None)
]
return vh_truth
@@ -126,7 +129,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
return mod_val
return ""
mock_get.side_effect = mock_get_cfg
self.config.parser.modules = set()
self.config.parser.modules = {}
self.config.parser.variables = {}
with mock.patch("certbot.util.get_os_info") as mock_osi:

View File

@@ -2,7 +2,10 @@
import shutil
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
import util

View File

@@ -6,7 +6,10 @@ import socket
import tempfile
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import six # pylint: disable=unused-import # six is used in mock.patch()
from acme import challenges
@@ -99,7 +102,7 @@ class MultipleVhostsTest(util.ApacheTest):
parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext",
"vhost_root", "logs_root", "challenge_location",
"handle_modules", "handle_sites", "ctl"]
exp = dict()
exp = {}
for k in ApacheConfigurator.OS_DEFAULTS:
if k in parserargs:
@@ -140,11 +143,9 @@ class MultipleVhostsTest(util.ApacheTest):
mock_utility = mock_getutility()
mock_utility.notification = mock.MagicMock(return_value=True)
names = self.config.get_all_names()
self.assertEqual(names, set(
["certbot.demo", "ocspvhost.com", "encryption-example.demo",
self.assertEqual(names, {"certbot.demo", "ocspvhost.com", "encryption-example.demo",
"nonsym.link", "vhost.in.rootconf", "www.certbot.demo",
"duplicate.example.com"]
))
"duplicate.example.com"})
@certbot_util.patch_get_utility()
@mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr")
@@ -154,9 +155,9 @@ class MultipleVhostsTest(util.ApacheTest):
mock_utility.notification.return_value = True
vhost = obj.VirtualHost(
"fp", "ap",
set([obj.Addr(("8.8.8.8", "443")),
{obj.Addr(("8.8.8.8", "443")),
obj.Addr(("zombo.com",)),
obj.Addr(("192.168.1.2"))]),
obj.Addr(("192.168.1.2"))},
True, False)
self.config.vhosts.append(vhost)
@@ -185,7 +186,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_bad_servername_alias(self):
ssl_vh1 = obj.VirtualHost(
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
"fp1", "ap1", {obj.Addr(("*", "443"))},
True, False)
# pylint: disable=protected-access
self.config._add_servernames(ssl_vh1)
@@ -198,7 +199,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.config._add_servernames(self.vh_truth[2])
self.assertEqual(
self.vh_truth[2].get_names(), set(["*.le.co", "ip-172-30-0-17"]))
self.vh_truth[2].get_names(), {"*.le.co", "ip-172-30-0-17"})
def test_get_virtual_hosts(self):
"""Make sure all vhosts are being properly found."""
@@ -269,7 +270,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select):
mock_select.return_value = self.vh_truth[3]
conflicting_vhost = obj.VirtualHost(
"path", "aug_path", set([obj.Addr.fromstring("*:443")]),
"path", "aug_path", {obj.Addr.fromstring("*:443")},
True, True)
self.config.vhosts.append(conflicting_vhost)
@@ -278,14 +279,14 @@ class MultipleVhostsTest(util.ApacheTest):
def test_find_best_http_vhost_default(self):
vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr.fromstring("_default_:80")]), False, True)
"fp", "ap", {obj.Addr.fromstring("_default_:80")}, False, True)
self.config.vhosts = [vh]
self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh)
def test_find_best_http_vhost_port(self):
port = "8080"
vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr.fromstring("*:" + port)]),
"fp", "ap", {obj.Addr.fromstring("*:" + port)},
False, True, "encryption-example.demo")
self.config.vhosts.append(vh)
self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh)
@@ -313,8 +314,8 @@ class MultipleVhostsTest(util.ApacheTest):
def test_find_best_vhost_variety(self):
# pylint: disable=protected-access
ssl_vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr(("*", "443")),
obj.Addr(("zombo.com",))]),
"fp", "ap", {obj.Addr(("*", "443")),
obj.Addr(("zombo.com",))},
True, False)
self.config.vhosts.append(ssl_vh)
self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh)
@@ -341,9 +342,9 @@ class MultipleVhostsTest(util.ApacheTest):
def test_deploy_cert_enable_new_vhost(self):
# Create
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
self.assertFalse(ssl_vhost.enabled)
self.config.deploy_cert(
@@ -377,9 +378,9 @@ class MultipleVhostsTest(util.ApacheTest):
# pragma: no cover
def test_deploy_cert(self):
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
# Patch _add_dummy_ssl_directives to make sure we write them correctly
# pylint: disable=protected-access
orig_add_dummy = self.config._add_dummy_ssl_directives
@@ -454,41 +455,6 @@ class MultipleVhostsTest(util.ApacheTest):
"SSLCertificateChainFile", "two/cert_chain.pem",
self.vh_truth[1].path))
def test_deploy_cert_invalid_vhost(self):
"""For test cases where the `ApacheConfigurator` class' `_deploy_cert`
method is called with an invalid vhost parameter. Currently this tests
that a PluginError is appropriately raised when important directives
are missing in an SSL module."""
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
def side_effect(*args):
"""Mocks case where an SSLCertificateFile directive can be found
but an SSLCertificateKeyFile directive is missing."""
if "SSLCertificateFile" in args:
return ["example/cert.pem"]
return []
mock_find_dir = mock.MagicMock(return_value=[])
mock_find_dir.side_effect = side_effect
self.config.parser.find_dir = mock_find_dir
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
self.assertRaises(
errors.PluginError, self.config.deploy_cert, "random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
# Remove side_effect to mock case where both SSLCertificateFile
# and SSLCertificateKeyFile directives are missing
self.config.parser.find_dir.side_effect = None
self.assertRaises(
errors.PluginError, self.config.deploy_cert, "random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
def test_is_name_vhost(self):
addr = obj.Addr.fromstring("*:80")
self.assertTrue(self.config.is_name_vhost(addr))
@@ -544,7 +510,8 @@ class MultipleVhostsTest(util.ApacheTest):
call_found = True
self.assertTrue(call_found)
def test_prepare_server_https(self):
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
def test_prepare_server_https(self, mock_reset):
mock_enable = mock.Mock()
self.config.enable_mod = mock_enable
@@ -570,7 +537,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(mock_add_dir.call_count, 2)
def test_prepare_server_https_named_listen(self):
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
def test_prepare_server_https_named_listen(self, mock_reset):
mock_find = mock.Mock()
mock_find.return_value = ["test1", "test2", "test3"]
mock_get = mock.Mock()
@@ -608,7 +576,8 @@ class MultipleVhostsTest(util.ApacheTest):
# self.config.prepare_server_https("8080", temp=True)
# self.assertEqual(self.listens, 0)
def test_prepare_server_https_needed_listen(self):
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
def test_prepare_server_https_needed_listen(self, mock_reset):
mock_find = mock.Mock()
mock_find.return_value = ["test1", "test2"]
mock_get = mock.Mock()
@@ -624,8 +593,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.prepare_server_https("443")
self.assertEqual(mock_add_dir.call_count, 1)
def test_prepare_server_https_mixed_listen(self):
@mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules")
def test_prepare_server_https_mixed_listen(self, mock_reset):
mock_find = mock.Mock()
mock_find.return_value = ["test1", "test2"]
mock_get = mock.Mock()
@@ -682,7 +651,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(ssl_vhost.path,
"/files" + ssl_vhost.filep + "/IfModule/Virtualhost")
self.assertEqual(len(ssl_vhost.addrs), 1)
self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs)
self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs)
self.assertEqual(ssl_vhost.name, "encryption-example.demo")
self.assertTrue(ssl_vhost.ssl)
self.assertFalse(ssl_vhost.enabled)
@@ -904,10 +873,10 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot_apache._internal.display_ops.select_vhost")
@mock.patch("certbot.util.exe_exists")
def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
mock_exe.return_value = True
ssl_vh1 = obj.VirtualHost(
"fp1", "ap1", set([obj.Addr(("*", "443"))]),
"fp1", "ap1", {obj.Addr(("*", "443"))},
True, False)
ssl_vh1.name = "satoshi.com"
self.config.vhosts.append(ssl_vh1)
@@ -942,8 +911,8 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
def test_ocsp_stapling(self, mock_exe):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
@@ -969,8 +938,8 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
def test_ocsp_stapling_twice(self, mock_exe):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
@@ -997,8 +966,8 @@ class MultipleVhostsTest(util.ApacheTest):
def test_ocsp_unsupported_apache_version(self, mock_exe):
mock_exe.return_value = True
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
self.config.choose_vhost("certbot.demo")
@@ -1008,7 +977,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_get_http_vhost_third_filter(self):
ssl_vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr(("*", "443"))]),
"fp", "ap", {obj.Addr(("*", "443"))},
True, False)
ssl_vh.name = "satoshi.com"
self.config.vhosts.append(ssl_vh)
@@ -1021,8 +990,8 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
def test_http_header_hsts(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("headers_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["headers_module"] = None
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
@@ -1042,9 +1011,9 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(len(hsts_header), 4)
def test_http_header_hsts_twice(self):
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["mod_ssl.c"] = None
# skip the enable mod
self.config.parser.modules.add("headers_module")
self.config.parser.modules["headers_module"] = None
# This will create an ssl vhost for encryption-example.demo
self.config.choose_vhost("encryption-example.demo")
@@ -1060,8 +1029,8 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
def test_http_header_uir(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("headers_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["headers_module"] = None
mock_exe.return_value = True
@@ -1084,9 +1053,9 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(len(uir_header), 4)
def test_http_header_uir_twice(self):
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["mod_ssl.c"] = None
# skip the enable mod
self.config.parser.modules.add("headers_module")
self.config.parser.modules["headers_module"] = None
# This will create an ssl vhost for encryption-example.demo
self.config.choose_vhost("encryption-example.demo")
@@ -1101,7 +1070,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_well_formed_http(self, mock_exe, _):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2))
@@ -1127,7 +1096,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_rewrite_rule_exists(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
self.config.parser.add_dir(
self.vh_truth[3].path, "RewriteRule", ["Unknown"])
@@ -1136,7 +1105,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_rewrite_engine_exists(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
self.config.parser.add_dir(
self.vh_truth[3].path, "RewriteEngine", "on")
@@ -1146,7 +1115,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_with_existing_rewrite(self, mock_exe, _):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
@@ -1180,7 +1149,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
def test_redirect_with_old_https_redirection(self, mock_exe, _):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.parser.update_runtime_variables = mock.Mock()
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
@@ -1209,10 +1178,10 @@ class MultipleVhostsTest(util.ApacheTest):
def test_redirect_with_conflict(self):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
ssl_vh = obj.VirtualHost(
"fp", "ap", set([obj.Addr(("*", "443")),
obj.Addr(("zombo.com",))]),
"fp", "ap", {obj.Addr(("*", "443")),
obj.Addr(("zombo.com",))},
True, False)
# No names ^ this guy should conflict.
@@ -1222,7 +1191,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_redirect_two_domains_one_vhost(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
# Creates ssl vhost for the domain
@@ -1237,7 +1206,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_redirect_from_previous_run(self):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
self.config.choose_vhost("red.blue.purple.com")
self.config.enhance("red.blue.purple.com", "redirect")
@@ -1250,22 +1219,22 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.enhance, "green.blue.purple.com", "redirect")
def test_create_own_redirect(self):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
# For full testing... give names...
self.vh_truth[1].name = "default.com"
self.vh_truth[1].aliases = set(["yes.default.com"])
self.vh_truth[1].aliases = {"yes.default.com"}
# pylint: disable=protected-access
self.config._enable_redirect(self.vh_truth[1], "")
self.assertEqual(len(self.config.vhosts), 13)
def test_create_own_redirect_for_old_apache_version(self):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
self.config.get_version = mock.Mock(return_value=(2, 2))
# For full testing... give names...
self.vh_truth[1].name = "default.com"
self.vh_truth[1].aliases = set(["yes.default.com"])
self.vh_truth[1].aliases = {"yes.default.com"}
# pylint: disable=protected-access
self.config._enable_redirect(self.vh_truth[1], "")
@@ -1326,9 +1295,9 @@ class MultipleVhostsTest(util.ApacheTest):
def test_deploy_cert_not_parsed_path(self):
# Make sure that we add include to root config for vhosts when
# handle-sites is false
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot"))
filesystem.chmod(tmp_path, 0o755)
mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path"
@@ -1345,6 +1314,16 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertTrue(mock_add.called)
shutil.rmtree(tmp_path)
def test_deploy_cert_no_mod_ssl(self):
# Create
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
self.config.parser.modules["socache_shmcb_module"] = None
self.config.prepare_server_https = mock.Mock()
self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert,
"encryption-example.demo", "example/cert.pem", "example/key.pem",
"example/cert_chain.pem", "example/fullchain.pem")
@mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original")
def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed):
ret_vh = self.vh_truth[8]
@@ -1441,8 +1420,8 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard")
def test_enhance_wildcard_after_install(self, mock_choose):
# pylint: disable=protected-access
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("headers_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["headers_module"] = None
self.vh_truth[3].ssl = True
self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]]
self.config.enhance("*.certbot.demo", "ensure-http-header",
@@ -1453,8 +1432,8 @@ class MultipleVhostsTest(util.ApacheTest):
def test_enhance_wildcard_no_install(self, mock_choose):
self.vh_truth[3].ssl = True
mock_choose.return_value = [self.vh_truth[3]]
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("headers_module")
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["headers_module"] = None
self.config.enhance("*.certbot.demo", "ensure-http-header",
"Upgrade-Insecure-Requests")
self.assertTrue(mock_choose.called)
@@ -1606,7 +1585,7 @@ class MultiVhostsTest(util.ApacheTest):
self.assertEqual(ssl_vhost.path,
"/files" + ssl_vhost.filep + "/IfModule/VirtualHost")
self.assertEqual(len(ssl_vhost.addrs), 1)
self.assertEqual(set([obj.Addr.fromstring("*:443")]), ssl_vhost.addrs)
self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs)
self.assertEqual(ssl_vhost.name, "banana.vomit.com")
self.assertTrue(ssl_vhost.ssl)
self.assertFalse(ssl_vhost.enabled)
@@ -1638,7 +1617,7 @@ class MultiVhostsTest(util.ApacheTest):
@certbot_util.patch_get_utility()
def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_get_utility):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4])
@@ -1658,7 +1637,7 @@ class MultiVhostsTest(util.ApacheTest):
@certbot_util.patch_get_utility()
def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_get_utility):
self.config.parser.modules.add("rewrite_module")
self.config.parser.modules["rewrite_module"] = None
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
@@ -1700,7 +1679,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.config.updated_mod_ssl_conf_digest)
def _current_ssl_options_hash(self):
return crypto_util.sha256sum(self.config.option("MOD_SSL_CONF_SRC"))
return crypto_util.sha256sum(self.config.pick_apache_config())
def _assert_current_file(self):
self.assertTrue(os.path.isfile(self.config.mod_ssl_conf))
@@ -1736,7 +1715,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.option("MOD_SSL_CONF_SRC")),
self.config.pick_apache_config()),
self._current_ssl_options_hash())
self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf),
self._current_ssl_options_hash())
@@ -1752,19 +1731,118 @@ 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.option("MOD_SSL_CONF_SRC")),
self.config.pick_apache_config()),
self._current_ssl_options_hash())
# only print warning once
with mock.patch("certbot.plugins.common.logger") as mock_logger:
self._call()
self.assertFalse(mock_logger.warning.called)
def test_current_file_hash_in_all_hashes(self):
def test_ssl_config_files_hash_in_all_hashes(self):
"""
It is really critical that all TLS Apache config files have their SHA256 hash registered in
constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config
file has been manually edited by the user, and will refuse to update it.
This test ensures that all necessary hashes are present.
"""
from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES
self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES,
"Constants.ALL_SSL_OPTIONS_HASHES must be appended"
" with the sha256 hash of self.config.mod_ssl_conf when it is updated.")
import pkg_resources
tls_configs_dir = pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "tls_configs"))
all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir)
if name.endswith('options-ssl-apache.conf')]
self.assertTrue(all_files)
for one_file in all_files:
file_hash = crypto_util.sha256sum(one_file)
self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES,
"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 "
"hash of {0} when it is updated.".format(one_file))
def test_openssl_version(self):
self.config._openssl_version = None
some_string_contents = b"""
SSLOpenSSLConfCmd
OpenSSL configuration command
SSLv3 not supported by this version of OpenSSL
'%s': invalid OpenSSL configuration command
OpenSSL 1.0.2g 1 Mar 2016
OpenSSL
AH02407: "SSLOpenSSLConfCmd %s %s" failed for %s
AH02556: "SSLOpenSSLConfCmd %s %s" applied to %s
OpenSSL 1.0.2g 1 Mar 2016
"""
# ssl_module as a DSO
self.config.parser.modules['ssl_module'] = '/fake/path'
with mock.patch("certbot_apache._internal.configurator."
"ApacheConfigurator._open_module_file") as mock_omf:
mock_omf.return_value = some_string_contents
self.assertEqual(self.config.openssl_version(), "1.0.2g")
# ssl_module statically linked
self.config._openssl_version = None
self.config.parser.modules['ssl_module'] = None
self.config.options['bin'] = '/fake/path/to/httpd'
with mock.patch("certbot_apache._internal.configurator."
"ApacheConfigurator._open_module_file") as mock_omf:
mock_omf.return_value = some_string_contents
self.assertEqual(self.config.openssl_version(), "1.0.2g")
def test_current_version(self):
self.config.version = (2, 4, 10)
self.config._openssl_version = '1.0.2m'
self.assertTrue('old' in self.config.pick_apache_config())
self.config.version = (2, 4, 11)
self.config._openssl_version = '1.0.2m'
self.assertTrue('current' in self.config.pick_apache_config())
self.config._openssl_version = '1.0.2a'
self.assertTrue('old' in self.config.pick_apache_config())
def test_openssl_version_warns(self):
self.config._openssl_version = '1.0.2a'
self.assertEqual(self.config.openssl_version(), '1.0.2a')
self.config._openssl_version = None
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
self.assertEqual(self.config.openssl_version(), None)
self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0])
# When no ssl_module is present at all
self.config._openssl_version = None
self.assertTrue("ssl_module" not in self.config.parser.modules)
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
self.assertEqual(self.config.openssl_version(), None)
self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0])
# When ssl_module is statically linked but --apache-bin not provided
self.config._openssl_version = None
self.config.options['bin'] = None
self.config.parser.modules['ssl_module'] = None
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
self.assertEqual(self.config.openssl_version(), None)
self.assertTrue("ssl_module is statically linked but" in mock_log.call_args[0][0])
self.config.parser.modules['ssl_module'] = "/fake/path"
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
# Check that correct logger.warning was printed
self.assertEqual(self.config.openssl_version(), None)
self.assertTrue("Unable to read" in mock_log.call_args[0][0])
contents_missing_openssl = b"these contents won't match the regex"
with mock.patch("certbot_apache._internal.configurator."
"ApacheConfigurator._open_module_file") as mock_omf:
mock_omf.return_value = contents_missing_openssl
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
# Check that correct logger.warning was printed
self.assertEqual(self.config.openssl_version(), None)
self.assertTrue("Could not find OpenSSL" in mock_log.call_args[0][0])
def test_open_module_file(self):
mock_open = mock.mock_open(read_data="testing 12 3")
with mock.patch("six.moves.builtins.open", mock_open):
self.assertEqual(self.config._open_module_file("/nonsense/"), "testing 12 3")
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -2,7 +2,10 @@
import shutil
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import os
@@ -61,8 +64,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
def test_deploy_cert_enable_new_vhost(self):
# Create
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
self.assertFalse(ssl_vhost.enabled)
self.config.deploy_cert(
"encryption-example.demo", "example/cert.pem", "example/key.pem",
@@ -92,8 +95,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
self.config_path, self.vhost_path, self.config_dir,
self.work_dir, version=(2, 4, 16))
self.config = self.mock_deploy_cert(self.config)
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
@@ -128,8 +131,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
self.config_path, self.vhost_path, self.config_dir,
self.work_dir, version=(2, 4, 16))
self.config = self.mock_deploy_cert(self.config)
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
@@ -143,8 +146,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
self.config_path, self.vhost_path, self.config_dir,
self.work_dir, version=(2, 4, 7))
self.config = self.mock_deploy_cert(self.config)
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
@@ -157,7 +160,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
def test_ocsp_stapling_enable_mod(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["mod_ssl.c"] = None
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
@@ -169,7 +172,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
@mock.patch("certbot.util.exe_exists")
def test_ensure_http_header_enable_mod(self, mock_exe, _):
self.config.parser.update_runtime_variables = mock.Mock()
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules["mod_ssl.c"] = None
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo

View File

@@ -1,7 +1,10 @@
"""Test certbot_apache._internal.display_ops."""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.display import util as display_util
@@ -93,9 +96,9 @@ class SelectVhostTest(unittest.TestCase):
self.vhosts.append(
obj.VirtualHost(
"path", "aug_path", set([obj.Addr.fromstring("*:80")]),
"path", "aug_path", {obj.Addr.fromstring("*:80")},
False, False,
"wildcard.com", set(["*.wildcard.com"])))
"wildcard.com", {"*.wildcard.com"}))
self.assertEqual(self.vhosts[5], self._call(self.vhosts))

View File

@@ -1,7 +1,10 @@
"""Tests for DualParserNode implementation"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot_apache._internal import assertions
from certbot_apache._internal import augeasparser

View File

@@ -1,7 +1,10 @@
"""Test for certbot_apache._internal.entrypoint for override class resolution"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot_apache._internal import configurator
from certbot_apache._internal import entrypoint

View File

@@ -1,7 +1,10 @@
"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import filesystem
@@ -120,7 +123,7 @@ class MultipleVhostsTestFedora(util.ApacheTest):
return mod_val
return ""
mock_get.side_effect = mock_get_cfg
self.config.parser.modules = set()
self.config.parser.modules = {}
self.config.parser.variables = {}
with mock.patch("certbot.util.get_os_info") as mock_osi:

View File

@@ -1,7 +1,10 @@
"""Test for certbot_apache._internal.configurator for Gentoo overrides"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import filesystem
@@ -21,19 +24,19 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "gentoo.example.com.conf"),
os.path.join(aug_pre, "gentoo.example.com.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "gentoo.example.com"),
obj.VirtualHost(
os.path.join(prefix, "00_default_vhost.conf"),
os.path.join(aug_pre, "00_default_vhost.conf/IfDefine/VirtualHost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "localhost"),
obj.VirtualHost(
os.path.join(prefix, "00_default_ssl_vhost.conf"),
os.path.join(aug_pre,
"00_default_ssl_vhost.conf" +
"/IfDefine/IfDefine/IfModule/VirtualHost"),
set([obj.Addr.fromstring("_default_:443")]),
{obj.Addr.fromstring("_default_:443")},
True, True, "localhost")
]
return vh_truth
@@ -117,7 +120,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
return mod_val
return None # pragma: no cover
mock_get.side_effect = mock_get_cfg
self.config.parser.modules = set()
self.config.parser.modules = {}
with mock.patch("certbot.util.get_os_info") as mock_osi:
# Make sure we have the have the Gentoo httpd constants

View File

@@ -1,10 +1,13 @@
"""Test for certbot_apache._internal.http_01."""
import unittest
import errno
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
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.compat import filesystem
@@ -40,8 +43,8 @@ class ApacheHttp01Test(util.ApacheTest):
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")
self.config.parser.modules["mod_{0}.c".format(mod)] = None
self.config.parser.modules[mod + "_module"] = None
from certbot_apache._internal.http_01 import ApacheHttp01
self.http = ApacheHttp01(self.config)
@@ -52,24 +55,24 @@ class ApacheHttp01Test(util.ApacheTest):
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
def test_enable_modules_apache_2_2(self, mock_enmod):
self.config.version = (2, 2)
self.config.parser.modules.remove("authz_host_module")
self.config.parser.modules.remove("mod_authz_host.c")
del self.config.parser.modules["authz_host_module"]
del self.config.parser.modules["mod_authz_host.c"]
enmod_calls = self.common_enable_modules_test(mock_enmod)
self.assertEqual(enmod_calls[0][0][0], "authz_host")
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
def test_enable_modules_apache_2_4(self, mock_enmod):
self.config.parser.modules.remove("authz_core_module")
self.config.parser.modules.remove("mod_authz_core.c")
del self.config.parser.modules["authz_core_module"]
del self.config.parser.modules["mod_authz_host.c"]
enmod_calls = self.common_enable_modules_test(mock_enmod)
self.assertEqual(enmod_calls[0][0][0], "authz_core")
def common_enable_modules_test(self, mock_enmod):
"""Tests enabling mod_rewrite and other modules."""
self.config.parser.modules.remove("rewrite_module")
self.config.parser.modules.remove("mod_rewrite.c")
del self.config.parser.modules["rewrite_module"]
del self.config.parser.modules["mod_rewrite.c"]
self.http.prepare_http01_modules()
@@ -197,6 +200,12 @@ class ApacheHttp01Test(util.ApacheTest):
self.assertTrue(os.path.exists(challenge_dir))
@mock.patch("certbot_apache._internal.http_01.filesystem.makedirs")
def test_failed_makedirs(self, mock_makedirs):
mock_makedirs.side_effect = OSError(errno.EACCES, "msg")
self.http.add_chall(self.achalls[0])
self.assertRaises(errors.PluginError, self.http.perform)
def _test_challenge_conf(self):
with open(self.http.challenge_conf_pre) as f:
pre_conf_contents = f.read()

View File

@@ -14,13 +14,13 @@ class VirtualHostTest(unittest.TestCase):
self.addr_default = Addr.fromstring("_default_:443")
self.vhost1 = VirtualHost(
"filep", "vh_path", set([self.addr1]), False, False, "localhost")
"filep", "vh_path", {self.addr1}, False, False, "localhost")
self.vhost1b = VirtualHost(
"filep", "vh_path", set([self.addr1]), False, False, "localhost")
"filep", "vh_path", {self.addr1}, False, False, "localhost")
self.vhost2 = VirtualHost(
"fp", "vhp", set([self.addr2]), False, False, "localhost")
"fp", "vhp", {self.addr2}, False, False, "localhost")
def test_repr(self):
self.assertEqual(repr(self.addr2),
@@ -42,7 +42,7 @@ class VirtualHostTest(unittest.TestCase):
complex_vh = VirtualHost(
"fp", "vhp",
set([Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")]),
{Addr.fromstring("*:443"), Addr.fromstring("1.2.3.4:443")},
False, False)
self.assertTrue(complex_vh.conflicts([self.addr1]))
self.assertTrue(complex_vh.conflicts([self.addr2]))
@@ -57,14 +57,14 @@ class VirtualHostTest(unittest.TestCase):
def test_same_server(self):
from certbot_apache._internal.obj import VirtualHost
no_name1 = VirtualHost(
"fp", "vhp", set([self.addr1]), False, False, None)
"fp", "vhp", {self.addr1}, False, False, None)
no_name2 = VirtualHost(
"fp", "vhp", set([self.addr2]), False, False, None)
"fp", "vhp", {self.addr2}, False, False, None)
no_name3 = VirtualHost(
"fp", "vhp", set([self.addr_default]),
"fp", "vhp", {self.addr_default},
False, False, None)
no_name4 = VirtualHost(
"fp", "vhp", set([self.addr2, self.addr_default]),
"fp", "vhp", {self.addr2, self.addr_default},
False, False, None)
self.assertTrue(self.vhost1.same_server(self.vhost2))

View File

@@ -2,7 +2,10 @@
import shutil
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import os
@@ -114,7 +117,7 @@ class BasicParserTest(util.ParserTest):
"""
from certbot_apache._internal.parser import get_aug_path
# This makes sure that find_dir will work
self.parser.modules.add("mod_ssl.c")
self.parser.modules["mod_ssl.c"] = "/fake/path"
self.parser.add_dir_to_ifmodssl(
get_aug_path(self.parser.loc["default"]),
@@ -128,7 +131,7 @@ class BasicParserTest(util.ParserTest):
def test_add_dir_to_ifmodssl_multiple(self):
from certbot_apache._internal.parser import get_aug_path
# This makes sure that find_dir will work
self.parser.modules.add("mod_ssl.c")
self.parser.modules["mod_ssl.c"] = "/fake/path"
self.parser.add_dir_to_ifmodssl(
get_aug_path(self.parser.loc["default"]),
@@ -260,7 +263,7 @@ class BasicParserTest(util.ParserTest):
expected_vars = {"TEST": "", "U_MICH": "", "TLS": "443",
"example_path": "Documents/path"}
self.parser.modules = set()
self.parser.modules = {}
with mock.patch(
"certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse:
self.parser.update_runtime_variables()
@@ -282,7 +285,7 @@ class BasicParserTest(util.ParserTest):
os.path.dirname(self.parser.loc["root"]))
mock_cfg.return_value = inc_val
self.parser.modules = set()
self.parser.modules = {}
with mock.patch(
"certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse:

View File

@@ -1,11 +1,21 @@
"""Tests for ApacheConfigurator for AugeasParserNode classes"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import util
try:
import apacheconfig
HAS_APACHECONFIG = True
except ImportError: # pragma: no cover
HAS_APACHECONFIG = False
@unittest.skipIf(not HAS_APACHECONFIG, reason='Tests require apacheconfig dependency')
class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-methods
"""Test AugeasParserNode using available test configurations"""

View File

@@ -5,7 +5,10 @@ import unittest
import augeas
import josepy as jose
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import zope.component
from certbot.compat import os
@@ -85,7 +88,8 @@ def get_apache_configurator(
config_dir, work_dir, version=(2, 4, 7),
os_info="generic",
conf_vhost_path=None,
use_parsernode=False):
use_parsernode=False,
openssl_version="1.1.1a"):
"""Create an Apache Configurator with the specified options.
:param conf: Function that returns binary paths. self.conf in Configurator
@@ -118,7 +122,8 @@ def get_apache_configurator(
except KeyError:
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version, use_parsernode=use_parsernode)
version=version, use_parsernode=use_parsernode,
openssl_version=openssl_version)
if not conf_vhost_path:
config_class.OS_DEFAULTS["vhost_root"] = vhost_path
else:
@@ -140,71 +145,71 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "encryption-example.conf"),
os.path.join(aug_pre, "encryption-example.conf/Virtualhost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "encryption-example.demo"),
obj.VirtualHost(
os.path.join(prefix, "default-ssl.conf"),
os.path.join(aug_pre,
"default-ssl.conf/IfModule/VirtualHost"),
set([obj.Addr.fromstring("_default_:443")]), True, True),
{obj.Addr.fromstring("_default_:443")}, True, True),
obj.VirtualHost(
os.path.join(prefix, "000-default.conf"),
os.path.join(aug_pre, "000-default.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80"),
obj.Addr.fromstring("[::]:80")]),
{obj.Addr.fromstring("*:80"),
obj.Addr.fromstring("[::]:80")},
False, True, "ip-172-30-0-17"),
obj.VirtualHost(
os.path.join(prefix, "certbot.conf"),
os.path.join(aug_pre, "certbot.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), False, True,
{obj.Addr.fromstring("*:80")}, False, True,
"certbot.demo", aliases=["www.certbot.demo"]),
obj.VirtualHost(
os.path.join(prefix, "mod_macro-example.conf"),
os.path.join(aug_pre,
"mod_macro-example.conf/Macro/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), False, True,
{obj.Addr.fromstring("*:80")}, False, True,
modmacro=True),
obj.VirtualHost(
os.path.join(prefix, "default-ssl-port-only.conf"),
os.path.join(aug_pre, ("default-ssl-port-only.conf/"
"IfModule/VirtualHost")),
set([obj.Addr.fromstring("_default_:443")]), True, True),
{obj.Addr.fromstring("_default_:443")}, True, True),
obj.VirtualHost(
os.path.join(prefix, "wildcard.conf"),
os.path.join(aug_pre, "wildcard.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), False, True,
{obj.Addr.fromstring("*:80")}, False, True,
"ip-172-30-0-17", aliases=["*.blue.purple.com"]),
obj.VirtualHost(
os.path.join(prefix, "ocsp-ssl.conf"),
os.path.join(aug_pre, "ocsp-ssl.conf/IfModule/VirtualHost"),
set([obj.Addr.fromstring("10.2.3.4:443")]), True, True,
{obj.Addr.fromstring("10.2.3.4:443")}, True, True,
"ocspvhost.com"),
obj.VirtualHost(
os.path.join(prefix, "non-symlink.conf"),
os.path.join(aug_pre, "non-symlink.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), False, True,
{obj.Addr.fromstring("*:80")}, False, True,
"nonsym.link"),
obj.VirtualHost(
os.path.join(prefix, "default-ssl-port-only.conf"),
os.path.join(aug_pre,
"default-ssl-port-only.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), True, True, ""),
{obj.Addr.fromstring("*:80")}, True, True, ""),
obj.VirtualHost(
os.path.join(temp_dir, config_name,
"apache2/apache2.conf"),
"/files" + os.path.join(temp_dir, config_name,
"apache2/apache2.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]), False, True,
{obj.Addr.fromstring("*:80")}, False, True,
"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,
{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,
{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":
@@ -215,27 +220,27 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "default.conf"),
os.path.join(aug_pre, "default.conf/VirtualHost[1]"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "ip-172-30-0-17"),
obj.VirtualHost(
os.path.join(prefix, "default.conf"),
os.path.join(aug_pre, "default.conf/VirtualHost[2]"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "banana.vomit.com"),
obj.VirtualHost(
os.path.join(prefix, "multi-vhost.conf"),
os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[1]"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "1.multi.vhost.tld"),
obj.VirtualHost(
os.path.join(prefix, "multi-vhost.conf"),
os.path.join(aug_pre, "multi-vhost.conf/IfModule/VirtualHost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "2.multi.vhost.tld"),
obj.VirtualHost(
os.path.join(prefix, "multi-vhost.conf"),
os.path.join(aug_pre, "multi-vhost.conf/VirtualHost[2]"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "3.multi.vhost.tld")]
return vh_truth
return None # pragma: no cover

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="1.2.0"
LE_AUTO_VERSION="1.5.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -910,20 +910,11 @@ elif [ -f /etc/manjaro-release ]; then
}
BOOTSTRAP_VERSION="BootstrapArchCommon $BOOTSTRAP_ARCH_COMMON_VERSION"
elif [ -f /etc/gentoo-release ]; then
Bootstrap() {
DeprecationBootstrap "Gentoo" BootstrapGentooCommon
}
BOOTSTRAP_VERSION="BootstrapGentooCommon $BOOTSTRAP_GENTOO_COMMON_VERSION"
DEPRECATED_OS=1
elif uname | grep -iq FreeBSD ; then
Bootstrap() {
DeprecationBootstrap "FreeBSD" BootstrapFreeBsd
}
BOOTSTRAP_VERSION="BootstrapFreeBsd $BOOTSTRAP_FREEBSD_VERSION"
DEPRECATED_OS=1
elif uname | grep -iq Darwin ; then
Bootstrap() {
DeprecationBootstrap "macOS" BootstrapMac
}
BOOTSTRAP_VERSION="BootstrapMac $BOOTSTRAP_MAC_VERSION"
DEPRECATED_OS=1
elif [ -f /etc/issue ] && grep -iq "Amazon Linux" /etc/issue ; then
Bootstrap() {
ExperimentalBootstrap "Amazon Linux" BootstrapRpmCommon
@@ -1343,7 +1334,9 @@ cryptography==2.8 \
distro==1.4.0 \
--hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \
--hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4
enum34==1.1.6 \
# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid
# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456.
enum34==1.1.6 ; python_version < '3.4' \
--hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \
--hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \
--hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \
@@ -1540,18 +1533,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==1.2.0 \
--hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \
--hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c
acme==1.2.0 \
--hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \
--hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab
certbot-apache==1.2.0 \
--hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \
--hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3
certbot-nginx==1.2.0 \
--hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \
--hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db
certbot==1.5.0 \
--hash=sha256:ec1f01af06b52a6f079f5b02cb70e88f0671a7b13ecb3e45b040563e32c6e53a \
--hash=sha256:c52017a4f84137e1312c898d6ae69c5f7977d79d2bd4c2df013cbbf39b6539bf
acme==1.5.0 \
--hash=sha256:66de67b394bb7606f97f2c21507e6eb6a88936db2a940f5c4893025f87e3852a \
--hash=sha256:b051ff7dd3935b2032c2f8c8386e905d9b658eba9f3455e352650d85bea9c8f0
certbot-apache==1.5.0 \
--hash=sha256:d2c28be6dcd6c56a8040c8c733e72c1341238b1b47fb59f544eb832b9d5c81ba \
--hash=sha256:3eec5a49ae4fcf86213f962eb1e11d8a725b65e7dcee18f9b92c7aa73f821764
certbot-nginx==1.5.0 \
--hash=sha256:3d27fd02ebe15b07ce5fa9525ceeda82aa5fdc45aa064729434faff0442d1f91 \
--hash=sha256:b38f101588af6d2b8ea7c2e3334f249afbe14461a85add2f1420091d860df983
UNLIKELY_EOF
# -------------------------------------------------------------------------

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env python
from __future__ import print_function
import os
import sys
@@ -7,5 +8,4 @@ if hook_script_type == 'deploy' and ('RENEWED_DOMAINS' not in os.environ or 'REN
sys.stderr.write('Environment variables not properly set!\n')
sys.exit(1)
with open(sys.argv[2], 'a') as file_h:
file_h.write(hook_script_type + '\n')
print(hook_script_type)

View File

@@ -1,4 +1,5 @@
"""This module contains advanced assertions for the certbot integration tests."""
import io
import os
try:
@@ -21,7 +22,8 @@ def assert_hook_execution(probe_path, probe_content):
:param probe_path: path to the file that received the hook output
:param probe_content: content expected when the hook is executed
"""
with open(probe_path, 'r') as file:
encoding = 'utf-8' if POSIX_MODE else 'utf-16'
with io.open(probe_path, 'rt', encoding=encoding) as file:
data = file.read()
lines = [line.strip() for line in data.splitlines()]

View File

@@ -1,5 +1,4 @@
"""Module to handle the context of integration tests."""
import logging
import os
import shutil
import sys

View File

@@ -595,6 +595,23 @@ def test_ocsp_status_live(context):
assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
def test_ocsp_renew(context):
"""Test that revoked certificates are renewed."""
# Obtain a certificate
certname = context.get_domain('ocsp-renew')
context.certbot(['--domains', certname])
# Test that "certbot renew" does not renew the certificate
assert_cert_count_for_lineage(context.config_dir, certname, 1)
context.certbot(['renew'], force_renew=False)
assert_cert_count_for_lineage(context.config_dir, certname, 1)
# Revoke the certificate and test that it does renew the certificate
context.certbot(['revoke', '--cert-name', certname, '--no-delete-after-revoke'])
context.certbot(['renew'], force_renew=False)
assert_cert_count_for_lineage(context.config_dir, certname, 2)
def test_dry_run_deactivate_authzs(context):
"""Test that Certbot deactivates authorizations when performing a dry run"""

View File

@@ -38,7 +38,7 @@ class ACMEServer(object):
:param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble)
:param list nodes: list of node names that will be setup by pytest xdist
:param bool http_proxy: if False do not start the HTTP proxy
:param bool stdout: if True stream subprocesses stdout to standard stdout
:param bool stdout: if True stream all subprocesses stdout to standard stdout
"""
self._construct_acme_xdist(acme_server, nodes)
@@ -86,7 +86,8 @@ class ACMEServer(object):
'alpine', 'rm', '-rf', '/workspace/boulder'])
process.wait()
finally:
shutil.rmtree(self._workspace)
if os.path.exists(self._workspace):
shutil.rmtree(self._workspace)
if self._stdout != sys.stdout:
self._stdout.close()
print('=> Test infrastructure stopped and cleaned up.')
@@ -131,7 +132,7 @@ class ACMEServer(object):
environ['PEBBLE_AUTHZREUSE'] = '100'
self._launch_process(
[pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053'],
[pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053', '-strict'],
env=environ)
self._launch_process(
@@ -165,17 +166,24 @@ class ACMEServer(object):
os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'),
join(instance_path, 'test/rate-limit-policies.yml'))
# Launch the Boulder server
self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path)
try:
# Launch the Boulder server
self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path)
# Wait for the ACME CA server to be up.
print('=> Waiting for boulder instance to respond...')
misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=240)
# Wait for the ACME CA server to be up.
print('=> Waiting for boulder instance to respond...')
misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=300)
# Configure challtestsrv to answer any A record request with ip of the docker host.
response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT),
json={'ip': '10.77.77.1'})
response.raise_for_status()
# Configure challtestsrv to answer any A record request with ip of the docker host.
response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT),
json={'ip': '10.77.77.1'})
response.raise_for_status()
except BaseException:
# If we failed to set up boulder, print its logs.
print('=> Boulder setup failed. Boulder logs are:')
process = self._launch_process(['docker-compose', 'logs'], cwd=instance_path, force_stderr=True)
process.wait()
raise
print('=> Finished boulder instance deployment.')
@@ -188,11 +196,12 @@ class ACMEServer(object):
self._launch_process(command)
print('=> Finished configuring the HTTP proxy.')
def _launch_process(self, command, cwd=os.getcwd(), env=None):
def _launch_process(self, command, cwd=os.getcwd(), env=None, force_stderr=False):
"""Launch silently a subprocess OS command"""
if not env:
env = os.environ
process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env)
stdout = sys.stderr if force_stderr else self._stdout
process = subprocess.Popen(command, stdout=stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env)
self._processes.append(process)
return process

View File

@@ -140,13 +140,12 @@ def generate_test_file_hooks(config_dir, hook_probe):
entrypoint_script = '''\
#!/usr/bin/env bash
set -e
"{0}" "{1}" "{2}" "{3}"
"{0}" "{1}" "{2}" >> "{3}"
'''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe)
else:
entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.bat')
entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.ps1')
entrypoint_script = '''\
@echo off
"{0}" "{1}" "{2}" "{3}"
& "{0}" "{1}" "{2}" >> "{3}"
'''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe)
with open(entrypoint_script_path, 'w') as file_h:
@@ -236,7 +235,7 @@ def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE):
file_h.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
req = crypto.X509Req()
san = ', '.join(['DNS:{0}'.format(item) for item in domains])
san = ', '.join('DNS:{0}'.format(item) for item in domains)
san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8'))
req.add_extensions([san_constraint])

View File

@@ -7,7 +7,7 @@ import requests
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
PEBBLE_VERSION = 'v2.2.1'
PEBBLE_VERSION = 'v2.3.0'
ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets')

View File

@@ -31,7 +31,7 @@ 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_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv -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

View File

@@ -3,7 +3,10 @@ import os
import shutil
import subprocess
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import zope.interface
from certbot import errors as le_errors

View File

@@ -96,7 +96,7 @@ def test_authenticator(plugin, config, temp_dir):
def _create_achalls(plugin):
"""Returns a list of annotated challenges to test on plugin"""
achalls = list()
achalls = []
names = plugin.get_testable_domain_names()
for domain in names:
prefs = plugin.get_chall_pref(domain)

View File

@@ -1,7 +1,10 @@
"""Tests for certbot_compatibility_test.validator."""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
import OpenSSL
import requests

View File

@@ -1,19 +1,29 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
version = '1.3.0.dev0'
version = '1.6.0.dev0'
install_requires = [
'certbot',
'certbot-apache',
'mock',
'six',
'requests',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
if sys.version_info < (2, 7, 9):
# For secure SSL connexion with Python 2.7 (InsecurePlatformWarning)
install_requires.append('ndg-httpsclient')

View File

@@ -21,8 +21,8 @@ Credentials
-----------
Use of this plugin requires a configuration file containing Cloudflare API
credentials, obtained from your Cloudflare
`account page <https://dash.cloudflare.com/profile/api-tokens>`_.
credentials, obtained from your
`Cloudflare dashboard <https://dash.cloudflare.com/?to=/:account/profile/api-tokens>`_.
Previously, Cloudflare's "Global API Key" was used for authentication, however
this key can access the entire Cloudflare API for all domains in your account,
@@ -31,11 +31,8 @@ meaning it could cause a lot of damage if leaked.
Cloudflare's newer API Tokens can be restricted to specific domains and
operations, and are therefore now the recommended authentication option.
However, due to some shortcomings in Cloudflare's implementation of Tokens,
Tokens created for Certbot currently require ``Zone:Zone:Read`` and ``Zone:DNS:Edit``
permissions for **all** zones in your account. While this is not ideal, your Token
will still have fewer permission than the Global key, so it's still worth doing.
Hopefully Cloudflare will improve this in the future.
The Token needed by Certbot requires ``Zone:DNS:Edit`` permissions for only the
zones you need certificates for.
Using Cloudflare Tokens also requires at least version 2.3.1 of the ``cloudflare``
python module. If the version that automatically installed with this plugin is

View File

@@ -14,7 +14,7 @@ from certbot.plugins import dns_common
logger = logging.getLogger(__name__)
ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens'
ACCOUNT_URL = 'https://dash.cloudflare.com/?to=/:account/profile/api-tokens'
@zope.interface.implementer(interfaces.IAuthenticator)
@@ -118,7 +118,7 @@ class _CloudflareClient(object):
code = int(e)
hint = None
if code == 9109:
if code == 1009:
hint = 'Does your API token have "Zone:DNS:Edit" permissions?'
logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e)
@@ -210,11 +210,22 @@ class _CloudflareClient(object):
logger.debug('Found zone_id of %s for %s using name %s', zone_id, domain, zone_name)
return zone_id
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. '
'Please confirm that the domain name has been entered correctly '
'and is already associated with the supplied Cloudflare account.{2}'
.format(domain, zone_name_guesses, ' The error from Cloudflare was:'
' {0} {1}'.format(code, msg) if code is not None else ''))
if msg is not None:
if 'com.cloudflare.api.account.zone.list' in msg:
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: '
'{1}. Please confirm that the domain name has been '
'entered correctly and your Cloudflare Token has access '
'to the domain.'.format(domain, zone_name_guesses))
else:
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: '
'{1}. The error from Cloudflare was: {2} {3}.'
.format(domain, zone_name_guesses, code, msg))
else:
raise errors.PluginError('Unable to determine zone_id for {0} using zone names: '
'{1}. Please confirm that the domain name has been '
'entered correctly and is already associated with the '
'supplied Cloudflare account.'
.format(domain, zone_name_guesses))
def _find_txt_record_id(self, zone_id, record_name, record_content):
"""

View File

@@ -1,10 +1,12 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -12,11 +14,19 @@ install_requires = [
'acme>=0.29.0',
'certbot>=1.1.0',
'cloudflare>=1.5.1',
'mock',
'setuptools',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',

View File

@@ -3,7 +3,10 @@
import unittest
import CloudFlare
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import os
@@ -130,7 +133,7 @@ class CloudflareClientTest(unittest.TestCase):
def test_add_txt_record_error(self):
self.cf.zones.get.return_value = [{'id': self.zone_id}]
self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '')
self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(1009, '', '')
self.assertRaises(
errors.PluginError,
@@ -172,6 +175,12 @@ class CloudflareClientTest(unittest.TestCase):
self.cloudflare_client.add_txt_record,
DOMAIN, self.record_name, self.record_content, self.record_ttl)
self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(0, 'com.cloudflare.api.account.zone.list', '')
self.assertRaises(
errors.PluginError,
self.cloudflare_client.add_txt_record,
DOMAIN, self.record_name, self.record_content, self.record_ttl)
def test_del_txt_record(self):
self.cf.zones.get.return_value = [{'id': self.zone_id}]
self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}]

View File

@@ -1,10 +1,12 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -12,11 +14,19 @@ install_requires = [
'acme>=0.31.0',
'certbot>=1.1.0',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',

View File

@@ -2,7 +2,10 @@
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from requests.exceptions import HTTPError
from requests.exceptions import RequestException

View File

@@ -1,23 +1,33 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.29.0',
'certbot>=1.1.0',
'mock',
'python-digitalocean>=1.11',
'setuptools',
'six',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',

View File

@@ -3,7 +3,10 @@
import unittest
import digitalocean
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import os

View File

@@ -1,22 +1,32 @@
from distutils.version import StrictVersion
import os
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.31.0',
'certbot>=1.1.0',
'mock',
'setuptools',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
# This package normally depends on dns-lexicon>=3.2.1 to address the
# problem described in https://github.com/AnalogJ/lexicon/issues/387,
# however, the fix there has been backported to older versions of

View File

@@ -2,7 +2,10 @@
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from requests.exceptions import HTTPError
from certbot.compat import os

View File

@@ -1,10 +1,12 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -12,11 +14,19 @@ install_requires = [
'acme>=0.31.0',
'certbot>=1.1.0',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',

View File

@@ -2,7 +2,10 @@
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from requests.exceptions import HTTPError
from certbot.compat import os

View File

@@ -1,21 +1,31 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'acme>=0.31.0',
'certbot>=1.1.0',
'dns-lexicon>=2.1.22',
'mock',
'setuptools',
'zope.interface',
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',

View File

@@ -2,7 +2,10 @@
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from requests.exceptions import HTTPError
from certbot.compat import os

View File

@@ -1,10 +1,12 @@
from distutils.version import StrictVersion
import sys
from setuptools import __version__ as setuptools_version
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.6.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -12,7 +14,6 @@ install_requires = [
'acme>=0.29.0',
'certbot>=1.1.0',
'google-api-python-client>=1.5.5',
'mock',
'oauth2client>=4.0',
'setuptools',
'zope.interface',
@@ -20,6 +21,15 @@ install_requires = [
'httplib2'
]
setuptools_known_environment_markers = (StrictVersion(setuptools_version) >= StrictVersion('36.2'))
if setuptools_known_environment_markers:
install_requires.append('mock ; python_version < "3.3"')
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
elif sys.version_info < (3,3):
install_requires.append('mock')
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',

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