Compare commits

..

61 Commits

Author SHA1 Message Date
Brad Warren
0306161645 update coverage 2021-05-28 14:28:18 -07:00
Brad Warren
c5cc2f9027 fix leaky test 2021-05-28 11:52:07 -07:00
Brad Warren
1ccb331f6d don't use capture_output 2021-05-27 12:20:47 -07:00
Brad Warren
68b0621b19 rename variable 2021-05-27 11:18:13 -07:00
Brad Warren
53d67d46e0 undouble counts 2021-05-27 11:09:34 -07:00
Brad Warren
34deb46625 capture stdout and stderr separately 2021-05-27 11:07:24 -07:00
Brad Warren
7f3817fe8d fix output count 2021-05-26 14:04:27 -07:00
Brad Warren
fecdb242d0 fix test_revoke_mutual_exclusive_flags 2021-05-26 13:29:31 -07:00
Brad Warren
9d57a4aa55 fix assertion 2021-05-26 13:29:31 -07:00
Brad Warren
d40626f76d ignore external mock warnings 2021-05-26 13:29:31 -07:00
Brad Warren
7df08d106f unpin pytest and update pinnings 2021-05-26 13:29:28 -07:00
alexzorin
39b396763a apache/nginx: certbot>=1.10.0 -> 1.10.1 (#8859)
1.10.0 was a bad release and this breaks our oldest Boulder tests.


I bumped the version to 1.10.0 in #8852 to get access to a new public display_util API, but that was the release with the broken deprecation of `--manual-public-ip-logging-ok`. So let's bump it to 1.10.1.
2021-05-25 08:40:18 -07:00
ohemorange
6f27c32db1 Command-line UX overhaul (#8852)
Streamline and reorganize Certbot's CLI output.

This change is a substantial command-line UX overhaul,
based on previous user research. The main goal was to streamline
and clarify output. To see more verbose output, use the -v or -vv flags.

---

* nginx,apache: CLI logging changes

- Add "Successfully deployed ..." message using display_util
- Remove IReporter usage and replace with display_util
- Standardize "... could not find a VirtualHost ..." error

This changes also bumps the version of certbot required by certbot-nginx
and certbot-apache to take use of the new display_util function.

* fix certbot_compatibility_test

since the http plugins now require IDisplay, we need to inject it

* fix dependency version on certbot

* use better asserts

* try fix oldest deps

because certbot 1.10.0 depends on acme>=1.8.0, we need to use
acme==1.8.0 in the -oldest tests

* cli: redesign output of new certificate reporting

Changes the output of run, certonly and certonly --csr. No longer uses
IReporter.

* cli: redesign output of failed authz reporting

* fix problem sorting to be stable between py2 & 3

* add some catch-all error text

* cli: dont use IReporter for EFF donation prompt

* add per-authenticator hints

* pass achalls to auth_hint, write some tests

* exclude static auth hints from coverage

* dont call auth_hint unless derived from .Plugin

* dns fallback hint: dont assume --dns-blah works

--dns-blah won't work for third-party plugins, they need to be specified
using --authenticator dns-blah.

* add code comments about the auth_hint interface

* renew: don't restart the installer for dry-runs

Prevents Certbot from superfluously invoking the installer restart
during dry-run renewals. (This does not affect authenticator restarts).

Additionally removes some CLI output that was reporting the fullchain
path of the renewed certificate.

* update CHANGELOG.md

* cli: redesign output when cert installation failed

- Display a message when certificate installation begins.
- Don't use IReporter, just log errors immediately if restart/rollback
  fails.
- Prompt the user with a command to retry the installation process once
  they have fixed any underlying problems.

* vary by preconfigured_renewal

and move expiry date to be above the renewal advice

* update code comment

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* update code comment

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* fix lint

* derve cert name from cert_path, if possible

* fix type annotation

* text change in nginx hint

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* print message when restarting server after renewal

* log: print "advice" when exiting with an error

When running in non-quiet mode.

* try fix -oldest lock_test.py

* fix docstring

* s/Restarting/Reloading/ when notifying the user

* fix test name

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* type annotations

* s/using the {} plugin/installer: {}/

* copy: avoid "plugin" where possible

* link to user guide#automated-renewals

when not running with --preconfigured-renewal

* cli: reduce default logging verbosity

* fix lock_test: -vv is needed to see logger.debug

* Change comment in log.py to match the change to default verbosity

* Audit and adjust logging levels in apache module

* Audit and adjust logging levels in nginx module

* Audit, adjust logging levels, and improve logging calls in certbot module

* Fix tests to mock correct methods and classes

* typo in non-preconfigured-renewal message

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* fix test

* revert acme version bump

* catch up to python3 changes

* Revert "revert acme version bump"

This reverts commit fa83d6a51c.

* Change ocsp check error to warning since it's non-fatal

* Update storage_test in parallel with last change

* get rid of leading newline on "Deploying [...]"

* shrink renewal and installation success messages

* print logfile rather than logdir in exit handler

* Decrease logging level to info for idempotent operation where enhancement is already set

* Display cert not yet due for renewal message when renewing and no other action will be taken, and change cert to certificate

* also write to logger so it goes in the log file

* Don't double write to log file; fix main test

* cli: remove trailing newline on new cert reporting

* ignore type error

* revert accidental changes to dependencies

* Pass tests in any timezone by using utcfromtimestamp

* Add changelog entry

* fix nits

* Improve wording of try again message

* minor wording change to changelog

* hooks: send hook stdout to CLI stdout

includes both --manual and --{pre,post,renew} hooks

* update docstrings and remove TODO

* add a pending deprecation on execute_command

* add test coverage for both

* update deprecation text

Co-authored-by: ohemorange <ebportnoy@gmail.com>

Co-authored-by: Alex Zorin <alex@zorin.id.au>
Co-authored-by: alexzorin <alex@zor.io>
2021-05-25 10:47:39 +10:00
Brad Warren
099c6c8b24 remove references to certbot-constraints.txt (#8858) 2021-05-24 14:17:11 -07:00
Brad Warren
315ddb247f Upgrade pylint (#8855)
This is part of https://github.com/certbot/certbot/issues/8782. I took it on now because the currently pinned version of `pylint` doesn't work with newer versions of `poetry` which I wanted to upgrade as part of https://github.com/certbot/certbot/issues/8787.

To say a bit more about the specific changes in this PR:

* Newer versions of `pylint` complain if `Popen` isn't used as a context manager. Instead of making this change, I switched to using `subprocess.run` which is simpler and [recommended in the Python docs](https://docs.python.org/3/library/subprocess.html#using-the-subprocess-module). I also disabled this check in a few places where no longer using `Popen` would require significant refactoring.
* The deleted code in `certbot/certbot/_internal/renewal.py` is cruft since https://github.com/certbot/certbot/pull/8685.
* The unused argument to `enable_mod` in the Apache plugin is used in some over the override classes that subclass that class.

* unpin pylint and repin dependencies

* disable raise-missing-from

* disable wrong-input-order

* remove unused code

* misc lint fixes

* remove unused import

* various lint fixes
2021-05-24 10:02:55 -07:00
alexzorin
2df279bc5b cli: dont double-print choosing plugins error (#8850) 2021-05-17 16:39:04 -07:00
osirisinferi
9e6b406218 Move 5040495 CHANGELOG.md entry to correct version (#8851)
The merge of #8789 left the CHANGELOG.md entry at a previous certbot release. This PR puts the entry at the correct certbot version.
2021-05-17 11:04:05 -07:00
Adrien Ferrand
352ee258b7 [Windows] Cleanup Certbot pkg dir before installing to avoid dependencies conflicts (#8836)
Fixes #8824

This PR makes the installer first delete (if exist) the previous `pkg` directory in the Certbot installation in order to avoid dependencies conflicts when a new version of Certbot (with new versions of dependencies) is intaller other an existing one.

I took the simplest approach here, which is to delete specifically the directories known to create conflicts, instead of more complex approaches that involve to factor in some way the complete uninstaller logic. This is because the complexity added without a clear improvement does not worth it in my opinion. More specifically:
* factorizing in some way the uninstaller section in the NSIS template make the installer use any potential new logic of a new installation of Certbot instead of the one applying for the current installation, and may create unexpected errors during installation or at runtime
* calling the existing `uninstaller.exe` would be better, but I could not find a proper way to let NSIS wait for the actual end of the uninstall logic, and again may create unexpected errors during installation or at runtime

* Cleanup Certbot pkg dir before installing to avoid dependencies conflicts

* Add a changelog

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-05-17 10:22:47 -07:00
osirisinferi
5040495741 Use UTF-8 for renewal configuration file encoding (#8789) 2021-05-16 15:17:41 +02:00
Thomas G
bc23e07ee5 Fix incompatibility with lexicon >= v3.6.0 (#8819) 2021-05-16 15:03:53 +02:00
Mads Jensen
466e437a20 Use new GitHub templates. Add funding link (#8845) 2021-05-14 11:43:58 -07:00
Brad Warren
ee3b3656ea Remove old apache tests (#8843)
Apache test farm tests started failing last night due to a change in pyenv. See https://dev.azure.com/certbot/certbot/_build/results?buildId=3948&view=logs&j=f67c2a39-2c4f-5190-915f-6f32a7a4306f&t=96f0f394-f513-5158-f5e7-a26e55aeadbf&l=26943.

I managed to fix that in d94f20f8b7, however, the OSes the tests were failing on were Debian 9 and Ubuntu 16.04. [Debian 9 reached its end-of-life in July 2020](https://wiki.debian.org/DebianReleases) and [Ubuntu 16.04 reached its end of standard support in April 2021](https://wiki.ubuntu.com/Releases). As shown at the same links, Debian 9 still has support from the LTS team and Ubuntu 16.04 has ESM support. Do we still want to support either of these OSes?

If so, we can use the commit I linked in the first sentence of the last paragraph, but I think supporting the OSes through their standard support is good enough. The Certbot team has enough on their plate and especially when the OSes are so old that we can't even use their packaged version of Python anymore which complicates our tests, I think we can just drop support and move on.

I don't have a strong opinion here though so if someone else does, let me know what you'd like to see or make the PR yourself based on the changes in my linked commit and I'll merge it.

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

* Remove apache tests on old OSes

* remove unused pyenv code
2021-05-14 11:27:47 -07:00
miigotu
db40974788 Add 3rd party certbot-dns-godaddy to the docs (#8844)
* Add 3rd party certbot-dns-godaddy to the docs

* fix up rst syntax for godaddy link

Co-authored-by: alexzorin <alex@zor.io>
2021-05-13 09:22:31 +10:00
Brad Warren
89396cefa2 Remove unnecessary release script output (#8820) 2021-05-11 15:42:52 -07:00
alexzorin
c48adc5753 docker: delete CARGO_HOME (#8839) 2021-05-11 01:03:35 +02:00
Brad Warren
c788820f5d Fix sphinx followup (#8841)
I think we should use our `pip_install*` scripts wherever we can and I'm not quite sure yet if I'd call `repoze.sphinx.autointerface` unmaintained.

* use pip_install_editable

* update sphinx comment
2021-05-10 14:32:37 -07:00
Adrien Ferrand
b0552e1939 Fix Sphinx builds (#8838)
Since Saturday the CI pipeline is failing due to several Sphinx errors. See https://dev.azure.com/certbot/certbot/_build/results?buildId=3928&view=logs&j=d74e04fe-9740-597d-e9fa-1d0400037dfd&t=dde413a4-f24c-59a0-9684-e33d79f9aa02

First, the build of certbot-dns-google is failing because of a particular configuration. It seems that this configuration has been written here to activate the support of the RST instruction `.. code-block:: json` in documentation. However, it does not seem to be necessary for a similar situation in certbot-dns-route53 documentation. So let's try to remove it and fix the Sphinx builds.

Second, Sphinx builds were not pinning dependencies, so Sphinx 4.x (that has been released yesterday) started to be used in the pipeline. Sadly this new version is not compatible with the plugin `repoze.sphinx.autointerface`, used to extract documentation from `zope.interface`. So I fixed the pinning and also explicitly pin Sphinx to 3.5.x for now.

Technically speaking the second action is sufficient to fix the first error, but I keep the dedicated solution because it improves the documentation in my opinion.

This situation could be fixed by not requiring `repoze.sphinx.autointerface`, but this is possible only if we remove `zope.interface` from Certbot. Luckily I started the work few days ago ;).

* Remove explicit lexer call in certbot-dns-google doc builds.

* Write a valid JSON file in the documentation

* Apply constraints to sphinx build environments

* Pin Sphinx to 3.5.4

* Update dependencies

* Pin traitlets
2021-05-10 12:11:31 -07:00
Brad Warren
7eae058af5 Remove OS instructions (#8833)
Fixes https://github.com/certbot/certbot/issues/8832.

[These instructions are creating confusion among users](https://github.com/certbot/certbot/issues/8832) and [frustration among packagers](https://pagure.io/fesco/issue/2570) for whom the warning at the top of the OS packaging section doesn't apply. Because of this, I think we should remove them in favor of our instruction generator and snap/docker/pip instructions.

I also told Fedora packagers that we could probably do this in response to them continuing to improve their Certbot packages which they've done through things like the renewal timer that is now enabled by default.
2021-05-07 13:10:02 -07:00
Brad Warren
934de48d44 fix typo (#8828) 2021-05-05 15:49:06 -07:00
Brad Warren
e39c7b5233 Merge pull request #8827 from certbot/candidate-1.15.0
Update files from 1.15.0 release
2021-05-05 15:48:43 -07:00
Brad Warren
56c781aec4 Bump version to 1.16.0 2021-05-04 11:50:12 -07:00
Brad Warren
484309ed95 Add contents to certbot/CHANGELOG.md for next version 2021-05-04 11:50:12 -07:00
Brad Warren
67e3c54744 Release 1.15.0 2021-05-04 11:50:10 -07:00
Brad Warren
bb6a076fda Update changelog for 1.15.0 release 2021-05-04 11:48:09 -07:00
Brad Warren
dd0e590de3 Make a test farm tests package (#8821)
Fixes https://github.com/certbot/certbot/issues/8781.

This PR makes our test farm tests into a normal package so it and its dependencies can be tracked and installed like our other packages.

Other noteworthy changes in this PR:

* Rather than continuing to place logs in your CWD, they're placed in a temporary directory that is printed to the terminal.
*  `tests/letstest/auto_targets.yaml` was deleted rather than renamed because the file is no longer used.

* make a letstest package

* remove deleted deps

* fix letstest install

* add __init__.py

* call main

* Explicitly mention activating venv

* rerename file

* fix version.py path

* clarify "this"

* Use >= instead of caret requirement
2021-05-03 17:42:30 -07:00
Brad Warren
d3d9a05826 fix client email address (#8817)
client-dev@letsencrypt.org is no longer used by the Certbot team so this PR updates the email address in our packages to our current mailing list.
2021-05-03 12:38:54 -07:00
Mads Jensen
2cf1775864 Update assertTrue/False to Python 3 precise asserts (#8792)
* Update assertTrue/False to Python 3 precise asserts

* Fix test failures

* Fix test failures

* More replacements

* Update to Python 3 asserts in acme-module

* Fix Windows test failure

* Fix failures

* Fix test failure

* More replacements

* Don't include the semgrep rules

* Fix test failure
2021-04-29 10:45:08 +10:00
ohemorange
f339d23e54 Remove further references to certbot-auto in the repo (#8814)
* Move version.py to tests/letstest since it's used by test_sdists.sh

* Delete unused components of certbot-auto

* Remove test_leauto_upgrades.sh and references to it

* Remove test_letsencrypt_auto_certonly_standalone.sh and references to it

* Remove outstanding references to certbot-auto

* Remove references to letsencrypt-auto

* find certbot in the correct directory

* delete letsencrypt-auto-source line from .isort.cfg since that directory no longer contains any python code

* remove (-auto) from certbot(-auto)

* delete line from test

* Improve style for version.py
2021-04-27 15:27:21 -07:00
Brad Warren
ac3edc2c1d don't ignore kgs (#8811) 2021-04-26 15:47:49 -07:00
ohemorange
ba912018f8 Remove pytest run from release script (#8810)
Fixes #8802.

Also removed the unused `kgs` cruft while I was here, since it's leftover from the [initial release commit](3c08b512c3) and I'm pretty sure we don't use that anymore.
2021-04-26 15:18:05 -07:00
Brad Warren
c06e40dbef Update certbot-auto modification checks (#8805)
* revert changes to letsencrypt-auto-source/le-auto

* update modification tests
2021-04-26 13:50:10 -07:00
ohemorange
32247b3c89 Remove modifications to certbot-auto from the release script (#8797)
Fixes #8707.

* Remove modifications to certbot-auto from the release script

* Update tools/_release.sh

* Delete tools/eff-pubkey.pem

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-04-22 13:37:46 -07:00
alexzorin
e4f5aced1c docs: add certbot-dns-azure third-party plugin (#8796) 2021-04-22 12:38:18 -07:00
Brad Warren
9292666b28 fix ciphers link (#8799) 2021-04-22 08:55:05 +10:00
Brad Warren
fb967fda15 pin cython (#8794) 2021-04-20 12:12:45 -07:00
osirisinferi
4a404e2a4a Expand manual DNS challenge instructions to include mention of propagation time and tool to check this (#8770)
* Expand manual DNS challenge instructions

* Less jargon

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* Less is more

Co-authored-by: ohemorange <ebportnoy@gmail.com>

* Make more clear where to look at Googles Toolbox

* Reshuffle text

* Show verify instructions only on last dns-01 challenge

* Swap domain and value

* Remove '(also)'

* Fix DNS verify message for mixed challenge types

* Add a lengthy comment about why there's a full stop after `{domain}`

* Typo

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2021-04-14 15:36:14 -07:00
Adrien Ferrand
0dbe17bbd4 Define OS options by a dedicated object in Apache configurator (#8778)
In https://github.com/certbot/certbot/pull/8748#discussion_r605457670 we discussed about changing the dict used to set OS options for Apache configurators into a dedicated object.

* Create _OsOptions class to configure the os specific options of the Apache configurators

* Fix tests

* Clean imports

* Fix naming

* Fix compatibility tests

* Rename a class

* Ensure restart_cmd_alt is set for specific OSes.

* Add docstring

* Fix override

* Fix coverage
2021-04-13 11:18:49 -07:00
Brad Warren
e33090f282 Fix homebrew (#8791)
The macOS tests run on this PR would fail without this change.

* brew update

* add link to upstream issue
2021-04-12 13:36:38 -07:00
Brad Warren
06bece36de Ensure that mock is pinned (#8786)
* List mock as a dependency in pyproject.toml
* Add a code comment to help us remember to remove it when we can
* Run pin.sh
2021-04-09 14:34:50 -07:00
Brad Warren
7f9857a81b Use Python 3 style super (#8777)
This is one of the things that newer versions of `pylint` complains about.

* git grep -l super\( | xargs sed -i 's/super([^)]*)/super()/g'

* fix spacing
2021-04-08 13:04:51 -07:00
Brad Warren
459a254aea Improve tools/snap/build_remote.py output (#8780)
I think this PR improves tools/snap/build_remote.py's output in a number of ways such as:

* Logs of snap builds were being deleted because they weren't being copied out of the temporary directory added in https://github.com/certbot/certbot/pull/8719.
* The lock should now always be acquired before printing output when multiple processes are running which helps prevent processes mixing their output with each other.
* Output is never buffered which ensures that repeated calls to `print` from the same process while it holds the output lock is kept together.
* The case where we printed output about the "chroot problem" and stopped retrying the build has been deleted because with the fix in https://github.com/certbot/certbot/pull/8719, we should be able to recover in this case.
* If the build failed for any reason, we dump as much output about the problem as we can. I think most times we won't need to read this output, but I personally prefer it being there in case we want it for some reason. Due to this change, I also simplified `_build_snap` and `_dump_results` a bit since `_build_snap` handles printing logs as needed.

* print more output

* lock when printing output

* clarify purpose of lock

* preserve logfiles

* python better

* consistently flush output

* remove workspaces dict

* rename variable

* remove unused variable

* don't use all which exits early

* fix typo
2021-04-07 14:52:15 -07:00
ohemorange
c21f277248 Merge pull request #8779 from certbot/candidate-1.14.0
Update files from 1.14.0 release
2021-04-06 16:09:19 -07:00
Brad Warren
04a85742c1 Bump version to 1.15.0 2021-04-06 10:24:35 -07:00
Brad Warren
21be290e24 Add contents to certbot/CHANGELOG.md for next version 2021-04-06 10:24:34 -07:00
Brad Warren
bf40b81b5a Release 1.14.0 2021-04-06 10:24:32 -07:00
Brad Warren
1b6e4028dc Update changelog for 1.14.0 release 2021-04-06 10:17:01 -07:00
Brad Warren
f15d10abc8 Update Dockerfile-dev (#8774)
* switch Dockerfile-dev to Ubuntu Focal

* Make apt noninteractive

* add --no-install-recommends
2021-04-05 16:02:14 -07:00
alexzorin
a12d91aef6 fix various fd leaks (#8747)
* fix various fd leaks

* use context manager for display provider
2021-04-06 00:50:12 +02:00
Adrien Ferrand
c438a397a0 Enable mypy strict mode (#8766)
Built on top of #8748, this PR reenables mypy strict mode and adds the appropriate corrections to pass the types checks.

* Upgrade mypy

* First step for acme

* Cast for the rescue

* Fixing types for certbot

* Fix typing for certbot-nginx

* Finalize type fixes, configure no optional strict check for mypy in tox

* Align requirements

* Isort

* Pylint

* Protocol for python 3.6

* Use Python 3.9 for mypy, make code compatible with Python 3.8<

* Pylint and mypy

* Pragma no cover

* Pythonic NotImplemented constant

* More type definitions

* Add comments

* Simplify typing logic

* Use vararg tuple

* Relax constraints on mypy

* Add more type

* Do not silence error if target is not defined

* Conditionally import Protocol for type checking only

* Clean up imports

* Add comments

* Align python version linting with mypy and coverage

* Just ignore types in an unused module

* Add comments

* Fix lint

* Work in progress

* Finish type control

* Isort

* Fix pylint

* Fix imports

* Fix cli subparser

* Some fixes

* Coverage

* Remove --no-strict-optional (obviously...)

* Update certbot-apache/certbot_apache/_internal/configurator.py

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

* Update certbot/certbot/_internal/display/completer.py

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

* Cleanup dns_google

* Improve lock controls and fix subparser

* Use the expected interfaces

* Fix code

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-04-05 15:04:21 -07:00
Adrien Ferrand
0f9f902b6e Use typing-extensions to ensure certbot dev environment is compatible with Python 3.6/3.7 (#8776)
Fixes #8773

I took option 2 from the issue mentionned above (importing `typing-extensions` on dev dependencies) to avoid modifying certbot runtime requirements given that what needs to be added is useful for mypy only.

I did not change the Python version used to execute the linting and mypy on the standard tests, given that the tox `docker_dev` target already checks if the development environment is working for Python < 3.8.
2021-04-05 11:53:57 -07:00
Brad Warren
33f177b361 Upgrade Python to 3.8.9. (#8775)
Over the weekend, Python released new versions of Python 3.8 and Python 3.9 partially in response to the OpenSSL CVEs discussed at https://github.com/certbot/certbot/pull/8741#issuecomment-809644789. You can see this mentioned in their changelog at https://docs.python.org/release/3.8.9/whatsnew/changelog.html#build.

This PR updates the windows installer to use that new release so all of our distribution methods that contain their own copy of OpenSSL are patched for the release tomorrow.

You can see tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=3751&view=results. You can see Python 3.8.9 being downloaded instead of an older version at https://dev.azure.com/certbot/certbot/_build/results?buildId=3751&view=logs&j=ad29f110-3cce-5317-4ef2-0a692ae1dee7&t=901eeead-396c-5477-aba2-f402fdcfb885&l=1055.
2021-04-05 11:15:09 -07:00
275 changed files with 3003 additions and 4650 deletions

View File

@@ -85,12 +85,6 @@ jobs:
IMAGE_NAME: macOS-10.15
PYTHON_VERSION: 3.8
TOXENV: test-farm-apache2
farmtest-leauto-upgrades:
PYTHON_VERSION: 3.7
TOXENV: test-farm-leauto-upgrades
farmtest-certonly-standalone:
PYTHON_VERSION: 3.7
TOXENV: test-farm-certonly-standalone
farmtest-sdists:
PYTHON_VERSION: 3.7
TOXENV: test-farm-sdists

View File

@@ -1,4 +1,58 @@
jobs:
- job: docker_build
pool:
vmImage: ubuntu-18.04
strategy:
matrix:
amd64:
DOCKER_ARCH: amd64
# Do not run the heavy non-amd64 builds for test branches
${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}:
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
# The default timeout of 60 minutes is a little low for compiling
# cryptography on ARM architectures.
timeoutInMinutes: 180
steps:
- bash: set -e && tools/docker/build.sh $(dockerTag) $DOCKER_ARCH
displayName: Build the Docker images
# We don't filter for the Docker Hub organization to continue to allow
# easy testing of these scripts on forks.
- bash: |
set -e
DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}')
docker save --output images.tar $DOCKER_IMAGES
displayName: Save the Docker images
# If the name of the tar file or artifact changes, the deploy stage will
# also need to be updated.
- bash: set -e && mv images.tar $(Build.ArtifactStagingDirectory)
displayName: Prepare Docker artifact
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
artifact: docker_$(DOCKER_ARCH)
displayName: Store Docker artifact
- job: docker_run
dependsOn: docker_build
pool:
vmImage: ubuntu-18.04
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: docker_amd64
path: $(Build.SourcesDirectory)
displayName: Retrieve Docker images
- bash: set -e && docker load --input $(Build.SourcesDirectory)/images.tar
displayName: Load Docker images
- bash: |
set -ex
DOCKER_IMAGES=$(docker images --filter reference='*/certbot' --filter reference='*/dns-*' --format '{{.Repository}}:{{.Tag}}')
for DOCKER_IMAGE in ${DOCKER_IMAGES}
do docker run --rm "${DOCKER_IMAGE}" plugins --prepare
done
displayName: Run integration tests for Docker images
- job: installer_build
pool:
vmImage: vs2017-win2016
@@ -68,3 +122,109 @@ jobs:
set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH%
venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4
displayName: Run certbot integration tests
- job: snaps_build
pool:
vmImage: ubuntu-18.04
strategy:
matrix:
amd64:
SNAP_ARCH: amd64
# Do not run the heavy non-amd64 builds for test branches
${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}:
armhf:
SNAP_ARCH: armhf
arm64:
SNAP_ARCH: arm64
timeoutInMinutes: 0
steps:
- script: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends snapd
sudo snap install --classic snapcraft
displayName: Install dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
addToPath: true
- task: DownloadSecureFile@1
name: credentials
inputs:
secureFile: launchpad-credentials
- script: |
set -e
git config --global user.email "$(Build.RequestedForEmail)"
git config --global user.name "$(Build.RequestedFor)"
mkdir -p ~/.local/share/snapcraft/provider/launchpad
cp $(credentials.secureFilePath) ~/.local/share/snapcraft/provider/launchpad/credentials
python3 tools/snap/build_remote.py ALL --archs ${SNAP_ARCH} --timeout 19800
displayName: Build snaps
- script: |
set -e
mv *.snap $(Build.ArtifactStagingDirectory)
mv certbot-dns-*/*.snap $(Build.ArtifactStagingDirectory)
displayName: Prepare artifacts
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
artifact: snaps_$(SNAP_ARCH)
displayName: Store snaps artifacts
- job: snap_run
dependsOn: snaps_build
pool:
vmImage: ubuntu-18.04
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
addToPath: true
- script: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends nginx-light snapd
python3 -m venv venv
venv/bin/python tools/pipstrap.py
venv/bin/python tools/pip_install.py -U tox
displayName: Install dependencies
- task: DownloadPipelineArtifact@2
inputs:
artifact: snaps_amd64
path: $(Build.SourcesDirectory)/snap
displayName: Retrieve Certbot snaps
- script: |
set -e
sudo snap install --dangerous --classic snap/certbot_*.snap
displayName: Install Certbot snap
- script: |
set -e
venv/bin/python -m tox -e integration-external,apacheconftest-external-with-pebble
displayName: Run tox
- job: snap_dns_run
dependsOn: snaps_build
pool:
vmImage: ubuntu-18.04
steps:
- script: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends snapd
displayName: Install dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:
artifact: snaps_amd64
path: $(Build.SourcesDirectory)/snap
displayName: Retrieve Certbot snaps
- script: |
set -e
python3 -m venv venv
venv/bin/python tools/pipstrap.py
venv/bin/python tools/pip_install.py -e certbot-ci
displayName: Prepare Certbot-CI
- script: |
set -e
sudo -E venv/bin/pytest certbot-ci/snap_integration_tests/dns_tests --allow-persistent-changes --snap-folder $(Build.SourcesDirectory)/snap --snap-arch amd64
displayName: Test DNS plugins snaps

View File

@@ -56,6 +56,8 @@ jobs:
apache-compat:
IMAGE_NAME: ubuntu-18.04
TOXENV: apache_compat
# le-modification can be moved to the extended test suite once
# https://github.com/certbot/certbot/issues/8742 is resolved.
le-modification:
IMAGE_NAME: ubuntu-18.04
TOXENV: modification

View File

@@ -1,4 +1,6 @@
stages:
- stage: TestAndPackage
jobs:
- template: ../jobs/standard-tests-jobs.yml
- template: ../jobs/extended-tests-jobs.yml
- template: ../jobs/packaging-jobs.yml

View File

@@ -9,7 +9,7 @@ steps:
do
echo ""
echo "##[group]Building $doc_path"
pip install -q -e $doc_path/..[docs]
tools/pip_install_editable.py $doc_path/..[docs]
if ! sphinx-build -W --keep-going -b html $doc_path $doc_path/_build/html; then
FINAL_STATUS=1
FAILED_BUILDS[${#FAILED_BUILDS[@]}]="${doc_path%/docs}"

View File

@@ -1,6 +1,10 @@
steps:
# We run brew update because we've seen attempts to install an older version
# of a package fail. See
# https://github.com/actions/virtual-environments/issues/3165.
- bash: |
set -e
brew update
brew install augeas
condition: startswith(variables['IMAGE_NAME'], 'macOS')
displayName: Install MacOS dependencies

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
custom: https://supporters.eff.org/donate/support-work-on-certbot

8
.gitignore vendored
View File

@@ -4,13 +4,11 @@
build/
dist*/
/venv*/
/kgs/
/.tox/
/releases*/
/log*
letsencrypt.log
certbot.log
letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64
poetry.lock
# coverage
@@ -32,12 +30,6 @@ tags
# auth --cert-path --chain-path
/*.pem
# letstest
tests/letstest/letest-*/
tests/letstest/*.pem
tests/letstest/venv/
tests/letstest/venv3/
.venv
# pytest cache

View File

@@ -1,6 +1,5 @@
[settings]
skip_glob=venv*
skip=letsencrypt-auto-source
force_sort_within_sections=True
force_single_line=True
order_by_type=False

View File

@@ -56,7 +56,18 @@ extension-pkg-whitelist=pywintypes,win32api,win32file,win32security
# See https://github.com/PyCQA/pylint/issues/1498.
# 3) Same as point 2 for no-value-for-parameter.
# See https://github.com/PyCQA/pylint/issues/2820.
disable=fixme,locally-disabled,locally-enabled,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design,import-outside-toplevel,useless-object-inheritance,unsubscriptable-object,no-value-for-parameter,no-else-return,no-else-raise,no-else-break,no-else-continue
# 4) raise-missing-from makes it an error to raise an exception from except
# block without using explicit exception chaining. While explicit exception
# chaining results in a slightly more informative traceback, I don't think
# it's beneficial enough for us to change all of our current instances and
# give Certbot developers errors about this when they're working on new code
# in the future. You can read more about exception chaining and this pylint
# check at
# https://blog.ram.rachum.com/post/621791438475296768/improving-python-exception-chaining-with.
# 5) wrong-import-order generates false positives and a pylint developer
# suggests that people using isort should disable this check at
# https://github.com/PyCQA/pylint/issues/3817#issuecomment-687892090.
disable=fixme,locally-disabled,locally-enabled,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design,import-outside-toplevel,useless-object-inheritance,unsubscriptable-object,no-value-for-parameter,no-else-return,no-else-raise,no-else-break,no-else-continue,raise-missing-from,wrong-import-order
[REPORTS]

View File

@@ -1,5 +1,5 @@
# This Dockerfile builds an image for development.
FROM debian:buster
FROM ubuntu:focal
# Note: this only exposes the port to other docker containers.
EXPOSE 80 443
@@ -8,8 +8,9 @@ WORKDIR /opt/certbot/src
COPY . .
RUN apt-get update && \
apt-get install apache2 git python3-dev python3-venv gcc libaugeas0 \
libssl-dev libffi-dev ca-certificates openssl nginx-light -y && \
DEBIAN_FRONTEND=noninteractive apt-get install apache2 git python3-dev \
python3-venv gcc libaugeas0 libssl-dev libffi-dev ca-certificates \
openssl nginx-light -y --no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* \
/tmp/* \

View File

@@ -30,7 +30,7 @@ class Challenge(jose.TypedJSONObjectWithFields):
@classmethod
def from_json(cls, jobj):
try:
return super(Challenge, cls).from_json(jobj)
return super().from_json(jobj)
except jose.UnrecognizedTypeError as error:
logger.debug(error)
return UnrecognizedChallenge.from_json(jobj)
@@ -58,7 +58,7 @@ class UnrecognizedChallenge(Challenge):
"""
def __init__(self, jobj):
super(UnrecognizedChallenge, self).__init__()
super().__init__()
object.__setattr__(self, "jobj", jobj)
def to_partial_json(self):
@@ -141,7 +141,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
return True
def to_partial_json(self):
jobj = super(KeyAuthorizationChallengeResponse, self).to_partial_json()
jobj = super().to_partial_json()
jobj.pop('keyAuthorization', None)
return jobj

View File

@@ -253,7 +253,7 @@ class Client(ClientBase):
if isinstance(directory, str):
directory = messages.Directory.from_json(
net.get(directory).json())
super(Client, self).__init__(directory=directory,
super().__init__(directory=directory,
net=net, acme_version=1)
def register(self, new_reg=None):
@@ -577,7 +577,7 @@ class ClientV2(ClientBase):
:param .messages.Directory directory: Directory Resource
:param .ClientNetwork net: Client network.
"""
super(ClientV2, self).__init__(directory=directory,
super().__init__(directory=directory,
net=net, acme_version=2)
def new_account(self, new_account):
@@ -627,7 +627,7 @@ class ClientV2(ClientBase):
"""
# https://github.com/certbot/certbot/issues/6155
new_regr = self._get_v2_account(regr)
return super(ClientV2, self).update_registration(new_regr, update)
return super().update_registration(new_regr, update)
def _get_v2_account(self, regr):
self.net.account = None

View File

@@ -29,7 +29,7 @@ class NonceError(ClientError):
class BadNonce(NonceError):
"""Bad nonce error."""
def __init__(self, nonce, error, *args):
super(BadNonce, self).__init__(*args)
super().__init__(*args)
self.nonce = nonce
self.error = error
@@ -48,7 +48,7 @@ class MissingNonce(NonceError):
"""
def __init__(self, response, *args):
super(MissingNonce, self).__init__(*args)
super().__init__(*args)
self.response = response
def __str__(self):
@@ -72,7 +72,7 @@ class PollError(ClientError):
def __init__(self, exhausted, updated):
self.exhausted = exhausted
self.updated = updated
super(PollError, self).__init__()
super().__init__()
@property
def timeout(self):
@@ -90,7 +90,7 @@ class ValidationError(Error):
"""
def __init__(self, failed_authzrs):
self.failed_authzrs = failed_authzrs
super(ValidationError, self).__init__()
super().__init__()
class TimeoutError(Error): # pylint: disable=redefined-builtin
@@ -106,7 +106,7 @@ class IssuanceError(Error):
:param messages.Error error: The error provided by the server.
"""
self.error = error
super(IssuanceError, self).__init__()
super().__init__()
class ConflictError(ClientError):
@@ -119,7 +119,7 @@ class ConflictError(ClientError):
"""
def __init__(self, location):
self.location = location
super(ConflictError, self).__init__()
super().__init__()
class WildcardUnsupportedError(Error):

View File

@@ -12,7 +12,7 @@ class Fixed(jose.Field):
def __init__(self, json_name, value):
self.value = value
super(Fixed, self).__init__(
super().__init__(
json_name=json_name, default=value, omitempty=False)
def decode(self, value):
@@ -53,7 +53,7 @@ class Resource(jose.Field):
def __init__(self, resource_type, *args, **kwargs):
self.resource_type = resource_type
super(Resource, self).__init__(
super().__init__(
'resource', default=resource_type, *args, **kwargs)
def decode(self, value):

View File

@@ -50,7 +50,7 @@ class JWS(jose.JWS):
# Per ACME spec, jwk and kid are mutually exclusive, so only include a
# jwk field if kid is not provided.
include_jwk = kid is None
return super(JWS, cls).sign(payload, key=key, alg=alg,
return super().sign(payload, key=key, alg=alg,
protect=frozenset(['nonce', 'url', 'kid', 'jwk', 'alg']),
nonce=nonce, url=url, kid=kid,
include_jwk=include_jwk)

View File

@@ -132,7 +132,7 @@ class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore
POSSIBLE_NAMES: Dict[str, '_Constant'] = NotImplemented
def __init__(self, name):
super(_Constant, self).__init__()
super().__init__()
self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation
self.name = name
@@ -201,7 +201,7 @@ class Directory(jose.JSONDeSerializable):
def __init__(self, **kwargs):
kwargs = {self._internal_name(k): v for k, v in kwargs.items()}
super(Directory.Meta, self).__init__(**kwargs)
super().__init__(**kwargs)
@property
def terms_of_service(self):
@@ -211,7 +211,7 @@ class Directory(jose.JSONDeSerializable):
def __iter__(self):
# When iterating over fields, use the external name 'terms_of_service' instead of
# the internal '_terms_of_service'.
for name in super(Directory.Meta, self).__iter__():
for name in super().__iter__():
yield name[1:] if name == '_terms_of_service' else name
def _internal_name(self, name):
@@ -357,7 +357,7 @@ class Registration(ResourceBody):
if 'contact' in kwargs:
# Avoid the __setattr__ used by jose.TypedJSONObjectWithFields
object.__setattr__(self, '_add_contact', True)
super(Registration, self).__init__(**kwargs)
super().__init__(**kwargs)
def _filter_contact(self, prefix):
return tuple(
@@ -383,12 +383,12 @@ class Registration(ResourceBody):
def to_partial_json(self):
"""Modify josepy.JSONDeserializable.to_partial_json()"""
jobj = super(Registration, self).to_partial_json()
jobj = super().to_partial_json()
return self._add_contact_if_appropriate(jobj)
def fields_to_partial_json(self):
"""Modify josepy.JSONObjectWithFields.fields_to_partial_json()"""
jobj = super(Registration, self).fields_to_partial_json()
jobj = super().fields_to_partial_json()
return self._add_contact_if_appropriate(jobj)
@property
@@ -460,19 +460,19 @@ class ChallengeBody(ResourceBody):
def __init__(self, **kwargs):
kwargs = {self._internal_name(k): v for k, v in kwargs.items()}
super(ChallengeBody, self).__init__(**kwargs)
super().__init__(**kwargs)
def encode(self, name):
return super(ChallengeBody, self).encode(self._internal_name(name))
return super().encode(self._internal_name(name))
def to_partial_json(self):
jobj = super(ChallengeBody, self).to_partial_json()
jobj = super().to_partial_json()
jobj.update(self.chall.to_partial_json())
return jobj
@classmethod
def fields_from_json(cls, jobj):
jobj_fields = super(ChallengeBody, cls).fields_from_json(jobj)
jobj_fields = super().fields_from_json(jobj)
jobj_fields['chall'] = challenges.Challenge.from_json(jobj)
return jobj_fields
@@ -487,7 +487,7 @@ class ChallengeBody(ResourceBody):
def __iter__(self):
# When iterating over fields, use the external name 'uri' instead of
# the internal '_uri'.
for name in super(ChallengeBody, self).__iter__():
for name in super().__iter__():
yield name[1:] if name == '_uri' else name
def _internal_name(self, name):

View File

@@ -20,7 +20,7 @@ class VersionedLEACMEMixin:
# Required for @property to operate properly. See comment above.
object.__setattr__(self, key, value)
else:
super(VersionedLEACMEMixin, self).__setattr__(key, value) # pragma: no cover
super().__setattr__(key, value) # pragma: no cover
class ResourceMixin(VersionedLEACMEMixin):
@@ -30,12 +30,12 @@ class ResourceMixin(VersionedLEACMEMixin):
"""
def to_partial_json(self):
"""See josepy.JSONDeserializable.to_partial_json()"""
return _safe_jobj_compliance(super(ResourceMixin, self),
return _safe_jobj_compliance(super(),
'to_partial_json', 'resource')
def fields_to_partial_json(self):
"""See josepy.JSONObjectWithFields.fields_to_partial_json()"""
return _safe_jobj_compliance(super(ResourceMixin, self),
return _safe_jobj_compliance(super(),
'fields_to_partial_json', 'resource')
@@ -46,12 +46,12 @@ class TypeMixin(VersionedLEACMEMixin):
"""
def to_partial_json(self):
"""See josepy.JSONDeserializable.to_partial_json()"""
return _safe_jobj_compliance(super(TypeMixin, self),
return _safe_jobj_compliance(super(),
'to_partial_json', 'type')
def fields_to_partial_json(self):
"""See josepy.JSONObjectWithFields.fields_to_partial_json()"""
return _safe_jobj_compliance(super(TypeMixin, self),
return _safe_jobj_compliance(super(),
'fields_to_partial_json', 'type')

View File

@@ -3,7 +3,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@@ -37,7 +37,7 @@ setup(
description='ACME protocol implementation in Python',
url='https://github.com/letsencrypt/letsencrypt',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -292,7 +292,7 @@ class TLSALPN01ResponseTest(unittest.TestCase):
def test_gen_verify_cert_gen_key(self):
cert, key = self.response.gen_cert(self.domain)
self.assertTrue(isinstance(key, OpenSSL.crypto.PKey))
self.assertIsInstance(key, OpenSSL.crypto.PKey)
self.assertTrue(self.response.verify_cert(self.domain, cert))
def test_verify_bad_cert(self):
@@ -431,7 +431,7 @@ class DNSTest(unittest.TestCase):
mock_gen.return_value = mock.sentinel.validation
response = self.msg.gen_response(KEY)
from acme.challenges import DNSResponse
self.assertTrue(isinstance(response, DNSResponse))
self.assertIsInstance(response, DNSResponse)
self.assertEqual(response.validation, mock.sentinel.validation)
def test_validation_domain_name(self):

View File

@@ -90,7 +90,7 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
"""Tests for acme.client.BackwardsCompatibleClientV2."""
def setUp(self):
super(BackwardsCompatibleClientV2Test, self).setUp()
super().setUp()
# contains a loaded cert
self.certr = messages.CertificateResource(
body=messages_test.CERT)
@@ -319,7 +319,7 @@ class ClientTest(ClientTestBase):
"""Tests for acme.client.Client."""
def setUp(self):
super(ClientTest, self).setUp()
super().setUp()
self.directory = DIRECTORY_V1
@@ -604,8 +604,8 @@ class ClientTest(ClientTestBase):
# make sure that max_attempts is per-authorization, rather
# than global
max_attempts=max(len(authzrs[0].retries), len(authzrs[1].retries)))
self.assertTrue(cert[0] is csr)
self.assertTrue(cert[1] is updated_authzrs)
self.assertIs(cert[0], csr)
self.assertIs(cert[1], updated_authzrs)
self.assertEqual(updated_authzrs[0].uri, 'a...')
self.assertEqual(updated_authzrs[1].uri, 'b.')
self.assertEqual(updated_authzrs[0].times, [
@@ -641,7 +641,7 @@ class ClientTest(ClientTestBase):
authzr = self.client.deactivate_authorization(self.authzr)
self.assertEqual(authzb, authzr.body)
self.assertEqual(self.client.net.post.call_count, 1)
self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0])
self.assertIn(self.authzr.uri, self.net.post.call_args_list[0][0])
def test_check_cert(self):
self.response.headers['Location'] = self.certr.uri
@@ -700,7 +700,7 @@ class ClientTest(ClientTestBase):
def test_revocation_payload(self):
obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn)
self.assertTrue('reason' in obj.to_partial_json().keys())
self.assertIn('reason', obj.to_partial_json().keys())
self.assertEqual(self.rsn, obj.to_partial_json()['reason'])
def test_revoke_bad_status_raises_error(self):
@@ -716,7 +716,7 @@ class ClientV2Test(ClientTestBase):
"""Tests for acme.client.ClientV2."""
def setUp(self):
super(ClientV2Test, self).setUp()
super().setUp()
self.directory = DIRECTORY_V2
@@ -877,9 +877,9 @@ class ClientV2Test(ClientTestBase):
self.response.headers['Location'] = self.regr.uri
self.response.json.return_value = self.regr.body.to_json()
self.assertEqual(self.regr, self.client.update_registration(self.regr))
self.assertNotEqual(self.client.net.account, None)
self.assertIsNotNone(self.client.net.account)
self.assertEqual(self.client.net.post.call_count, 2)
self.assertTrue(DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0])
self.assertIn(DIRECTORY_V2.newAccount, self.net.post.call_args_list[0][0])
self.response.json.return_value = self.regr.body.update(
contact=()).to_json()
@@ -943,7 +943,7 @@ class ClientNetworkTest(unittest.TestCase):
self.response.links = {}
def test_init(self):
self.assertTrue(self.net.verify_ssl is self.verify_ssl)
self.assertIs(self.net.verify_ssl, self.verify_ssl)
def test_wrap_in_jws(self):
# pylint: disable=protected-access
@@ -1185,7 +1185,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
def send_request(*args, **kwargs):
# pylint: disable=unused-argument,missing-docstring
self.assertFalse("new_nonce_url" in kwargs)
self.assertNotIn("new_nonce_url", kwargs)
method = args[0]
uri = args[1]
if method == 'HEAD' and uri != "new_nonce_uri":
@@ -1330,7 +1330,7 @@ class ClientNetworkSourceAddressBindingTest(unittest.TestCase):
from acme.client import ClientNetwork
net = ClientNetwork(key=None, alg=None, source_address=self.source_address)
for adapter in net.session.adapters.values():
self.assertTrue(self.source_address in adapter.source_address)
self.assertIn(self.source_address, adapter.source_address)
def test_behavior_assumption(self):
"""This is a test that guardrails the HTTPAdapter behavior so that if the default for

View File

@@ -191,7 +191,7 @@ class RandomSnTest(unittest.TestCase):
for _ in range(self.cert_count):
cert = gen_ss_cert(self.key, ['dummy'], force_san=True)
self.serial_num.append(cert.get_serial_number())
self.assertTrue(len(set(self.serial_num)) > 1)
self.assertGreater(len(set(self.serial_num)), 1)
class MakeCSRTest(unittest.TestCase):
"""Test for standalone functions."""
@@ -206,8 +206,8 @@ class MakeCSRTest(unittest.TestCase):
def test_make_csr(self):
csr_pem = self._call_with_key(["a.example", "b.example"])
self.assertTrue(b'--BEGIN CERTIFICATE REQUEST--' in csr_pem)
self.assertTrue(b'--END CERTIFICATE REQUEST--' in csr_pem)
self.assertIn(b'--BEGIN CERTIFICATE REQUEST--', csr_pem)
self.assertIn(b'--END CERTIFICATE REQUEST--', csr_pem)
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# In pyopenssl 0.13 (used with TOXENV=py27-oldest), csr objects don't

View File

@@ -24,8 +24,8 @@ class MissingNonceTest(unittest.TestCase):
self.error = MissingNonce(self.response)
def test_str(self):
self.assertTrue("FOO" in str(self.error))
self.assertTrue("{}" in str(self.error))
self.assertIn("FOO", str(self.error))
self.assertIn("{}", str(self.error))
class PollErrorTest(unittest.TestCase):

View File

@@ -48,7 +48,7 @@ class JWSTest(unittest.TestCase):
self.assertEqual(jws.signature.combined.nonce, self.nonce)
self.assertEqual(jws.signature.combined.url, self.url)
self.assertEqual(jws.signature.combined.kid, self.kid)
self.assertEqual(jws.signature.combined.jwk, None)
self.assertIsNone(jws.signature.combined.jwk)
# TODO: check that nonce is in protected header
self.assertEqual(jws, JWS.from_json(jws.to_json()))
@@ -58,7 +58,7 @@ class JWSTest(unittest.TestCase):
jws = JWS.sign(payload=b'foo', key=self.privkey,
alg=jose.RS256, nonce=self.nonce,
url=self.url)
self.assertEqual(jws.signature.combined.kid, None)
self.assertIsNone(jws.signature.combined.kid)
self.assertEqual(jws.signature.combined.jwk, self.pubkey)

View File

@@ -41,13 +41,13 @@ class ErrorTest(unittest.TestCase):
def test_description(self):
self.assertEqual('The request message was malformed', self.error.description)
self.assertTrue(self.error_custom.description is None)
self.assertIsNone(self.error_custom.description)
def test_code(self):
from acme.messages import Error
self.assertEqual('malformed', self.error.code)
self.assertEqual(None, self.error_custom.code)
self.assertEqual(None, Error().code)
self.assertIsNone(self.error_custom.code)
self.assertIsNone(Error().code)
def test_is_acme_error(self):
from acme.messages import is_acme_error, Error
@@ -260,10 +260,10 @@ class RegistrationTest(unittest.TestCase):
self.assertEqual(empty_new_reg.contact, ())
self.assertEqual(new_reg_with_contact.contact, ())
self.assertTrue('contact' not in empty_new_reg.to_partial_json())
self.assertTrue('contact' not in empty_new_reg.fields_to_partial_json())
self.assertTrue('contact' in new_reg_with_contact.to_partial_json())
self.assertTrue('contact' in new_reg_with_contact.fields_to_partial_json())
self.assertNotIn('contact', empty_new_reg.to_partial_json())
self.assertNotIn('contact', empty_new_reg.fields_to_partial_json())
self.assertIn('contact', new_reg_with_contact.to_partial_json())
self.assertIn('contact', new_reg_with_contact.fields_to_partial_json())
class UpdateRegistrationTest(unittest.TestCase):
@@ -406,7 +406,7 @@ class AuthorizationResourceTest(unittest.TestCase):
authzr = AuthorizationResource(
uri=mock.sentinel.uri,
body=mock.sentinel.body)
self.assertTrue(isinstance(authzr, jose.JSONDeSerializable))
self.assertIsInstance(authzr, jose.JSONDeSerializable)
class CertificateRequestTest(unittest.TestCase):
@@ -417,7 +417,7 @@ class CertificateRequestTest(unittest.TestCase):
self.req = CertificateRequest(csr=CSR)
def test_json_de_serializable(self):
self.assertTrue(isinstance(self.req, jose.JSONDeSerializable))
self.assertIsInstance(self.req, jose.JSONDeSerializable)
from acme.messages import CertificateRequest
self.assertEqual(
self.req, CertificateRequest.from_json(self.req.to_json()))
@@ -433,7 +433,7 @@ class CertificateResourceTest(unittest.TestCase):
cert_chain_uri=mock.sentinel.cert_chain_uri)
def test_json_de_serializable(self):
self.assertTrue(isinstance(self.certr, jose.JSONDeSerializable))
self.assertIsInstance(self.certr, jose.JSONDeSerializable)
from acme.messages import CertificateResource
self.assertEqual(
self.certr, CertificateResource.from_json(self.certr.to_json()))

View File

@@ -220,13 +220,14 @@ def _get_runtime_cfg(command):
"""
try:
proc = subprocess.Popen(
proc = subprocess.run(
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True,
check=False,
env=util.env_no_snap_for_external_calls())
stdout, stderr = proc.communicate()
stdout, stderr = proc.stdout, proc.stderr
except (OSError, ValueError):
logger.error(

View File

@@ -15,7 +15,7 @@ class ApacheParserNode(interfaces.ParserNode):
def __init__(self, **kwargs):
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
super(ApacheParserNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.ancestor = ancestor
self.filepath = filepath
self.dirty = dirty
@@ -39,7 +39,7 @@ class ApacheCommentNode(ApacheParserNode):
def __init__(self, **kwargs):
comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable
super(ApacheCommentNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.comment = comment
def __eq__(self, other): # pragma: no cover
@@ -57,7 +57,7 @@ class ApacheDirectiveNode(ApacheParserNode):
def __init__(self, **kwargs):
name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs)
super(ApacheDirectiveNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.name = name
self.parameters = parameters
self.enabled = enabled
@@ -83,7 +83,7 @@ class ApacheBlockNode(ApacheDirectiveNode):
""" apacheconfig implementation of BlockNode interface """
def __init__(self, **kwargs):
super(ApacheBlockNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.children: Tuple[ApacheParserNode, ...] = ()
def __eq__(self, other): # pragma: no cover

View File

@@ -136,6 +136,6 @@ def assertEqualPathsList(first, second): # pragma: no cover
if any(isPass(path) for path in second):
return
for fpath in first:
assert any([fnmatch.fnmatch(fpath, spath) for spath in second])
assert any(fnmatch.fnmatch(fpath, spath) for spath in second)
for spath in second:
assert any([fnmatch.fnmatch(fpath, spath) for fpath in first])
assert any(fnmatch.fnmatch(fpath, spath) for fpath in first)

View File

@@ -80,7 +80,7 @@ class AugeasParserNode(interfaces.ParserNode):
def __init__(self, **kwargs):
ancestor, dirty, filepath, metadata = util.parsernode_kwargs(kwargs) # pylint: disable=unused-variable
super(AugeasParserNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.ancestor = ancestor
self.filepath = filepath
self.dirty = dirty
@@ -169,7 +169,7 @@ class AugeasCommentNode(AugeasParserNode):
def __init__(self, **kwargs):
comment, kwargs = util.commentnode_kwargs(kwargs) # pylint: disable=unused-variable
super(AugeasCommentNode, self).__init__(**kwargs)
super().__init__(**kwargs)
# self.comment = comment
self.comment = comment
@@ -188,7 +188,7 @@ class AugeasDirectiveNode(AugeasParserNode):
def __init__(self, **kwargs):
name, parameters, enabled, kwargs = util.directivenode_kwargs(kwargs)
super(AugeasDirectiveNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.name = name
self.enabled = enabled
if parameters:
@@ -245,7 +245,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
""" Augeas implementation of BlockNode interface """
def __init__(self, **kwargs):
super(AugeasBlockNode, self).__init__(**kwargs)
super().__init__(**kwargs)
self.children = ()
def __eq__(self, other):

View File

@@ -8,10 +8,10 @@ import logging
import re
import socket
import time
from typing import cast
from typing import DefaultDict
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Union
@@ -25,6 +25,7 @@ from certbot import util
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import
from certbot.compat import filesystem
from certbot.compat import os
from certbot.display import util as display_util
from certbot.plugins import common
from certbot.plugins.enhancements import AutoHSTSEnhancement
from certbot.plugins.util import path_surgery
@@ -36,6 +37,9 @@ from certbot_apache._internal import dualparser
from certbot_apache._internal import http_01
from certbot_apache._internal import obj
from certbot_apache._internal import parser
from certbot_apache._internal.dualparser import DualBlockNode
from certbot_apache._internal.obj import VirtualHost
from certbot_apache._internal.parser import ApacheParser
try:
import apacheconfig
@@ -47,6 +51,47 @@ except ImportError: # pragma: no cover
logger = logging.getLogger(__name__)
class OsOptions:
"""
Dedicated class to describe the OS specificities (eg. paths, binary names)
that the Apache configurator needs to be aware to operate properly.
"""
def __init__(self,
server_root="/etc/apache2",
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd: Optional[List[str]] = None,
restart_cmd: Optional[List[str]] = None,
restart_cmd_alt: Optional[List[str]] = None,
conftest_cmd: Optional[List[str]] = None,
enmod: Optional[str] = None,
dismod: Optional[str] = None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2",
apache_bin: Optional[str] = None,
):
self.server_root = server_root
self.vhost_root = vhost_root
self.vhost_files = vhost_files
self.logs_root = logs_root
self.ctl = ctl
self.version_cmd = ['apache2ctl', '-v'] if not version_cmd else version_cmd
self.restart_cmd = ['apache2ctl', 'graceful'] if not restart_cmd else restart_cmd
self.restart_cmd_alt = restart_cmd_alt
self.conftest_cmd = ['apache2ctl', 'configtest'] if not conftest_cmd else conftest_cmd
self.enmod = enmod
self.dismod = dismod
self.le_vhost_ext = le_vhost_ext
self.handle_modules = handle_modules
self.handle_sites = handle_sites
self.challenge_location = challenge_location
self.bin = apache_bin
# TODO: Augeas sections ie. <VirtualHost>, <IfModule> beginning and closing
# tags need to be the same case, otherwise Augeas doesn't recognize them.
# This is not able to be completely remedied by regular expressions because
@@ -102,27 +147,7 @@ class ApacheConfigurator(common.Installer):
" change depending on the operating system Certbot is run on.)"
)
OS_DEFAULTS = dict(
server_root="/etc/apache2",
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2",
bin=None
)
def option(self, key):
"""Get a value from options"""
return self.options.get(key)
OS_DEFAULTS = OsOptions()
def pick_apache_config(self, warn_on_no_mod_ssl=True):
"""
@@ -152,14 +177,14 @@ class ApacheConfigurator(common.Installer):
for o in opts:
# Config options use dashes instead of underscores
if self.conf(o.replace("_", "-")) is not None:
self.options[o] = self.conf(o.replace("_", "-"))
setattr(self.options, o, self.conf(o.replace("_", "-")))
else:
self.options[o] = self.OS_DEFAULTS[o]
setattr(self.options, o, getattr(self.OS_DEFAULTS, o))
# Special cases
cast(List[str], self.options["version_cmd"])[0] = self.option("ctl")
cast(List[str], self.options["restart_cmd"])[0] = self.option("ctl")
cast(List[str], self.options["conftest_cmd"])[0] = self.option("ctl")
self.options.version_cmd[0] = self.options.ctl
self.options.restart_cmd[0] = self.options.ctl
self.options.conftest_cmd[0] = self.options.ctl
@classmethod
def add_parser_arguments(cls, add):
@@ -174,30 +199,30 @@ class ApacheConfigurator(common.Installer):
else:
# cls.OS_DEFAULTS can be distribution specific, see override classes
DEFAULTS = cls.OS_DEFAULTS
add("enmod", default=DEFAULTS["enmod"],
add("enmod", default=DEFAULTS.enmod,
help="Path to the Apache 'a2enmod' binary")
add("dismod", default=DEFAULTS["dismod"],
add("dismod", default=DEFAULTS.dismod,
help="Path to the Apache 'a2dismod' binary")
add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"],
add("le-vhost-ext", default=DEFAULTS.le_vhost_ext,
help="SSL vhost configuration extension")
add("server-root", default=DEFAULTS["server_root"],
add("server-root", default=DEFAULTS.server_root,
help="Apache server root directory")
add("vhost-root", default=None,
help="Apache server VirtualHost configuration root")
add("logs-root", default=DEFAULTS["logs_root"],
add("logs-root", default=DEFAULTS.logs_root,
help="Apache server logs directory")
add("challenge-location",
default=DEFAULTS["challenge_location"],
default=DEFAULTS.challenge_location,
help="Directory path for challenge configuration")
add("handle-modules", default=DEFAULTS["handle_modules"],
add("handle-modules", default=DEFAULTS.handle_modules,
help="Let installer handle enabling required modules for you " +
"(Only Ubuntu/Debian currently)")
add("handle-sites", default=DEFAULTS["handle_sites"],
add("handle-sites", default=DEFAULTS.handle_sites,
help="Let installer handle enabling sites for you " +
"(Only Ubuntu/Debian currently)")
add("ctl", default=DEFAULTS["ctl"],
add("ctl", default=DEFAULTS.ctl,
help="Full path to Apache control script")
add("bin", default=DEFAULTS["bin"],
add("bin", default=DEFAULTS.bin,
help="Full path to apache2/httpd binary")
def __init__(self, *args, **kwargs):
@@ -210,7 +235,7 @@ 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)
super().__init__(*args, **kwargs)
# Add name_server association dict
self.assoc: Dict[str, obj.VirtualHost] = {}
@@ -232,11 +257,11 @@ class ApacheConfigurator(common.Installer):
self.parsed_paths: List[str] = []
# These will be set in the prepare function
self._prepared = False
self.parser = None
self.parser_root = None
self.parser: ApacheParser
self.parser_root: Optional[DualBlockNode] = None
self.version = version
self._openssl_version = openssl_version
self.vhosts = None
self.vhosts: List[VirtualHost]
self.options = copy.deepcopy(self.OS_DEFAULTS)
self._enhance_func = {"redirect": self._enable_redirect,
"ensure-http-header": self._set_http_header,
@@ -286,8 +311,8 @@ class ApacheConfigurator(common.Installer):
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")
if self.options.bin:
ssl_module_location = self.options.bin
else:
logger.warning("ssl_module is statically linked but --apache-bin is "
"missing; not disabling session tickets.")
@@ -317,7 +342,7 @@ class ApacheConfigurator(common.Installer):
self._prepare_options()
# Verify Apache is installed
self._verify_exe_availability(self.option("ctl"))
self._verify_exe_availability(self.options.ctl)
# Make sure configuration is valid
self.config_test()
@@ -345,8 +370,9 @@ class ApacheConfigurator(common.Installer):
"augeaspath": self.parser.get_root_augpath(),
"ac_ast": None}
if self.USE_PARSERNODE:
self.parser_root = self.get_parsernode_root(pn_meta)
self.parsed_paths = self.parser_root.parsed_paths()
parser_root = self.get_parsernode_root(pn_meta)
self.parser_root = parser_root
self.parsed_paths = parser_root.parsed_paths()
# Check for errors in parsing files with Augeas
self.parser.check_parsing_errors("httpd.aug")
@@ -356,20 +382,20 @@ class ApacheConfigurator(common.Installer):
# 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")
warn_on_no_mod_ssl = not self.options.handle_modules
self.install_ssl_options_conf(self.mod_ssl_conf,
self.updated_mod_ssl_conf_digest,
warn_on_no_mod_ssl)
# Prevent two Apache plugins from modifying a config at once
try:
util.lock_dir_until_exit(self.option("server_root"))
util.lock_dir_until_exit(self.options.server_root)
except (OSError, errors.LockError):
logger.debug("Encountered error:", exc_info=True)
raise errors.PluginError(
"Unable to create a lock file in {0}. Are you running"
" Certbot with sufficient privileges to modify your"
" Apache configuration?".format(self.option("server_root")))
" Apache configuration?".format(self.options.server_root))
self._prepared = True
def save(self, title=None, temporary=False):
@@ -405,10 +431,10 @@ class ApacheConfigurator(common.Installer):
:raises .errors.PluginError: If unable to recover the configuration
"""
super(ApacheConfigurator, self).recovery_routine()
super().recovery_routine()
# Reload configuration after these changes take effect if needed
# ie. ApacheParser has been initialized.
if self.parser:
if hasattr(self, "parser"):
# TODO: wrap into non-implementation specific parser interface
self.parser.aug.load()
@@ -430,7 +456,7 @@ class ApacheConfigurator(common.Installer):
the function is unable to correctly revert the configuration
"""
super(ApacheConfigurator, self).rollback_checkpoints(rollback)
super().rollback_checkpoints(rollback)
self.parser.aug.load()
def _verify_exe_availability(self, exe):
@@ -444,7 +470,7 @@ class ApacheConfigurator(common.Installer):
"""Initializes the ApacheParser"""
# If user provided vhost_root value in command line, use it
return parser.ApacheParser(
self.option("server_root"), self.conf("vhost-root"),
self.options.server_root, self.conf("vhost-root"),
self.version, configurator=self)
def get_parsernode_root(self, metadata):
@@ -452,9 +478,9 @@ class ApacheConfigurator(common.Installer):
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"))
apache_vars["defines"] = apache_util.parse_defines(self.options.ctl)
apache_vars["includes"] = apache_util.parse_includes(self.options.ctl)
apache_vars["modules"] = apache_util.parse_modules(self.options.ctl)
metadata["apache_vars"] = apache_vars
with open(self.parser.loc["root"]) as f:
@@ -490,6 +516,8 @@ class ApacheConfigurator(common.Installer):
vhosts = self.choose_vhosts(domain)
for vhost in vhosts:
self._deploy_cert(vhost, cert_path, key_path, chain_path, fullchain_path)
display_util.notify("Successfully deployed certificate for {} to {}"
.format(domain, vhost.filep))
def choose_vhosts(self, domain, create_if_no_ssl=True):
"""
@@ -528,6 +556,19 @@ class ApacheConfigurator(common.Installer):
return list(matched)
def _raise_no_suitable_vhost_error(self, target_name: str):
"""
Notifies the user that Certbot could not find a vhost to secure
and raises an error.
:param str target_name: The server name that could not be mapped
:raises errors.PluginError: Raised unconditionally
"""
raise errors.PluginError(
"Certbot could not find a VirtualHost for {0} in the Apache "
"configuration. Please create a VirtualHost with a ServerName "
"matching {0} and try again.".format(target_name)
)
def _in_wildcard_scope(self, name, domain):
"""
Helper method for _vhosts_for_wildcard() that makes sure that the domain
@@ -565,12 +606,7 @@ class ApacheConfigurator(common.Installer):
dialog_output = display_ops.select_vhost_multiple(list(dialog_input))
if not dialog_output:
logger.error(
"No vhost exists with servername or alias for domain %s. "
"No vhost was selected. Please specify ServerName or ServerAlias "
"in the Apache config.",
domain)
raise errors.PluginError("No vhost selected")
self._raise_no_suitable_vhost_error(domain)
# Make sure we create SSL vhosts for the ones that are HTTP only
# if requested.
@@ -694,12 +730,7 @@ class ApacheConfigurator(common.Installer):
# Select a vhost from a list
vhost = display_ops.select_vhost(target_name, self.vhosts)
if vhost is None:
logger.error(
"No vhost exists with servername or alias of %s. "
"No vhost was selected. Please specify ServerName or ServerAlias "
"in the Apache config.",
target_name)
raise errors.PluginError("No vhost selected")
self._raise_no_suitable_vhost_error(target_name)
if temp:
return vhost
if not vhost.ssl:
@@ -1051,6 +1082,9 @@ class ApacheConfigurator(common.Installer):
:rtype: list
"""
if not self.parser_root:
raise errors.Error("This ApacheConfigurator instance is not" # pragma: no cover
" configured to use a node parser.")
vhs = []
vhosts = self.parser_root.find_blocks("VirtualHost", exclude=False)
for vhblock in vhosts:
@@ -1303,7 +1337,7 @@ class ApacheConfigurator(common.Installer):
:param boolean temp: If the change is temporary
"""
if self.option("handle_modules"):
if self.options.handle_modules:
if self.version >= (2, 4) and ("socache_shmcb_module" not in
self.parser.modules):
self.enable_mod("socache_shmcb", temp=temp)
@@ -1323,7 +1357,7 @@ class ApacheConfigurator(common.Installer):
Duplicates vhost and adds default ssl options
New vhost will reside as (nonssl_vhost.path) +
``self.option("le_vhost_ext")``
``self.options.le_vhost_ext``
.. note:: This function saves the configuration
@@ -1422,15 +1456,15 @@ class ApacheConfigurator(common.Installer):
"""
if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")):
fp = os.path.join(filesystem.realpath(self.option("vhost_root")),
fp = os.path.join(filesystem.realpath(self.options.vhost_root),
os.path.basename(non_ssl_vh_fp))
else:
# Use non-ssl filepath
fp = filesystem.realpath(non_ssl_vh_fp)
if fp.endswith(".conf"):
return fp[:-(len(".conf"))] + self.option("le_vhost_ext")
return fp + self.option("le_vhost_ext")
return fp[:-(len(".conf"))] + self.options.le_vhost_ext
return fp + self.options.le_vhost_ext
def _sift_rewrite_rule(self, line):
"""Decides whether a line should be copied to a SSL vhost.
@@ -1504,12 +1538,11 @@ class ApacheConfigurator(common.Installer):
raise errors.PluginError("Unable to write/read in make_vhost_ssl")
if sift:
reporter = zope.component.getUtility(interfaces.IReporter)
reporter.add_message(
"Some rewrite rules copied from {0} were disabled in the "
"vhost for your HTTPS site located at {1} because they have "
"the potential to create redirection loops.".format(
vhost.filep, ssl_fp), reporter.MEDIUM_PRIORITY)
display_util.notify(
f"Some rewrite rules copied from {vhost.filep} were disabled in the "
f"vhost for your HTTPS site located at {ssl_fp} because they have "
"the potential to create redirection loops."
)
self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0")
self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0")
@@ -1838,13 +1871,13 @@ class ApacheConfigurator(common.Installer):
if options:
msg_enhancement += ": " + options
msg = msg_tmpl.format(domain, msg_enhancement)
logger.warning(msg)
logger.error(msg)
raise errors.PluginError(msg)
try:
for vhost in vhosts:
func(vhost, options)
except errors.PluginError:
logger.warning("Failed %s for %s", enhancement, domain)
logger.error("Failed %s for %s", enhancement, domain)
raise
def _autohsts_increase(self, vhost, id_str, nextstep):
@@ -2274,7 +2307,7 @@ class ApacheConfigurator(common.Installer):
addr in self._get_proposed_addrs(ssl_vhost)),
servername, serveralias,
" ".join(rewrite_rule_args),
self.option("logs_root")))
self.options.logs_root))
def _write_out_redirect(self, ssl_vhost, text):
# This is the default name
@@ -2286,7 +2319,7 @@ class ApacheConfigurator(common.Installer):
if len(ssl_vhost.name) < (255 - (len(redirect_filename) + 1)):
redirect_filename = "le-redirect-%s.conf" % ssl_vhost.name
redirect_filepath = os.path.join(self.option("vhost_root"),
redirect_filepath = os.path.join(self.options.vhost_root,
redirect_filename)
# Register the new file that will be created
@@ -2368,7 +2401,7 @@ class ApacheConfigurator(common.Installer):
vhost.enabled = True
return
def enable_mod(self, mod_name, temp=False):
def enable_mod(self, mod_name, temp=False): # pylint: disable=unused-argument
"""Enables module in Apache.
Both enables and reloads Apache so module is active.
@@ -2406,19 +2439,18 @@ class ApacheConfigurator(common.Installer):
"""
try:
util.run_script(self.option("restart_cmd"))
util.run_script(self.options.restart_cmd)
except errors.SubprocessError as err:
logger.info("Unable to restart apache using %s",
self.option("restart_cmd"))
alt_restart = self.option("restart_cmd_alt")
logger.warning("Unable to restart apache using %s",
self.options.restart_cmd)
alt_restart = self.options.restart_cmd_alt
if alt_restart:
logger.debug("Trying alternative restart command: %s",
alt_restart)
# There is an alternative restart command available
# This usually is "restart" verb while original is "graceful"
try:
util.run_script(self.option(
"restart_cmd_alt"))
util.run_script(self.options.restart_cmd_alt)
return
except errors.SubprocessError as secerr:
error = str(secerr)
@@ -2433,7 +2465,7 @@ class ApacheConfigurator(common.Installer):
"""
try:
util.run_script(self.option("conftest_cmd"))
util.run_script(self.options.conftest_cmd)
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
@@ -2449,11 +2481,11 @@ class ApacheConfigurator(common.Installer):
"""
try:
stdout, _ = util.run_script(self.option("version_cmd"))
stdout, _ = util.run_script(self.options.version_cmd)
except errors.SubprocessError:
raise errors.PluginError(
"Unable to run %s -v" %
self.option("version_cmd"))
self.options.version_cmd)
regex = re.compile(r"Apache/([0-9\.]*)", re.IGNORECASE)
matches = regex.findall(stdout)
@@ -2473,6 +2505,11 @@ class ApacheConfigurator(common.Installer):
version=".".join(str(i) for i in self.version))
)
def auth_hint(self, failed_achalls): # pragma: no cover
return ("The Certificate Authority failed to verify the temporary Apache configuration "
"changes made by Certbot. Ensure that the listed domains point to this Apache "
"server and that it is accessible from the internet.")
###########################################################################
# Challenges Section
###########################################################################
@@ -2566,7 +2603,7 @@ class ApacheConfigurator(common.Installer):
msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a "
"domain {0} for enabling AutoHSTS enhancement.")
msg = msg_tmpl.format(d)
logger.warning(msg)
logger.error(msg)
raise errors.PluginError(msg)
for vh in vhosts:
try:
@@ -2652,7 +2689,7 @@ class ApacheConfigurator(common.Installer):
except errors.PluginError:
msg = ("Could not find VirtualHost with ID {0}, disabling "
"AutoHSTS for this VirtualHost").format(id_str)
logger.warning(msg)
logger.error(msg)
# Remove the orphaned AutoHSTS entry from pluginstorage
self._autohsts.pop(id_str)
continue
@@ -2692,7 +2729,7 @@ class ApacheConfigurator(common.Installer):
except errors.PluginError:
msg = ("VirtualHost with id {} was not found, unable to "
"make HSTS max-age permanent.").format(id_str)
logger.warning(msg)
logger.error(msg)
self._autohsts.pop(id_str)
continue
if self._autohsts_vhost_in_lineage(vhost, lineage):

View File

@@ -119,7 +119,7 @@ def _vhost_menu(domain, vhosts):
"guidance in non-interactive mode. Certbot may need "
"vhosts to be explicitly labelled with ServerName or "
"ServerAlias directives.".format(domain))
logger.warning(msg)
logger.error(msg)
raise errors.MissingCommandlineFlag(msg)
return code, tag

View File

@@ -47,7 +47,7 @@ class ApacheHttp01(common.ChallengePerformer):
"""
def __init__(self, *args, **kwargs):
super(ApacheHttp01, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
self.challenge_conf_pre = os.path.join(
self.configurator.conf("challenge-location"),
"le_http_01_challenge_pre.conf")

View File

@@ -238,7 +238,7 @@ class CommentNode(ParserNode, metaclass=abc.ABCMeta):
created or changed after the last save. Default: False.
:type dirty: bool
"""
super(CommentNode, self).__init__(ancestor=kwargs['ancestor'],
super().__init__(ancestor=kwargs['ancestor'],
dirty=kwargs.get('dirty', False),
filepath=kwargs['filepath'],
metadata=kwargs.get('metadata', {})) # pragma: no cover
@@ -302,7 +302,7 @@ class DirectiveNode(ParserNode, metaclass=abc.ABCMeta):
:type enabled: bool
"""
super(DirectiveNode, self).__init__(ancestor=kwargs['ancestor'],
super().__init__(ancestor=kwargs['ancestor'],
dirty=kwargs.get('dirty', False),
filepath=kwargs['filepath'],
metadata=kwargs.get('metadata', {})) # pragma: no cover

View File

@@ -26,7 +26,7 @@ class Addr(common.Addr):
def __hash__(self): # pylint: disable=useless-super-delegation
# Python 3 requires explicit overridden for __hash__ if __eq__ or
# __cmp__ is overridden. See https://bugs.python.org/issue2235
return super(Addr, self).__hash__()
return super().__hash__()
def _addr_less_specific(self, addr):
"""Returns if addr.get_addr() is more specific than self.get_addr()."""

View File

@@ -3,13 +3,14 @@ import zope.interface
from certbot import interfaces
from certbot_apache._internal import configurator
from certbot_apache._internal.configurator import OsOptions
@zope.interface.provider(interfaces.IPluginFactory)
class ArchConfigurator(configurator.ApacheConfigurator):
"""Arch Linux specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
OS_DEFAULTS = OsOptions(
server_root="/etc/httpd",
vhost_root="/etc/httpd/conf",
vhost_files="*.conf",
@@ -18,11 +19,5 @@ class ArchConfigurator(configurator.ApacheConfigurator):
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf",
bin=None,
)

View File

@@ -12,6 +12,7 @@ from certbot.errors import MisconfigurationError
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
from certbot_apache._internal.configurator import OsOptions
logger = logging.getLogger(__name__)
@@ -20,7 +21,7 @@ logger = logging.getLogger(__name__)
class CentOSConfigurator(configurator.ApacheConfigurator):
"""CentOS specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
OS_DEFAULTS = OsOptions(
server_root="/etc/httpd",
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
@@ -30,13 +31,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf.d",
bin=None,
)
def config_test(self):
@@ -51,7 +46,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
fedora = os_info[0].lower() == "fedora"
try:
super(CentOSConfigurator, self).config_test()
super().config_test()
except errors.MisconfigurationError:
if fedora:
self._try_restart_fedora()
@@ -69,20 +64,22 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
raise errors.MisconfigurationError(str(err))
# Finish with actual config check to see if systemctl restart helped
super(CentOSConfigurator, self).config_test()
super().config_test()
def _prepare_options(self):
"""
Override the options dictionary initialization in order to support
alternative restart cmd used in CentOS.
"""
super(CentOSConfigurator, self)._prepare_options()
cast(List[str], self.options["restart_cmd_alt"])[0] = self.option("ctl")
super()._prepare_options()
if not self.options.restart_cmd_alt: # pragma: no cover
raise ValueError("OS option restart_cmd_alt must be set for CentOS.")
self.options.restart_cmd_alt[0] = self.options.ctl
def get_parser(self):
"""Initializes the ApacheParser"""
return CentOSParser(
self.option("server_root"), self.option("vhost_root"),
self.options.server_root, self.options.vhost_root,
self.version, configurator=self)
def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ
@@ -91,7 +88,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
has "LoadModule ssl_module..." before parsing the VirtualHost configuration
that was created by Certbot
"""
super(CentOSConfigurator, self)._deploy_cert(*args, **kwargs)
super()._deploy_cert(*args, **kwargs)
if self.version < (2, 4, 0):
self._deploy_loadmodule_ssl_if_needed()
@@ -119,8 +116,9 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
else:
loadmod_args = path_args
if self.parser.not_modssl_ifmodule(noarg_path): # pylint: disable=no-member
if self.parser.loc["default"] in noarg_path:
centos_parser: CentOSParser = cast(CentOSParser, self.parser)
if centos_parser.not_modssl_ifmodule(noarg_path):
if centos_parser.loc["default"] in noarg_path:
# LoadModule already in the main configuration file
if ("ifmodule/" in noarg_path.lower() or
"ifmodule[1]" in noarg_path.lower()):
@@ -168,12 +166,12 @@ class CentOSParser(parser.ApacheParser):
def __init__(self, *args, **kwargs):
# CentOS specific configuration file for Apache
self.sysconfig_filep = "/etc/sysconfig/httpd"
super(CentOSParser, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def update_runtime_variables(self):
""" Override for update_runtime_variables for custom parsing """
# Opportunistic, works if SELinux not enforced
super(CentOSParser, self).update_runtime_variables()
super().update_runtime_variables()
self.parse_sysconfig_var()
def parse_sysconfig_var(self):

View File

@@ -3,26 +3,19 @@ import zope.interface
from certbot import interfaces
from certbot_apache._internal import configurator
from certbot_apache._internal.configurator import OsOptions
@zope.interface.provider(interfaces.IPluginFactory)
class DarwinConfigurator(configurator.ApacheConfigurator):
"""macOS specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
server_root="/etc/apache2",
OS_DEFAULTS = OsOptions(
vhost_root="/etc/apache2/other",
vhost_files="*.conf",
logs_root="/var/log/apache2",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/other",
bin=None,
)

View File

@@ -10,6 +10,7 @@ from certbot.compat import filesystem
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal.configurator import OsOptions
logger = logging.getLogger(__name__)
@@ -18,22 +19,11 @@ logger = logging.getLogger(__name__)
class DebianConfigurator(configurator.ApacheConfigurator):
"""Debian specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
server_root="/etc/apache2",
vhost_root="/etc/apache2/sites-available",
vhost_files="*",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
OS_DEFAULTS = OsOptions(
enmod="a2enmod",
dismod="a2dismod",
le_vhost_ext="-le-ssl.conf",
handle_modules=True,
handle_sites=True,
challenge_location="/etc/apache2",
bin=None,
)
def enable_site(self, vhost):
@@ -58,7 +48,7 @@ class DebianConfigurator(configurator.ApacheConfigurator):
if not os.path.isdir(os.path.dirname(enabled_path)):
# For some reason, sites-enabled / sites-available do not exist
# Call the parent method
return super(DebianConfigurator, self).enable_site(vhost)
return super().enable_site(vhost)
self.reverter.register_file_creation(False, enabled_path)
try:
os.symlink(vhost.filep, enabled_path)
@@ -68,7 +58,7 @@ class DebianConfigurator(configurator.ApacheConfigurator):
# Already in shape
vhost.enabled = True
return None
logger.warning(
logger.error(
"Could not symlink %s to %s, got error: %s", enabled_path,
vhost.filep, err.strerror)
errstring = ("Encountered error while trying to enable a " +
@@ -132,11 +122,11 @@ class DebianConfigurator(configurator.ApacheConfigurator):
# Generate reversal command.
# Try to be safe here... check that we can probably reverse before
# applying enmod command
if not util.exe_exists(self.option("dismod")):
if not util.exe_exists(self.options.dismod):
raise errors.MisconfigurationError(
"Unable to find a2dismod, please make sure a2enmod and "
"a2dismod are configured correctly for certbot.")
self.reverter.register_undo_command(
temp, [self.option("dismod"), "-f", mod_name])
util.run_script([self.option("enmod"), mod_name])
temp, [self.options.dismod, "-f", mod_name])
util.run_script([self.options.enmod, mod_name])

View File

@@ -1,7 +1,4 @@
""" Distribution specific override class for Fedora 29+ """
from typing import cast
from typing import List
import zope.interface
from certbot import errors
@@ -10,13 +7,14 @@ from certbot import util
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
from certbot_apache._internal.configurator import OsOptions
@zope.interface.provider(interfaces.IPluginFactory)
class FedoraConfigurator(configurator.ApacheConfigurator):
"""Fedora 29+ specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
OS_DEFAULTS = OsOptions(
server_root="/etc/httpd",
vhost_root="/etc/httpd/conf.d",
vhost_files="*.conf",
@@ -26,13 +24,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/httpd/conf.d",
bin=None,
)
def config_test(self):
@@ -43,14 +35,14 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
during the first (re)start of httpd.
"""
try:
super(FedoraConfigurator, self).config_test()
super().config_test()
except errors.MisconfigurationError:
self._try_restart_fedora()
def get_parser(self):
"""Initializes the ApacheParser"""
return FedoraParser(
self.option("server_root"), self.option("vhost_root"),
self.options.server_root, self.options.vhost_root,
self.version, configurator=self)
def _try_restart_fedora(self):
@@ -63,7 +55,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
raise errors.MisconfigurationError(str(err))
# Finish with actual config check to see if systemctl restart helped
super(FedoraConfigurator, self).config_test()
super().config_test()
def _prepare_options(self):
"""
@@ -71,10 +63,12 @@ class FedoraConfigurator(configurator.ApacheConfigurator):
instead of httpd and so take advantages of this new bash script in newer versions
of Fedora to restart httpd.
"""
super(FedoraConfigurator, self)._prepare_options()
cast(List[str], self.options["restart_cmd"])[0] = 'apachectl'
cast(List[str], self.options["restart_cmd_alt"])[0] = 'apachectl'
cast(List[str], self.options["conftest_cmd"])[0] = 'apachectl'
super()._prepare_options()
self.options.restart_cmd[0] = 'apachectl'
if not self.options.restart_cmd_alt: # pragma: no cover
raise ValueError("OS option restart_cmd_alt must be set for Fedora.")
self.options.restart_cmd_alt[0] = 'apachectl'
self.options.conftest_cmd[0] = 'apachectl'
class FedoraParser(parser.ApacheParser):
@@ -82,12 +76,12 @@ class FedoraParser(parser.ApacheParser):
def __init__(self, *args, **kwargs):
# Fedora 29+ specific configuration file for Apache
self.sysconfig_filep = "/etc/sysconfig/httpd"
super(FedoraParser, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def update_runtime_variables(self):
""" Override for update_runtime_variables for custom parsing """
# Opportunistic, works if SELinux not enforced
super(FedoraParser, self).update_runtime_variables()
super().update_runtime_variables()
self._parse_sysconfig_var()
def _parse_sysconfig_var(self):

View File

@@ -1,36 +1,23 @@
""" Distribution specific override class for Gentoo Linux """
from typing import cast
from typing import List
import zope.interface
from certbot import interfaces
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
from certbot_apache._internal.configurator import OsOptions
@zope.interface.provider(interfaces.IPluginFactory)
class GentooConfigurator(configurator.ApacheConfigurator):
"""Gentoo specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
OS_DEFAULTS = OsOptions(
server_root="/etc/apache2",
vhost_root="/etc/apache2/vhosts.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
ctl="apache2ctl",
version_cmd=['apache2ctl', '-v'],
restart_cmd=['apache2ctl', 'graceful'],
restart_cmd_alt=['apache2ctl', 'restart'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
bin=None,
)
def _prepare_options(self):
@@ -38,13 +25,15 @@ class GentooConfigurator(configurator.ApacheConfigurator):
Override the options dictionary initialization in order to support
alternative restart cmd used in Gentoo.
"""
super(GentooConfigurator, self)._prepare_options()
cast(List[str], self.options["restart_cmd_alt"])[0] = self.option("ctl")
super()._prepare_options()
if not self.options.restart_cmd_alt: # pragma: no cover
raise ValueError("OS option restart_cmd_alt must be set for Gentoo.")
self.options.restart_cmd_alt[0] = self.options.ctl
def get_parser(self):
"""Initializes the ApacheParser"""
return GentooParser(
self.option("server_root"), self.option("vhost_root"),
self.options.server_root, self.options.vhost_root,
self.version, configurator=self)
@@ -53,7 +42,7 @@ class GentooParser(parser.ApacheParser):
def __init__(self, *args, **kwargs):
# Gentoo specific configuration file for Apache2
self.apacheconfig_filep = "/etc/conf.d/apache2"
super(GentooParser, self).__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
def update_runtime_variables(self):
""" Override for update_runtime_variables for custom parsing """
@@ -69,7 +58,7 @@ class GentooParser(parser.ApacheParser):
def update_modules(self):
"""Get loaded modules from httpd process, and add them to DOM"""
mod_cmd = [self.configurator.option("ctl"), "modules"]
mod_cmd = [self.configurator.options.ctl, "modules"]
matches = apache_util.parse_from_subprocess(mod_cmd, r"(.*)_module")
for mod in matches:
self.add_mod(mod.strip())

View File

@@ -3,26 +3,21 @@ import zope.interface
from certbot import interfaces
from certbot_apache._internal import configurator
from certbot_apache._internal.configurator import OsOptions
@zope.interface.provider(interfaces.IPluginFactory)
class OpenSUSEConfigurator(configurator.ApacheConfigurator):
"""OpenSUSE specific ApacheConfigurator override class"""
OS_DEFAULTS = dict(
server_root="/etc/apache2",
OS_DEFAULTS = OsOptions(
vhost_root="/etc/apache2/vhosts.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
enmod="a2enmod",
dismod="a2dismod",
le_vhost_ext="-le-ssl.conf",
handle_modules=False,
handle_sites=False,
challenge_location="/etc/apache2/vhosts.d",
bin=None,
)

View File

@@ -5,12 +5,18 @@ import logging
import re
from typing import Dict
from typing import List
from typing import Optional
from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import constants
try:
from augeas import Augeas
except ImportError: # pragma: no cover
Augeas = None # type: ignore
logger = logging.getLogger(__name__)
@@ -39,8 +45,7 @@ class ApacheParser:
self.configurator = configurator
# Initialize augeas
self.aug = None
self.init_augeas()
self.aug = init_augeas()
if not self.check_aug_version():
raise errors.NotSupportedError(
@@ -48,7 +53,7 @@ class ApacheParser:
"version 1.2.0 or higher, please make sure you have you have "
"those installed.")
self.modules: Dict[str, str] = {}
self.modules: Dict[str, Optional[str]] = {}
self.parser_paths: Dict[str, List[str]] = {}
self.variables: Dict[str, str] = {}
@@ -76,30 +81,13 @@ class ApacheParser:
# Must also attempt to parse additional virtual host root
if vhostroot:
self.parse_file(os.path.abspath(vhostroot) + "/" +
self.configurator.option("vhost_files"))
self.configurator.options.vhost_files)
# check to see if there were unparsed define statements
if version < (2, 4):
if self.find_dir("Define", exclude=False):
raise errors.PluginError("Error parsing runtime variables")
def init_augeas(self):
""" Initialize the actual Augeas instance """
try:
import augeas
except ImportError: # pragma: no cover
raise errors.NoInstallationError("Problem in Augeas installation")
self.aug = augeas.Augeas(
# specify a directory to load our preferred lens from
loadpath=constants.AUGEAS_LENS_DIR,
# Do not save backup (we do it ourselves), do not load
# anything by default
flags=(augeas.Augeas.NONE |
augeas.Augeas.NO_MODL_AUTOLOAD |
augeas.Augeas.ENABLE_SPAN))
def check_parsing_errors(self, lens):
"""Verify Augeas can parse all of the lens files.
@@ -294,7 +282,7 @@ class ApacheParser:
def update_defines(self):
"""Updates the dictionary of known variables in the configuration"""
self.variables = apache_util.parse_defines(self.configurator.option("ctl"))
self.variables = apache_util.parse_defines(self.configurator.options.ctl)
def update_includes(self):
"""Get includes from httpd process, and add them to DOM if needed"""
@@ -304,7 +292,7 @@ class ApacheParser:
# configuration files
_ = self.find_dir("Include")
matches = apache_util.parse_includes(self.configurator.option("ctl"))
matches = apache_util.parse_includes(self.configurator.options.ctl)
if matches:
for i in matches:
if not self.parsed_in_current(i):
@@ -313,7 +301,7 @@ class ApacheParser:
def update_modules(self):
"""Get loaded modules from httpd process, and add them to DOM"""
matches = apache_util.parse_modules(self.configurator.option("ctl"))
matches = apache_util.parse_modules(self.configurator.options.ctl)
for mod in matches:
self.add_mod(mod.strip())
@@ -949,3 +937,19 @@ def get_aug_path(file_path):
"""
return "/files%s" % file_path
def init_augeas() -> Augeas:
""" Initialize the actual Augeas instance """
if not Augeas: # pragma: no cover
raise errors.NoInstallationError("Problem in Augeas installation")
return Augeas(
# specify a directory to load our preferred lens from
loadpath=constants.AUGEAS_LENS_DIR,
# Do not save backup (we do it ourselves), do not load
# anything by default
flags=(Augeas.NONE |
Augeas.NO_MODL_AUTOLOAD |
Augeas.ENABLE_SPAN))

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.6.0
acme[dev]==1.8.0
certbot[dev]==1.10.1

View File

@@ -1,13 +1,13 @@
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.29.0',
'certbot>=1.6.0',
'acme>=1.8.0',
'certbot>=1.10.1',
'python-augeas',
'setuptools>=39.0.1',
'zope.component',
@@ -24,7 +24,7 @@ setup(
description="Apache plugin for Certbot",
url='https://github.com/letsencrypt/letsencrypt',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -29,7 +29,7 @@ class AugeasParserNodeTest(util.ApacheTest): # pylint: disable=too-many-public-
"""Test AugeasParserNode using available test configurations"""
def setUp(self): # pylint: disable=arguments-differ
super(AugeasParserNodeTest, self).setUp()
super().setUp()
with mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root") as mock_parsernode:
mock_parsernode.side_effect = _get_augeasnode_mock(

View File

@@ -18,7 +18,7 @@ class AutoHSTSTest(util.ApacheTest):
# pylint: disable=protected-access
def setUp(self): # pylint: disable=arguments-differ
super(AutoHSTSTest, self).setUp()
super().setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
@@ -146,7 +146,7 @@ class AutoHSTSTest(util.ApacheTest):
@mock.patch("certbot_apache._internal.display_ops.select_vhost")
def test_autohsts_no_ssl_vhost(self, mock_select):
mock_select.return_value = self.vh_truth[0]
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log:
self.assertRaises(errors.PluginError,
self.config.enable_autohsts,
mock.MagicMock(), "invalid.example.com")
@@ -179,7 +179,7 @@ class AutoHSTSTest(util.ApacheTest):
self.config._autohsts_fetch_state()
self.config._autohsts["orphan_id"] = {"laststep": 999, "timestamp": 0}
self.config._autohsts_save_state()
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log:
self.config.deploy_autohsts(mock.MagicMock())
self.assertTrue(mock_log.called)
self.assertTrue(

View File

@@ -1,5 +1,6 @@
"""Test for certbot_apache._internal.configurator for CentOS 6 overrides"""
import unittest
from unittest import mock
from certbot.compat import os
from certbot.errors import MisconfigurationError
@@ -36,9 +37,9 @@ class CentOS6Tests(util.ApacheTest):
test_dir = "centos6_apache/apache"
config_root = "centos6_apache/apache/httpd"
vhost_root = "centos6_apache/apache/httpd/conf.d"
super(CentOS6Tests, self).setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
super().setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
@@ -65,7 +66,8 @@ class CentOS6Tests(util.ApacheTest):
raise Exception("Missed: %s" % vhost) # pragma: no cover
self.assertEqual(found, 2)
def test_loadmod_default(self):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_loadmod_default(self, unused_mock_notify):
ssl_loadmods = self.config.parser.find_dir(
"LoadModule", "ssl_module", exclude=False)
self.assertEqual(len(ssl_loadmods), 1)
@@ -95,7 +97,8 @@ class CentOS6Tests(util.ApacheTest):
ifmod_args = self.config.parser.get_all_args(lm[:-17])
self.assertTrue("!mod_ssl.c" in ifmod_args)
def test_loadmod_multiple(self):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_loadmod_multiple(self, unused_mock_notify):
sslmod_args = ["ssl_module", "modules/mod_ssl.so"]
# Adds another LoadModule to main httpd.conf in addtition to ssl.conf
self.config.parser.add_dir(self.config.parser.loc["default"], "LoadModule",
@@ -115,7 +118,8 @@ class CentOS6Tests(util.ApacheTest):
for mod in post_loadmods:
self.assertTrue(self.config.parser.not_modssl_ifmodule(mod)) #pylint: disable=no-member
def test_loadmod_rootconf_exists(self):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_loadmod_rootconf_exists(self, unused_mock_notify):
sslmod_args = ["ssl_module", "modules/mod_ssl.so"]
rootconf_ifmod = self.config.parser.get_ifmod(
parser.get_aug_path(self.config.parser.loc["default"]),
@@ -142,7 +146,8 @@ class CentOS6Tests(util.ApacheTest):
self.config.parser.get_all_args(mods[0][:-7]),
sslmod_args)
def test_neg_loadmod_already_on_path(self):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_neg_loadmod_already_on_path(self, unused_mock_notify):
loadmod_args = ["ssl_module", "modules/mod_ssl.so"]
ifmod = self.config.parser.get_ifmod(
self.vh_truth[1].path, "!mod_ssl.c", beginning=True)
@@ -185,7 +190,8 @@ class CentOS6Tests(util.ApacheTest):
# Make sure that none was changed
self.assertEqual(pre_matches, post_matches)
def test_loadmod_not_found(self):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_loadmod_not_found(self, unused_mock_notify):
# Remove all existing LoadModule ssl_module... directives
orig_loadmods = self.config.parser.find_dir("LoadModule",
"ssl_module",

View File

@@ -41,9 +41,9 @@ class FedoraRestartTest(util.ApacheTest):
test_dir = "centos7_apache/apache"
config_root = "centos7_apache/apache/httpd"
vhost_root = "centos7_apache/apache/httpd/conf.d"
super(FedoraRestartTest, self).setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
super().setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="fedora_old")
@@ -96,9 +96,9 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
test_dir = "centos7_apache/apache"
config_root = "centos7_apache/apache/httpd"
vhost_root = "centos7_apache/apache/httpd/conf.d"
super(MultipleVhostsTestCentOS, self).setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
super().setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,

View File

@@ -11,7 +11,7 @@ class ComplexParserTest(util.ParserTest):
"""Apache Parser Test."""
def setUp(self): # pylint: disable=arguments-differ
super(ComplexParserTest, self).setUp(
super().setUp(
"complex_parsing", "complex_parsing")
self.setup_variables()

View File

@@ -16,7 +16,7 @@ class ConfiguratorReverterTest(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ
super(ConfiguratorReverterTest, self).setUp()
super().setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir)

View File

@@ -30,7 +30,7 @@ class MultipleVhostsTest(util.ApacheTest):
"""Test two standard well-configured HTTP vhosts."""
def setUp(self): # pylint: disable=arguments-differ
super(MultipleVhostsTest, self).setUp()
super().setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
@@ -103,9 +103,9 @@ class MultipleVhostsTest(util.ApacheTest):
"handle_modules", "handle_sites", "ctl"]
exp = {}
for k in ApacheConfigurator.OS_DEFAULTS:
for k in ApacheConfigurator.OS_DEFAULTS.__dict__.keys():
if k in parserargs:
exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k]
exp[k.replace("_", "-")] = getattr(ApacheConfigurator.OS_DEFAULTS, k)
# Special cases
exp["vhost-root"] = None
@@ -128,14 +128,13 @@ class MultipleVhostsTest(util.ApacheTest):
def test_all_configurators_defaults_defined(self):
from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES
from certbot_apache._internal.configurator import ApacheConfigurator
parameters = set(ApacheConfigurator.OS_DEFAULTS.keys())
parameters = set(ApacheConfigurator.OS_DEFAULTS.__dict__.keys())
for cls in OVERRIDE_CLASSES.values():
self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys())))
self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.__dict__.keys())))
def test_constant(self):
self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in
self.config.option("server_root"))
self.assertEqual(self.config.option("nonexistent"), None)
self.config.options.server_root)
@certbot_util.patch_get_utility()
def test_get_all_names(self, mock_getutility):
@@ -338,7 +337,8 @@ class MultipleVhostsTest(util.ApacheTest):
vhosts = self.config._non_default_vhosts(self.config.vhosts)
self.assertEqual(len(vhosts), 10)
def test_deploy_cert_enable_new_vhost(self):
@mock.patch('certbot_apache._internal.configurator.display_util.notify')
def test_deploy_cert_enable_new_vhost(self, unused_mock_notify):
# Create
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
self.config.parser.modules["ssl_module"] = None
@@ -376,7 +376,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \
# pragma: no cover
def test_deploy_cert(self):
@mock.patch('certbot_apache._internal.configurator.display_util.notify')
def test_deploy_cert(self, unused_mock_notify):
self.config.parser.modules["ssl_module"] = None
self.config.parser.modules["mod_ssl.c"] = None
self.config.parser.modules["socache_shmcb_module"] = None
@@ -892,7 +893,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.enhance, "certbot.demo", "unknown_enhancement")
def test_enhance_no_ssl_vhost(self):
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log:
self.assertRaises(errors.PluginError, self.config.enhance,
"certbot.demo", "redirect")
# Check that correct logger.warning was printed
@@ -1291,7 +1292,8 @@ class MultipleVhostsTest(util.ApacheTest):
os.path.basename(inc_path) in self.config.parser.existing_paths[
os.path.dirname(inc_path)])
def test_deploy_cert_not_parsed_path(self):
@mock.patch('certbot_apache._internal.configurator.display_util.notify')
def test_deploy_cert_not_parsed_path(self, unused_mock_notify):
# Make sure that we add include to root config for vhosts when
# handle-sites is false
self.config.parser.modules["ssl_module"] = None
@@ -1387,7 +1389,8 @@ class MultipleVhostsTest(util.ApacheTest):
self.assertEqual(vhs[0], self.vh_truth[7])
def test_deploy_cert_wildcard(self):
@mock.patch('certbot_apache._internal.configurator.display_util.notify')
def test_deploy_cert_wildcard(self, unused_mock_notify):
# pylint: disable=protected-access
mock_choose_vhosts = mock.MagicMock()
mock_choose_vhosts.return_value = [self.vh_truth[7]]
@@ -1477,9 +1480,9 @@ class AugeasVhostsTest(util.ApacheTest):
td = "debian_apache_2_4/augeas_vhosts"
cr = "debian_apache_2_4/augeas_vhosts/apache2"
vr = "debian_apache_2_4/augeas_vhosts/apache2/sites-available"
super(AugeasVhostsTest, self).setUp(test_dir=td,
config_root=cr,
vhost_root=vr)
super().setUp(test_dir=td,
config_root=cr,
vhost_root=vr)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir,
@@ -1556,9 +1559,9 @@ class MultiVhostsTest(util.ApacheTest):
td = "debian_apache_2_4/multi_vhosts"
cr = "debian_apache_2_4/multi_vhosts/apache2"
vr = "debian_apache_2_4/multi_vhosts/apache2/sites-available"
super(MultiVhostsTest, self).setUp(test_dir=td,
config_root=cr,
vhost_root=vr)
super().setUp(test_dir=td,
config_root=cr,
vhost_root=vr)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path,
@@ -1607,8 +1610,8 @@ class MultiVhostsTest(util.ApacheTest):
self.assertEqual(self.config._get_new_vh_path(without_index, both),
with_index_2[0])
@certbot_util.patch_get_utility()
def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_get_utility):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_notify):
self.config.parser.modules["rewrite_module"] = None
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4])
@@ -1624,11 +1627,11 @@ class MultiVhostsTest(util.ApacheTest):
"\"http://new.example.com/docs/$1\" [R,L]")
self.assertTrue(commented_rewrite_rule in conf_text)
self.assertTrue(uncommented_rewrite_rule in conf_text)
mock_get_utility().add_message.assert_called_once_with(mock.ANY,
mock.ANY)
self.assertEqual(mock_notify.call_count, 1)
self.assertIn("Some rewrite rules", mock_notify.call_args[0][0])
@certbot_util.patch_get_utility()
def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_get_utility):
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_notify):
self.config.parser.modules["rewrite_module"] = None
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3])
@@ -1653,15 +1656,15 @@ class MultiVhostsTest(util.ApacheTest):
self.assertTrue(commented_cond1 in conf_line_set)
self.assertTrue(commented_cond2 in conf_line_set)
self.assertTrue(commented_rewrite_rule in conf_line_set)
mock_get_utility().add_message.assert_called_once_with(mock.ANY,
mock.ANY)
self.assertEqual(mock_notify.call_count, 1)
self.assertIn("Some rewrite rules", mock_notify.call_args[0][0])
class InstallSslOptionsConfTest(util.ApacheTest):
"""Test that the options-ssl-nginx.conf file is installed and updated properly."""
def setUp(self): # pylint: disable=arguments-differ
super(InstallSslOptionsConfTest, self).setUp()
super().setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
@@ -1774,7 +1777,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
# ssl_module statically linked
self.config._openssl_version = None
self.config.parser.modules['ssl_module'] = None
self.config.options['bin'] = '/fake/path/to/httpd'
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
@@ -1810,7 +1813,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
# When ssl_module is statically linked but --apache-bin not provided
self.config._openssl_version = None
self.config.options['bin'] = 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)

View File

@@ -20,7 +20,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
_multiprocess_can_split_ = True
def setUp(self): # pylint: disable=arguments-differ
super(MultipleVhostsTestDebian, self).setUp()
super().setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="debian")
@@ -49,10 +49,11 @@ class MultipleVhostsTestDebian(util.ApacheTest):
@mock.patch("certbot.util.run_script")
@mock.patch("certbot.util.exe_exists")
@mock.patch("certbot_apache._internal.apache_util.subprocess.Popen")
def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script):
mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "")
mock_popen().returncode = 0
@mock.patch("certbot_apache._internal.apache_util.subprocess.run")
def test_enable_mod(self, mock_run, mock_exe_exists, mock_run_script):
mock_run.return_value.stdout = "Define: DUMP_RUN_CFG"
mock_run.return_value.stderr = ""
mock_run.return_value.returncode = 0
mock_exe_exists.return_value = True
self.config.enable_mod("ssl")

View File

@@ -46,9 +46,9 @@ class FedoraRestartTest(util.ApacheTest):
test_dir = "centos7_apache/apache"
config_root = "centos7_apache/apache/httpd"
vhost_root = "centos7_apache/apache/httpd/conf.d"
super(FedoraRestartTest, self).setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
super().setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,
os_info="fedora")
@@ -90,9 +90,9 @@ class MultipleVhostsTestFedora(util.ApacheTest):
test_dir = "centos7_apache/apache"
config_root = "centos7_apache/apache/httpd"
vhost_root = "centos7_apache/apache/httpd/conf.d"
super(MultipleVhostsTestFedora, self).setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
super().setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir,

View File

@@ -50,9 +50,9 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
test_dir = "gentoo_apache/apache"
config_root = "gentoo_apache/apache/apache2"
vhost_root = "gentoo_apache/apache/apache2/vhosts.d"
super(MultipleVhostsTestGentoo, self).setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
super().setUp(test_dir=test_dir,
config_root=config_root,
vhost_root=vhost_root)
# pylint: disable=line-too-long
with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_runtime_variables"):

View File

@@ -24,7 +24,7 @@ class ApacheHttp01Test(util.ApacheTest):
"""Test for certbot_apache._internal.http_01.ApacheHttp01."""
def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ
super(ApacheHttp01Test, self).setUp(*args, **kwargs)
super().setUp(*args, **kwargs)
self.account_key = self.rsa512jwk
self.achalls: List[achallenges.KeyAuthorizationAnnotatedChallenge] = []

View File

@@ -16,7 +16,7 @@ class BasicParserTest(util.ParserTest):
"""Apache Parser Test."""
def setUp(self): # pylint: disable=arguments-differ
super(BasicParserTest, self).setUp()
super().setUp()
def tearDown(self):
shutil.rmtree(self.temp_dir)
@@ -305,19 +305,19 @@ class BasicParserTest(util.ParserTest):
self.assertRaises(
errors.PluginError, self.parser.update_runtime_variables)
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option")
@mock.patch("certbot_apache._internal.apache_util.subprocess.Popen")
def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt):
mock_popen.side_effect = OSError
mock_opt.return_value = "nonexistent"
@mock.patch("certbot_apache._internal.apache_util.subprocess.run")
def test_update_runtime_vars_bad_ctl(self, mock_run):
mock_run.side_effect = OSError
self.assertRaises(
errors.MisconfigurationError,
self.parser.update_runtime_variables)
@mock.patch("certbot_apache._internal.apache_util.subprocess.Popen")
def test_update_runtime_vars_bad_exit(self, mock_popen):
mock_popen().communicate.return_value = ("", "")
mock_popen.returncode = -1
@mock.patch("certbot_apache._internal.apache_util.subprocess.run")
def test_update_runtime_vars_bad_exit(self, mock_run):
mock_proc = mock_run.return_value
mock_proc.stdout = ""
mock_proc.stderr = ""
mock_proc.returncode = -1
self.assertRaises(
errors.MisconfigurationError,
self.parser.update_runtime_variables)
@@ -332,14 +332,14 @@ class BasicParserTest(util.ParserTest):
class ParserInitTest(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ
super(ParserInitTest, self).setUp()
super().setUp()
def tearDown(self):
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
@mock.patch("certbot_apache._internal.parser.ApacheParser.init_augeas")
@mock.patch("certbot_apache._internal.parser.init_augeas")
def test_prepare_no_augeas(self, mock_init_augeas):
from certbot_apache._internal.parser import ApacheParser
mock_init_augeas.side_effect = errors.NoInstallationError

View File

@@ -20,7 +20,7 @@ class ConfiguratorParserNodeTest(util.ApacheTest): # pylint: disable=too-many-p
"""Test AugeasParserNode using available test configurations"""
def setUp(self): # pylint: disable=arguments-differ
super(ConfiguratorParserNodeTest, self).setUp()
super().setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir,

View File

@@ -18,7 +18,7 @@ class DummyParserNode(interfaces.ParserNode):
self.dirty = dirty
self.filepath = filepath
self.metadata = metadata
super(DummyParserNode, self).__init__(**kwargs)
super().__init__(**kwargs)
def save(self, msg): # pragma: no cover
"""Save"""
@@ -38,7 +38,7 @@ class DummyCommentNode(DummyParserNode):
"""
comment, kwargs = util.commentnode_kwargs(kwargs)
self.comment = comment
super(DummyCommentNode, self).__init__(**kwargs)
super().__init__(**kwargs)
class DummyDirectiveNode(DummyParserNode):
@@ -54,7 +54,7 @@ class DummyDirectiveNode(DummyParserNode):
self.parameters = parameters
self.enabled = enabled
super(DummyDirectiveNode, self).__init__(**kwargs)
super().__init__(**kwargs)
def set_parameters(self, parameters): # pragma: no cover
"""Set parameters"""

View File

@@ -67,7 +67,7 @@ class ParserTest(ApacheTest):
def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts",
config_root="debian_apache_2_4/multiple_vhosts/apache2",
vhost_root="debian_apache_2_4/multiple_vhosts/apache2/sites-available"):
super(ParserTest, self).setUp(test_dir, config_root, vhost_root)
super().setUp(test_dir, config_root, vhost_root)
zope.component.provideUtility(display_util.FileDisplay(sys.stdout,
False))
@@ -123,11 +123,11 @@ def get_apache_configurator(
version=version, use_parsernode=use_parsernode,
openssl_version=openssl_version)
if not conf_vhost_path:
config_class.OS_DEFAULTS["vhost_root"] = vhost_path
config_class.OS_DEFAULTS.vhost_root = vhost_path
else:
# Custom virtualhost path was requested
config.config.apache_vhost_root = conf_vhost_path
config.config.apache_ctl = config_class.OS_DEFAULTS["ctl"]
config.config.apache_ctl = config_class.OS_DEFAULTS.ctl
config.prepare()
return config

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.13.0"
LE_AUTO_VERSION="1.14.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -800,6 +800,7 @@ BootstrapMageiaCommon() {
# packages BOOTSTRAP_VERSION is not set.
if [ -f /etc/debian_version ]; then
DEPRECATED_OS=1
NO_SELF_UPGRADE=1
elif [ -f /etc/mageia-release ]; then
# Mageia has both /etc/mageia-release and /etc/redhat-release
DEPRECATED_OS=1
@@ -1488,18 +1489,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==1.13.0 \
--hash=sha256:082eb732e1318bb9605afa7aea8db2c2f4c5029d523c73f24c6aa98f03caff76 \
--hash=sha256:64cf41b57df7667d9d849fcaa9031a4f151788246733d1f4c3f37a5aa5e2f458
acme==1.13.0 \
--hash=sha256:93b6365c9425de03497a6b8aee1107814501d2974499b42e9bcc9a7378771143 \
--hash=sha256:6b4257dfd6a6d5f01e8cd4f0b10422c17836bed7c67e9c5b0a0ad6c7d651c088
certbot-apache==1.13.0 \
--hash=sha256:36ed02ac7d2d91febee8dd3181ae9095b3f06434c9ed8959fbc6db24ab4da2e8 \
--hash=sha256:4b5a16e80c1418e2edc05fc2578f522fb24974b2c13eb747cdfeef69e5bd5ae1
certbot-nginx==1.13.0 \
--hash=sha256:3ff271f65321b25c77a868af21f76f58754a7d61529ad565a1d66e29c711120f \
--hash=sha256:9e972cc19c0fa9e5b7863da0423b156fbfb5623fd30b558fd2fd6d21c24c0b08
certbot==1.14.0 \
--hash=sha256:67b4d26ceaea6c7f8325d0d45169e7a165a2cabc7122c84bc971ba068ca19cca \
--hash=sha256:959ea90c6bb8dca38eab9772722cb940972ef6afcd5f15deef08b3c3636841eb
acme==1.14.0 \
--hash=sha256:4f48c41261202f1a389ec2986b2580b58f53e0d5a1ae2463b34318d78b87fc66 \
--hash=sha256:61daccfb0343628cbbca551a7fc4c82482113952c21db3fe0c585b7c98fa1c35
certbot-apache==1.14.0 \
--hash=sha256:b757038db23db707c44630fecb46e99172bd791f0db5a8e623c0842613c4d3d9 \
--hash=sha256:887fe4a21af2de1e5c2c9428bacba6eb7c1219257bc70f1a1d8447c8a321adb0
certbot-nginx==1.14.0 \
--hash=sha256:8916a815437988d6c192df9f035bb7a176eab20eee0956677b335d0698d243fb \
--hash=sha256:cc2a8a0de56d9bb6b2efbda6c80c647dad8db2bb90675cac03ade94bd5fc8597
UNLIKELY_EOF
# -------------------------------------------------------------------------

View File

@@ -61,7 +61,7 @@ class IntegrationTestsContext:
Execute certbot with given args, not renewing certificates by default.
:param args: args to pass to certbot
:param force_renew: set to False to not renew by default
:return: output of certbot execution
:return: stdout and stderr from certbot execution
"""
command = ['--authenticator', 'standalone', '--installer', 'null']
command.extend(args)

View File

@@ -78,9 +78,9 @@ def test_registration_override(context):
def test_prepare_plugins(context):
"""Test that plugins are correctly instantiated and displayed."""
output = context.certbot(['plugins', '--init', '--prepare'])
stdout, _ = context.certbot(['plugins', '--init', '--prepare'])
assert 'webroot' in output
assert 'webroot' in stdout
def test_http_01(context):
@@ -407,9 +407,9 @@ def test_invalid_domain_with_dns_challenge(context):
'--manual-cleanup-hook', context.manual_dns_cleanup_hook
])
output = context.certbot(['certificates'])
stdout, _ = context.certbot(['certificates'])
assert context.get_domain('fail-dns1') not in output
assert context.get_domain('fail-dns1') not in stdout
def test_reuse_key(context):
@@ -614,11 +614,11 @@ def test_revoke_and_unregister(context):
context.certbot(['unregister'])
output = context.certbot(['certificates'])
stdout, _ = context.certbot(['certificates'])
assert cert1 not in output
assert cert2 not in output
assert cert3 in output
assert cert1 not in stdout
assert cert2 not in stdout
assert cert3 in stdout
def test_revoke_mutual_exclusive_flags(context):
@@ -630,7 +630,7 @@ def test_revoke_mutual_exclusive_flags(context):
'revoke', '--cert-name', cert,
'--cert-path', join(context.config_dir, 'live', cert, 'fullchain.pem')
])
assert 'Exactly one of --cert-path or --cert-name must be specified' in error.out
assert 'Exactly one of --cert-path or --cert-name must be specified' in error.value.stderr
def test_revoke_multiple_lineages(context):
@@ -685,12 +685,12 @@ def test_wildcard_certificates(context):
def test_ocsp_status_stale(context):
"""Test retrieval of OCSP statuses for staled config"""
sample_data_path = misc.load_sample_data_path(context.workspace)
output = context.certbot(['certificates', '--config-dir', sample_data_path])
stdout, _ = context.certbot(['certificates', '--config-dir', sample_data_path])
assert output.count('TEST_CERT') == 2, ('Did not find two test certs as expected ({0})'
.format(output.count('TEST_CERT')))
assert output.count('EXPIRED') == 2, ('Did not find two expired certs as expected ({0})'
.format(output.count('EXPIRED')))
assert stdout.count('TEST_CERT') == 2, ('Did not find two test certs as expected ({0})'
.format(stdout.count('TEST_CERT')))
assert stdout.count('EXPIRED') == 2, ('Did not find two expired certs as expected ({0})'
.format(stdout.count('EXPIRED')))
def test_ocsp_status_live(context):
@@ -699,20 +699,20 @@ def test_ocsp_status_live(context):
# OSCP 1: Check live certificate OCSP status (VALID)
context.certbot(['--domains', cert])
output = context.certbot(['certificates'])
stdout, _ = context.certbot(['certificates'])
assert output.count('VALID') == 1, 'Expected {0} to be VALID'.format(cert)
assert output.count('EXPIRED') == 0, 'Did not expect {0} to be EXPIRED'.format(cert)
assert stdout.count('VALID') == 1, 'Expected {0} to be VALID'.format(cert)
assert stdout.count('EXPIRED') == 0, 'Did not expect {0} to be EXPIRED'.format(cert)
# OSCP 2: Check live certificate OCSP status (REVOKED)
context.certbot(['revoke', '--cert-name', cert, '--no-delete-after-revoke'])
# Sometimes in oldest tests (using openssl binary and not cryptography), the OCSP status is
# not seen immediately by Certbot as invalid. Waiting few seconds solves this transient issue.
time.sleep(5)
output = context.certbot(['certificates'])
stdout, _ = context.certbot(['certificates'])
assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert)
assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
assert stdout.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert)
assert stdout.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
def test_ocsp_renew(context):

View File

@@ -11,7 +11,7 @@ from certbot_integration_tests.utils import misc
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
"""General fixture describing a certbot-nginx integration tests context"""
def __init__(self, request):
super(IntegrationTestsContext, self).__init__(request)
super().__init__(request)
self.nginx_root = os.path.join(self.workspace, 'nginx')
os.mkdir(self.nginx_root)
@@ -29,7 +29,7 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
def cleanup(self):
self._stop_nginx()
super(IntegrationTestsContext, self).cleanup()
super().cleanup()
def certbot_test_nginx(self, args):
"""
@@ -51,6 +51,7 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
with open(self.nginx_config_path, 'w') as file:
file.write(self.nginx_config)
# pylint: disable=consider-using-with
process = subprocess.Popen(['nginx', '-c', self.nginx_config_path, '-g', 'daemon off;'])
assert process.poll() is None

View File

@@ -13,11 +13,10 @@ from certbot_integration_tests.utils import certbot_call
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
"""Integration test context for certbot-dns-rfc2136"""
def __init__(self, request):
super(IntegrationTestsContext, self).__init__(request)
super().__init__(request)
self.request = request
self._dns_xdist = None
if hasattr(request.config, 'workerinput'): # Worker node
self._dns_xdist = request.config.workerinput['dns_xdist']
else: # Primary node
@@ -45,7 +44,6 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
src_file = resource_filename('certbot_integration_tests',
'assets/bind-config/rfc2136-credentials-{}.ini.tpl'
.format(label))
contents = None
with open(src_file, 'r') as f:
contents = f.read().format(

View File

@@ -240,6 +240,7 @@ class ACMEServer:
if not env:
env = os.environ
stdout = sys.stderr if force_stderr else self._stdout
# pylint: disable=consider-using-with
process = subprocess.Popen(
command, stdout=stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env
)

View File

@@ -17,7 +17,7 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
Invoke the certbot executable available in PATH in a test context for the given args.
The test context consists in running certbot in debug mode, with various flags suitable
for tests (eg. no ssl check, customizable ACME challenge ports and config directory ...).
This command captures stdout and returns it to the caller.
This command captures both stdout and stderr and returns it to the caller.
:param list certbot_args: the arguments to pass to the certbot executable
:param str directory_url: URL of the ACME directory server to use
:param int http_01_port: port for the HTTP-01 challenges
@@ -25,13 +25,19 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
:param str config_dir: certbot configuration directory to use
:param str workspace: certbot current directory to use
:param bool force_renew: set False to not force renew existing certificates (default: True)
:return: stdout as string
:rtype: str
:return: stdout and stderr as strings
:rtype: `tuple` of `str`
"""
command, env = _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
config_dir, workspace, force_renew)
return subprocess.check_output(command, universal_newlines=True, cwd=workspace, env=env)
proc = subprocess.run(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, check=False, universal_newlines=True,
cwd=workspace, env=env)
print('--> Certbot log output was:')
print(proc.stderr)
proc.check_returncode()
return proc.stdout, proc.stderr
def _prepare_environ(workspace):

View File

@@ -8,6 +8,7 @@ import subprocess
import sys
import tempfile
import time
from typing import Optional
from pkg_resources import resource_filename
@@ -38,7 +39,7 @@ class DNSServer:
self.bind_root = tempfile.mkdtemp()
self.process: subprocess.Popen = None
self.process: Optional[subprocess.Popen] = None
self.dns_xdist = {"address": BIND_BIND_ADDRESS[0], "port": BIND_BIND_ADDRESS[1]}
@@ -82,6 +83,7 @@ class DNSServer:
def _start_bind(self):
"""Launch the BIND9 server as a Docker container"""
addr_str = "{}:{}".format(BIND_BIND_ADDRESS[0], BIND_BIND_ADDRESS[1])
# pylint: disable=consider-using-with
self.process = subprocess.Popen(
[
"docker",
@@ -119,6 +121,9 @@ class DNSServer:
but otherwise the contents are ignored.
:param int attempts: The number of attempts to make.
"""
if not self.process:
raise ValueError("DNS server has not been started. Please run start() first.")
for _ in range(attempts):
if self.process.poll():
raise ValueError("BIND9 server stopped unexpectedly")

View File

@@ -38,7 +38,7 @@ setup(
description="Certbot continuous integration framework",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -44,4 +44,4 @@ def test_dns_plugin_install(dns_snap_path):
'certbot:certbot-metadata'])
subprocess.check_call(['snap', 'install', '--dangerous', dns_snap_path])
finally:
subprocess.call(['snap', 'remove', 'plugin_name'])
subprocess.call(['snap', 'remove', plugin_name])

View File

@@ -22,7 +22,7 @@ class Proxy(configurators_common.Proxy):
def __init__(self, args):
"""Initializes the plugin with the given command line args"""
super(Proxy, self).__init__(args)
super().__init__(args)
self.le_config.apache_le_vhost_ext = "-le-ssl.conf"
self.modules = self.server_root = self.test_conf = self.version = None
@@ -34,7 +34,7 @@ class Proxy(configurators_common.Proxy):
def load_config(self):
"""Loads the next configuration for the plugin to test"""
config = super(Proxy, self).load_config()
config = super().load_config()
self._all_names, self._test_names = _get_names(config)
server_root = _get_server_root(config)
@@ -54,9 +54,9 @@ class Proxy(configurators_common.Proxy):
def _prepare_configurator(self):
"""Prepares the Apache plugin for testing"""
for k in entrypoint.ENTRYPOINT.OS_DEFAULTS:
for k in entrypoint.ENTRYPOINT.OS_DEFAULTS.__dict__.keys():
setattr(self.le_config, "apache_" + k,
entrypoint.ENTRYPOINT.OS_DEFAULTS[k])
getattr(entrypoint.ENTRYPOINT.OS_DEFAULTS, k))
self._configurator = entrypoint.ENTRYPOINT(
config=configuration.NamespaceConfig(self.le_config),
@@ -65,7 +65,7 @@ class Proxy(configurators_common.Proxy):
def cleanup_from_tests(self):
"""Performs any necessary cleanup from running plugin tests"""
super(Proxy, self).cleanup_from_tests()
super().cleanup_from_tests()
mock.patch.stopall()

View File

@@ -33,7 +33,9 @@ class Proxy:
self.args = args
self.http_port = 80
self.https_port = 443
self._configurator = self._all_names = self._test_names = None
self._configurator = None
self._all_names = None
self._test_names = None
def __getattr__(self, name):
"""Wraps the configurator methods"""
@@ -93,5 +95,7 @@ class Proxy:
"""Installs cert"""
cert_path, key_path, chain_path = self.copy_certs_and_keys(
cert_path, key_path, chain_path)
if not self._configurator:
raise ValueError("Configurator plugin is not set.")
self._configurator.deploy_cert(
domain, cert_path, key_path, chain_path, fullchain_path)

View File

@@ -21,7 +21,7 @@ class Proxy(configurators_common.Proxy):
def load_config(self):
"""Loads the next configuration for the plugin to test"""
config = super(Proxy, self).load_config()
config = super().load_config()
self._all_names, self._test_names = _get_names(config)
server_root = _get_server_root(config)

View File

@@ -10,6 +10,7 @@ import tempfile
import time
from typing import List
from typing import Tuple
import zope.component
import OpenSSL
from urllib3.util import connection
@@ -19,6 +20,7 @@ from acme import crypto_util
from acme import messages
from certbot import achallenges
from certbot import errors as le_errors
from certbot.display import util as display_util
from certbot.tests import acme_util
from certbot_compatibility_test import errors
from certbot_compatibility_test import util
@@ -327,10 +329,17 @@ def setup_logging(args):
root_logger.addHandler(handler)
def setup_display():
""""Prepares IDisplay for the Certbot plugins """
displayer = display_util.NoninteractiveDisplay(sys.stdout)
zope.component.provideUtility(displayer)
def main():
"""Main test script execution."""
args = get_args()
setup_logging(args)
setup_display()
if args.plugin not in PLUGINS:
raise errors.Error("Unknown plugin {0}".format(args.plugin))

View File

@@ -3,7 +3,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
install_requires = [
'certbot',
@@ -24,7 +24,7 @@ setup(
description="Compatibility tests for Certbot",
url='https://github.com/letsencrypt/letsencrypt',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -3,6 +3,7 @@ import logging
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
import CloudFlare
import zope.interface
@@ -10,6 +11,7 @@ import zope.interface
from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins.dns_common import CredentialsConfiguration
logger = logging.getLogger(__name__)
@@ -29,12 +31,12 @@ class Authenticator(dns_common.DNSAuthenticator):
ttl = 120
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
super().__init__(*args, **kwargs)
self.credentials: Optional[CredentialsConfiguration] = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add)
super().add_parser_arguments(add)
add('credentials', help='Cloudflare credentials INI file.')
def more_info(self): # pylint: disable=missing-function-docstring
@@ -79,6 +81,8 @@ class Authenticator(dns_common.DNSAuthenticator):
self._get_cloudflare_client().del_txt_record(domain, validation_name, validation)
def _get_cloudflare_client(self):
if not self.credentials: # pragma: no cover
raise errors.Error("Plugin has not been prepared.")
if self.credentials.conf('api-token'):
return _CloudflareClient(None, self.credentials.conf('api-token'))
return _CloudflareClient(self.credentials.conf('email'), self.credentials.conf('api-key'))

View File

@@ -4,7 +4,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -36,7 +36,7 @@ setup(
description="Cloudflare DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -27,7 +27,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
def setUp(self):
from certbot_dns_cloudflare._internal.dns_cloudflare import Authenticator
super(AuthenticatorTest, self).setUp()
super().setUp()
path = os.path.join(self.tempdir, 'file.ini')
dns_test_common.write({"cloudflare_email": EMAIL, "cloudflare_api_key": API_KEY}, path)
@@ -41,7 +41,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
# _get_cloudflare_client | pylint: disable=protected-access
self.auth._get_cloudflare_client = mock.MagicMock(return_value=self.mock_client)
def test_perform(self):
@test_util.patch_get_utility()
def test_perform(self, unused_mock_get_utility):
self.auth.perform([self.achall])
expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
@@ -55,7 +56,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
expected = [mock.call.del_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY)]
self.assertEqual(expected, self.mock_client.mock_calls)
def test_api_token(self):
@test_util.patch_get_utility()
def test_api_token(self, unused_mock_get_utility):
dns_test_common.write({"cloudflare_api_token": API_TOKEN},
self.config.cloudflare_credentials)
self.auth.perform([self.achall])

View File

@@ -1,5 +1,6 @@
"""DNS Authenticator for CloudXNS DNS."""
import logging
from typing import Optional
from lexicon.providers import cloudxns
import zope.interface
@@ -8,6 +9,7 @@ from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins import dns_common_lexicon
from certbot.plugins.dns_common import CredentialsConfiguration
logger = logging.getLogger(__name__)
@@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator):
ttl = 60
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
super().__init__(*args, **kwargs)
self.credentials: Optional[CredentialsConfiguration] = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30)
super().add_parser_arguments(add, default_propagation_seconds=30)
add('credentials', help='CloudXNS credentials INI file.')
def more_info(self): # pylint: disable=missing-function-docstring
@@ -56,6 +58,8 @@ class Authenticator(dns_common.DNSAuthenticator):
self._get_cloudxns_client().del_txt_record(domain, validation_name, validation)
def _get_cloudxns_client(self):
if not self.credentials: # pragma: no cover
raise errors.Error("Plugin has not been prepared.")
return _CloudXNSLexiconClient(self.credentials.conf('api-key'),
self.credentials.conf('secret-key'),
self.ttl)
@@ -67,7 +71,7 @@ class _CloudXNSLexiconClient(dns_common_lexicon.LexiconClient):
"""
def __init__(self, api_key, secret_key, ttl):
super(_CloudXNSLexiconClient, self).__init__()
super().__init__()
config = dns_common_lexicon.build_lexicon_config('cloudxns', {
'ttl': ttl,

View File

@@ -4,12 +4,12 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
'zope.interface',
]
@@ -36,7 +36,7 @@ setup(
description="CloudXNS DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -26,7 +26,7 @@ class AuthenticatorTest(test_util.TempDirTestCase,
dns_test_common_lexicon.BaseLexiconAuthenticatorTest):
def setUp(self):
super(AuthenticatorTest, self).setUp()
super().setUp()
from certbot_dns_cloudxns._internal.dns_cloudxns import Authenticator

View File

@@ -1,5 +1,6 @@
"""DNS Authenticator for DigitalOcean."""
import logging
from typing import Optional
import digitalocean
import zope.interface
@@ -7,6 +8,7 @@ import zope.interface
from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins.dns_common import CredentialsConfiguration
logger = logging.getLogger(__name__)
@@ -24,12 +26,12 @@ class Authenticator(dns_common.DNSAuthenticator):
ttl = 30
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
super().__init__(*args, **kwargs)
self.credentials: Optional[CredentialsConfiguration] = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add)
super().add_parser_arguments(add)
add('credentials', help='DigitalOcean credentials INI file.')
def more_info(self): # pylint: disable=missing-function-docstring
@@ -53,6 +55,8 @@ class Authenticator(dns_common.DNSAuthenticator):
self._get_digitalocean_client().del_txt_record(domain, validation_name, validation)
def _get_digitalocean_client(self):
if not self.credentials: # pragma: no cover
raise errors.Error("Plugin has not been prepared.")
return _DigitalOceanClient(self.credentials.conf('token'))

View File

@@ -4,7 +4,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -36,7 +36,7 @@ setup(
description="DigitalOcean DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -23,7 +23,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
def setUp(self):
from certbot_dns_digitalocean._internal.dns_digitalocean import Authenticator
super(AuthenticatorTest, self).setUp()
super().setUp()
path = os.path.join(self.tempdir, 'file.ini')
dns_test_common.write({"digitalocean_token": TOKEN}, path)
@@ -37,7 +37,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
# _get_digitalocean_client | pylint: disable=protected-access
self.auth._get_digitalocean_client = mock.MagicMock(return_value=self.mock_client)
def test_perform(self):
@test_util.patch_get_utility()
def test_perform(self, unused_mock_get_utility):
self.auth.perform([self.achall])
expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, 30)]

View File

@@ -1,5 +1,6 @@
"""DNS Authenticator for DNSimple DNS."""
import logging
from typing import Optional
from lexicon.providers import dnsimple
import zope.interface
@@ -8,6 +9,7 @@ from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins import dns_common_lexicon
from certbot.plugins.dns_common import CredentialsConfiguration
logger = logging.getLogger(__name__)
@@ -26,12 +28,12 @@ class Authenticator(dns_common.DNSAuthenticator):
ttl = 60
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
super().__init__(*args, **kwargs)
self.credentials: Optional[CredentialsConfiguration] = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30)
super().add_parser_arguments(add, default_propagation_seconds=30)
add('credentials', help='DNSimple credentials INI file.')
def more_info(self): # pylint: disable=missing-function-docstring
@@ -54,6 +56,8 @@ class Authenticator(dns_common.DNSAuthenticator):
self._get_dnsimple_client().del_txt_record(domain, validation_name, validation)
def _get_dnsimple_client(self):
if not self.credentials: # pragma: no cover
raise errors.Error("Plugin has not been prepared.")
return _DNSimpleLexiconClient(self.credentials.conf('token'), self.ttl)
@@ -63,7 +67,7 @@ class _DNSimpleLexiconClient(dns_common_lexicon.LexiconClient):
"""
def __init__(self, token, ttl):
super(_DNSimpleLexiconClient, self).__init__()
super().__init__()
config = dns_common_lexicon.build_lexicon_config('dnssimple', {
'ttl': ttl,

View File

@@ -4,7 +4,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -32,7 +32,7 @@ if os.environ.get('SNAP_BUILD'):
# which allows us to potentially upgrade our packages in these distros
# as necessary.
if os.environ.get('CERTBOT_OLDEST') == '1':
install_requires.append('dns-lexicon>=2.2.1')
install_requires.append('dns-lexicon>=3.1.0') # Changed parameter name
else:
install_requires.append('dns-lexicon>=3.2.1')
@@ -47,7 +47,7 @@ setup(
description="DNSimple DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase,
dns_test_common_lexicon.BaseLexiconAuthenticatorTest):
def setUp(self):
super(AuthenticatorTest, self).setUp()
super().setUp()
from certbot_dns_dnsimple._internal.dns_dnsimple import Authenticator

View File

@@ -1,5 +1,6 @@
"""DNS Authenticator for DNS Made Easy DNS."""
import logging
from typing import Optional
from lexicon.providers import dnsmadeeasy
import zope.interface
@@ -8,6 +9,7 @@ from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins import dns_common_lexicon
from certbot.plugins.dns_common import CredentialsConfiguration
logger = logging.getLogger(__name__)
@@ -27,12 +29,12 @@ class Authenticator(dns_common.DNSAuthenticator):
ttl = 60
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
super().__init__(*args, **kwargs)
self.credentials: Optional[CredentialsConfiguration] = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60)
super().add_parser_arguments(add, default_propagation_seconds=60)
add('credentials', help='DNS Made Easy credentials INI file.')
def more_info(self): # pylint: disable=missing-function-docstring
@@ -58,6 +60,8 @@ class Authenticator(dns_common.DNSAuthenticator):
self._get_dnsmadeeasy_client().del_txt_record(domain, validation_name, validation)
def _get_dnsmadeeasy_client(self):
if not self.credentials: # pragma: no cover
raise errors.Error("Plugin has not been prepared.")
return _DNSMadeEasyLexiconClient(self.credentials.conf('api-key'),
self.credentials.conf('secret-key'),
self.ttl)
@@ -69,7 +73,7 @@ class _DNSMadeEasyLexiconClient(dns_common_lexicon.LexiconClient):
"""
def __init__(self, api_key, secret_key, ttl):
super(_DNSMadeEasyLexiconClient, self).__init__()
super().__init__()
config = dns_common_lexicon.build_lexicon_config('dnsmadeeasy', {
'ttl': ttl,

View File

@@ -4,12 +4,12 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
'zope.interface',
]
@@ -36,7 +36,7 @@ setup(
description="DNS Made Easy DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -22,7 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase,
dns_test_common_lexicon.BaseLexiconAuthenticatorTest):
def setUp(self):
super(AuthenticatorTest, self).setUp()
super().setUp()
from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import Authenticator

View File

@@ -1,12 +1,15 @@
"""DNS Authenticator for Gehirn Infrastructure Service DNS."""
import logging
from typing import Optional
from lexicon.providers import gehirn
import zope.interface
from certbot import errors
from certbot import interfaces
from certbot.plugins import dns_common
from certbot.plugins import dns_common_lexicon
from certbot.plugins.dns_common import CredentialsConfiguration
logger = logging.getLogger(__name__)
@@ -26,12 +29,12 @@ class Authenticator(dns_common.DNSAuthenticator):
ttl = 60
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
super().__init__(*args, **kwargs)
self.credentials: Optional[CredentialsConfiguration] = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30)
super().add_parser_arguments(add, default_propagation_seconds=30)
add('credentials', help='Gehirn Infrastructure Service credentials file.')
def more_info(self): # pylint: disable=missing-function-docstring
@@ -57,6 +60,8 @@ class Authenticator(dns_common.DNSAuthenticator):
self._get_gehirn_client().del_txt_record(domain, validation_name, validation)
def _get_gehirn_client(self):
if not self.credentials: # pragma: no cover
raise errors.Error("Plugin has not been prepared.")
return _GehirnLexiconClient(
self.credentials.conf('api-token'),
self.credentials.conf('api-secret'),
@@ -70,7 +75,7 @@ class _GehirnLexiconClient(dns_common_lexicon.LexiconClient):
"""
def __init__(self, api_token, api_secret, ttl):
super(_GehirnLexiconClient, self).__init__()
super().__init__()
config = dns_common_lexicon.build_lexicon_config('gehirn', {
'ttl': ttl,
@@ -84,4 +89,4 @@ class _GehirnLexiconClient(dns_common_lexicon.LexiconClient):
def _handle_http_error(self, e, domain_name):
if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')):
return None # Expected errors when zone name guess is wrong
return super(_GehirnLexiconClient, self)._handle_http_error(e, domain_name)
return super()._handle_http_error(e, domain_name)

View File

@@ -4,11 +4,11 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
'dns-lexicon>=2.1.22',
'dns-lexicon>=3.1.0', # Changed `rtype` parameter name
'setuptools>=39.0.1',
'zope.interface',
]
@@ -35,7 +35,7 @@ setup(
description="Gehirn Infrastructure Service DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase,
dns_test_common_lexicon.BaseLexiconAuthenticatorTest):
def setUp(self):
super(AuthenticatorTest, self).setUp()
super().setUp()
from certbot_dns_gehirn._internal.dns_gehirn import Authenticator

View File

@@ -51,8 +51,16 @@ are automatically obtained by certbot through the `metadata service
:caption: Example credentials file:
{
"type": "service_account",
...
"type": "service_account",
"project_id": "...",
"private_key_id": "...",
"private_key": "...",
"client_email": "...",
"client_id": "...",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "..."
}
The path to this file can be provided interactively or using the

View File

@@ -32,13 +32,9 @@ class Authenticator(dns_common.DNSAuthenticator):
'for DNS).')
ttl = 60
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.credentials = None
@classmethod
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=60)
super().add_parser_arguments(add, default_propagation_seconds=60)
add('credentials',
help=('Path to Google Cloud DNS service account JSON file. (See {0} for' +
'information about creating a service account and {1} for information about the' +

View File

@@ -1,16 +0,0 @@
"""Copied from https://stackoverflow.com/a/16863232"""
def setup(app):
# enable Pygments json lexer
try:
import pygments
if pygments.__version__ >= '1.5':
# use JSON lexer included in recent versions of Pygments
from pygments.lexers import JsonLexer
else:
# use JSON lexer from pygments-json if installed
from pygson.json_lexer import JSONLexer as JsonLexer
except ImportError:
pass # not fatal if we have old (or no) Pygments and no pygments-json
else:
app.add_lexer('json', JsonLexer())

View File

@@ -35,8 +35,7 @@ extensions = ['sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'jsonlexer']
'sphinx.ext.viewcode']
autodoc_member_order = 'bysource'
autodoc_default_flags = ['show-inheritance']

View File

@@ -4,7 +4,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.14.0.dev0'
version = '1.16.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
@@ -39,7 +39,7 @@ setup(
description="Google Cloud DNS Authenticator plugin for Certbot",
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.6',
classifiers=[

View File

@@ -26,14 +26,14 @@ PROJECT_ID = "test-test-1"
class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest):
def setUp(self):
super(AuthenticatorTest, self).setUp()
super().setUp()
from certbot_dns_google._internal.dns_google import Authenticator
path = os.path.join(self.tempdir, 'file.json')
open(path, "wb").close()
super(AuthenticatorTest, self).setUp()
super().setUp()
self.config = mock.MagicMock(google_credentials=path,
google_propagation_seconds=0) # don't wait during tests
@@ -43,7 +43,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
# _get_google_client | pylint: disable=protected-access
self.auth._get_google_client = mock.MagicMock(return_value=self.mock_client)
def test_perform(self):
@test_util.patch_get_utility()
def test_perform(self, unused_mock_get_utility):
self.auth.perform([self.achall])
expected = [mock.call.add_txt_record(DOMAIN, '_acme-challenge.'+DOMAIN, mock.ANY, mock.ANY)]
@@ -58,7 +59,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
self.assertEqual(expected, self.mock_client.mock_calls)
@mock.patch('httplib2.Http.request', side_effect=ServerNotFoundError)
def test_without_auth(self, unused_mock):
@test_util.patch_get_utility()
def test_without_auth(self, unused_mock_get_utility, unused_mock):
self.config.google_credentials = None
self.assertRaises(PluginError, self.auth.perform, [self.achall])

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