Compare commits

...

587 Commits

Author SHA1 Message Date
Brad Warren
0a15e95f3a test farm tests only 2021-05-11 16:00:02 -07: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
Brad Warren
69479b7277 use standard errno (#8768)
We were originally using `socket.errno` with a `type: ignore` and a comment suggesting that this attribute needs to be included in the typeshed. This is incorrect.

While it's true that [socket imports errno](43682f1e39/Lib/socket.py (L58)), it's not intended to be part of its API. https://docs.python.org/3/library/socket.html has no mention of it.

Instead, we should be using the standard `errno` module and remove this `type: ignore`.
2021-04-05 10:42:18 -07:00
Brad Warren
2622a700e0 Update a few type ignore comments (#8767)
Some are no longer needed and other's comments are out of date.

For the changes to the acme nonce errors, `Exception` doesn't take kwargs. The error message about this our own classes isn't super helpful:
```
In [2]: BadNonce('nonce', 'error', foo='bar')                                                                                                                                                                                                                                                                               
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-2-54555658ef99> in <module>
----> 1 BadNonce('nonce', 'error', foo='bar')

TypeError: __init__() got an unexpected keyword argument 'foo'
```
but if you try this on `Exception` which these classes inherit from, you get:
```
In [4]: Exception(foo='bar')                                                                                                                                                                                                                                                                                                
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-028b924f74c5> in <module>
----> 1 Exception(foo='bar')

TypeError: Exception() takes no keyword arguments
```
See https://github.com/python/typeshed/pull/2348 for more info.

* remove outdated ignores

* update locking ignore comment

* don't accept kwargs
2021-04-02 16:19:30 -07:00
Adrien Ferrand
06a53cb7df Upgrade to mypy 0.812 (#8748)
Fixes #8425

This PR upgrades mypy to the latest version available, 0.812.

Given the advanced type inference capabilities provided by this newer version, this PRs also fixes various type inconsistencies that are now detected. Here are the non obvious changes done to fix types:
* typing in mixins has been solved using `Protocol` classes, as recommended by mypy (https://mypy.readthedocs.io/en/latest/more_types.html#mixin-classes, https://mypy.readthedocs.io/en/stable/protocols.html)
* `cast` when we are playing with `Union` types

This PR also disables the strict optional checks that have been enable by default in recent versions of mypy. Once this PR is merged, I will create an issue to study how these checks can be enabled.

`typing.Protocol` is available only since Python 3.8. To keep compatibility with Python 3.6, I try to import the class `Protocol` from `typing`, and fallback to assign `object` to `Protocol` if that fails. This way the code is working with all versions of Python, but the mypy check can be run only with Python 3.8+ because it needs the protocol feature. As a consequence, tox runs mypy under Python 3.8.

Alternatives are:
* importing `typing_extensions`, that proposes backport of newest typing features to Python 3.6, but this implies to add a dependency to Certbot just to run mypy
* redesign the concerned classes to not use mixins, or use them differently, but this implies to modify the code itself even if there is nothing wrong with it and it is just a matter of instructing mypy to understand in which context the mixins can be used
* ignoring type for these classes with `# type: ignore` but we loose the benefit of mypy for them

* 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
2021-04-02 11:54:40 -07:00
Brad Warren
584a1a3ece simplify setup.py (#8760)
I recently noticed that we only support versions of `setuptools` that support environment markers which allows us to simplify our `setup.py` files a bit.
2021-04-02 10:37:48 -07:00
Brad Warren
28fac893f4 add and update pynsist template comments (#8759) 2021-04-02 10:37:40 -07:00
Adrien Ferrand
8a84c88fee Remove wheel hack in windows installer construction script (#8752)
In #8649 we added some code to trick pynsist and make it understand that `abi3` wheels for Windows are forward compatible, meaning that the cryptography wheel tagged `cp36-abi3` is in fact compatible with Python 3.6+, and not only Python 3.6.

Since pynsist 2.7 the tool now understand `abi3` wheels properly, and this trick is not needed anymore.

Please note that despite modifying the pynsist pinning in `dev_constraints.txt`, it will have no effect since pynsist currently escape the pinning system. This is handled in https://github.com/certbot/certbot/pull/8749.
2021-04-02 10:37:19 -07:00
Adrien Ferrand
fea0b4e2e5 Pin pynsist (#8749)
* Pin pynsist

* Update dependencies

* Set windows installer a proper python project

* Optimize usage of the venvs

* Add windows-installer when venv is set up

* Fix call

* Remove env marker
2021-04-01 13:57:03 -07:00
Brad Warren
1ea588d504 increase ARM build timeout (#8757) 2021-03-31 12:42:42 -07:00
Brad Warren
24fd4121cf use snapcraft login (#8756) 2021-03-31 12:41:29 -07:00
ohemorange
8759ccaecb Update issue template to list snap as an installation option (#8754) 2021-03-29 13:56:08 -07:00
Brad Warren
f4fc3e636d Redo the majority of Certbot's pinning system (#8741)
* add initial pyproject.toml

* add extra dependencies

* add simple bash script

* polish

* reuse pipstrap

* add requirements.txt

* temporarily remove hashin dep

* Switch to requirements.txt

* remove hashin check

* update requirements.txt again

* remove unnecessary merge

* pin back augeas

* unpin cryptography

* simplify pywin32 pinning

* update comment

* pin back pytest and pylint

* pin back pytest-forked

* pin back coverage

* update script comments

* fix pyopenssl case

* add minimum poetry version

* run pin.sh
2021-03-26 07:51:59 +01:00
Adrien Ferrand
018efc241c Split snap build over three Azure jobs (one per architecture) (#8731)
Fixes #8700

Now that `snapcraft remote-build` truly uses new builds for each call, we can split the builds to have a dedicated Azure job for each target architecture. This PR does that.

* Split snap_build job  on each architecture

* Also parallelize the publish_snap jobs over each architecture
2021-03-25 14:38:34 -07:00
ohemorange
fa25d8356d Remove references to certbot-auto and letsencrypt-auto that we don't need for the final release (#8738)
Fixes #8661.

As mentioned in https://github.com/certbot/certbot/issues/8661#issuecomment-806168214, there are quite a few remaining references, but until we modify the release script, we still need those. The changes here and the list there were created by grepping for the following terms:

```
certbot-auto
cb-auto
cbauto
certbotauto
letsencrypt-auto
le-auto
leauto
letsencryptauto
LEAUTO
LE_AUTO
LETSENCRYPT_AUTO
LETSENCRYPTAUTO
CB_AUTO
CERTBOT_AUTO
CBAUTO
CERTBOTAUTO
```

* Remove references to certbot-auto from certbot code

* Remove references to LEAUTO

* Remove references to CERTBOT_AUTO

* Remove references to letsencrypt-auto

* Remove references to certbot-auto from docs and tools

* remove cli constants header files

* Remove Python virtual environment section
2021-03-24 16:58:15 -07:00
ohemorange
fd62a09197 dump test farm failure logs (#8740) 2021-03-24 16:19:54 -07:00
Brad Warren
8d8b35b7c0 update requirements (#8739) 2021-03-24 15:55:30 -07:00
Brad Warren
74f6f734c8 remove outdated comment (#8736) 2021-03-25 08:00:47 +11:00
Brad Warren
0480959893 use pip in test_sdists.sh (#8737) 2021-03-24 11:50:34 -07:00
ohemorange
f90e93134c Upgrade cryptography to 3.4.6 (#8730)
* Upgrade cryptography to 3.4.6

* Fix comment with instructions for how to use hashin

* run tools/rebuild_certbot_constraints.py

* add deps for building cryptography in snaps

* Update cryptography build dependencies for docker

* Update sources for test farm tests

* Remove rust if it's installed for test farm tests

* source bootstrap script and call sudo as needed
2021-03-24 10:29:12 -07:00
Mads Jensen
d3b74f41e0 Added missing from typing imports. (#8724) 2021-03-23 21:33:47 +01:00
Brad Warren
1d7ddb0c0c fix pylint (#8729) 2021-03-23 13:01:01 -07:00
Brad Warren
54b0b98988 use worker terminology (#8728)
This will be needed for me to update `pytest-xdist` as part of https://github.com/certbot/certbot/issues/8705 since `pytest-xdist` removed the "slave" terminology. See https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst#deprecations-and-removals.
2021-03-23 11:29:01 -07:00
alexzorin
9fdb24331c docs: rewrite "Automated Renewals" in User Guide (#8717) 2021-03-22 15:05:37 -07:00
Adrien Ferrand
84178e2773 Do not reuse existing builds on Launchpad when executing snapcraft remote-build (#8719)
We observed recently several unexpected behavior during the execution of snap jobs in Azure. In particular it seems that `snapcraft remote-build` is tending to reattach to the latest builds on Launchpad triggered by the nightly builds on master, independently from the actual branch, status of the code, or targeted architectures.

Primarily if the builds on Launchpad are stalled for some reason, it blocks effectively any other Azure snap jobs until someone manually cancel the builds on Launchpad. Secondarily it means that the outcome of the builds may be inconsistent, because they can be the result of a build for the master source even if you are on a PR that modifieds these sources (including `snapcraft.yaml`).

After digging in `snapcraft` source code, I realized that the signature computed to understand if a build should be resumed, is not based one some hashes against the snapcraft working directory content, but is simply a hash of the working directory absolute path *itself*. It means that every builds triggered from the working directory `/my/path/certbot` for instance, are recognized as the same unique build on Launchpad side, and may be resumed if they already exist, and so independently from the source code, `snapcraft.yaml` or targeted archs.

For the record, relevant parts in `snapcraft` source code:
82024d3748/snapcraft/project/_project.py (L44)
82024d3748/snapcraft/project/_project.py (L86-L89)
82024d3748/snapcraft/cli/remote.py (L128-L132)

This PR makes effectively the resume build mechanism effectively a noop by moving the source code first in a temporary directory with random name before running `snapcraft remote-build`. This way the signature is never the same and builds are always recognized as brand new builds.

* Invalidate snapcraft remote-build cache by using a temporary workspace.

* Capture one more state in the build
2021-03-22 10:39:09 -07:00
osirisinferi
ae2247163e Remove empty lines from certbot certificates when (#8723)
.. envoked with `--cert-name` or `-d`.
2021-03-22 08:42:23 +11:00
Adrien Ferrand
6bc8b3d2ba Precise the certificate naming convention mechanism in the compatibility document (#8652)
* Precise the certificate naming convention mechanism in a note.

* Add certificate name convention in user guide, refer to it in compatibility page.

* Update certbot/docs/compatibility.rst

Co-authored-by: alexzorin <alex@zor.io>

* Update certbot/docs/using.rst

Co-authored-by: alexzorin <alex@zor.io>

* Update certbot/docs/using.rst

Co-authored-by: alexzorin <alex@zor.io>

* Improve the note about naming conventions

Co-authored-by: alexzorin <alex@zor.io>
2021-03-22 08:39:54 +11:00
ohemorange
40ae5d939e Fix linux-py39-cover test (#8720)
* update setuptools

* upgrade Markupsafe
2021-03-19 14:27:29 -07:00
Brad Warren
1b39d3dc47 switch to wait_until_running (#8715) 2021-03-16 17:53:43 -07:00
Brad Warren
2324c1bb7a Update installing from source instructions (#8713) 2021-03-15 14:10:44 -07:00
Adrien Ferrand
bc892e04c4 Fixing imports in cli module (#8708)
While working on #8640, I realized that there were some hidden circular dependencies in certbot._internal.cli package. Then cerbot could break if the order of these imports changes.

This PR fixes that and apply isort on top of the result.
2021-03-11 13:17:41 -08:00
Adrien Ferrand
0962b0fc83 Kill current snapcraft build when a "Chroot problem" is encountered (#8442)
* Kill snapcraft build when a "Chroot problem" is encountered

* Display specific helper for "Chroot problem" status and cancel retry mechanism in this case.

* Isolate build tmp directories

* Configure XDG_CACHE_HOME

* Kill snapcraftctl with chroot problem is encountered
2021-03-11 13:08:20 -08:00
Adrien Ferrand
dd6f2f565e Convert Python 2 type hints to Python 3 types annotations (#8640)
Fixes #8427

This PR converts the Python 2 types hints into Python 3 types annotations. I have used the project https://github.com/ilevkivskyi/com2ann which has been designed for that specific purpose and did that very well.

The only remaining things to do were to fix broken type hints that became wrong code after migration, and to fix lines too long with the new syntax.

* Raw execution of com2ann

* Fixing broken type annotations

* Cleanup imports
2021-03-10 11:51:27 -08:00
Brad Warren
f2d8c81e9b remove reference to acme.magic_typing from docs (#8709) 2021-03-09 16:53:44 -08:00
Adrien Ferrand
67b65bb2c0 Deprecate acme.typing_magic module, stop using it in certbot (#8643)
* Deprecate acme.magic_typing, stop to use it in certbot

* Isort

* Add a changelog entry

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-03-09 16:12:32 -08:00
alexzorin
76895457c9 dns-digitalocean: use a 30 second TTL (#8693)
Fixes an issue with long TTLs and caching behavior on DigitalOcean's
DNS hosting service.
2021-03-09 15:56:51 -08:00
Mads Jensen
c02b2d30f2 Removed Python legacy __future__ imports (#8697)
There are still some left, but the `modification_check` test fails. Some are still in `tools`, and they can probably be removed as well. `with_statement` was introduced officially in Python 2.5, so there's really old stuff in the code base.
2021-03-05 16:53:20 -08:00
Brad Warren
94dc6936e7 Final update to certbot-auto (#8706)
Fixes https://github.com/certbot/certbot/issues/8690.

After this PR, we'll let the release script make its automated changes to certbot-auto as part of the 1.14.0 release and then never make any code changes to certbot-auto ever again!

* disable upgrades on debian

* update test_leauto_upgrades.sh

* update changelog
2021-03-05 14:14:32 -08:00
Mads Jensen
a3abcc001a Removed a Python 2 fallback in certbot.Reverter. (#8694)
* Removed a Python 2 fallback in certbot.Reverter.

* Removed a Python < 3.6 fallback in certbot-apache._internal.parser.
2021-03-04 08:10:56 +11:00
Brad Warren
9643e85b4c Merge pull request #8699 from certbot/candidate-1.13.0
Release 1.13.0
2021-03-03 13:00:31 -08:00
Erica Portnoy
9d97be3a84 Bump version to 1.14.0 2021-03-02 13:50:04 -08:00
Erica Portnoy
4d6db0eb71 Add contents to certbot/CHANGELOG.md for next version 2021-03-02 13:50:03 -08:00
Erica Portnoy
92a66454b6 Release 1.13.0 2021-03-02 13:49:58 -08:00
Erica Portnoy
976068b5a0 Update changelog for 1.13.0 release 2021-03-02 13:37:04 -08:00
alexzorin
1e30723003 revoke: try determine the server automatically (#8691)
* revoke: try determine the server automatically

When revoking via --cert-name, use the server from the lineage (unless
overriden by the CLI).

* RenewableCert.storage might be None

* guard against an empty lineage server
2021-03-01 12:56:22 -08:00
Brad Warren
496a4ced25 Remove broken test for typing import failure (#8692)
* remove broken test

* fix coverage

* don't worry about getattr test
2021-02-26 16:05:34 -08:00
alexzorin
fab9bfd878 nginx: authenticate all matching vhosts for HTTP01 (#8663)
* nginx: authenticate all matching vhosts for HTTP01

Previously, the nginx authenticator would set up the HTTP-01 challenge
response on a single HTTP vhost which matched the challenge domain.

The nginx authenticator will now set the challenge response on every
vhost which matches the challenge domain, including duplicates and HTTPS
vhosts.

This makes the authenticator usable behind a CDN where all origin
traffic is performed over HTTPS and also makes the authenticator work
more reliably against "invalid" nginx configurations, such as those
where there are duplicate vhosts.

* some typos

* dont authenticate the same vhost twice

One vhost may appear in both the HTTP and HTTPS vhost lists. Use a set()
to avoid trying to mod the same vhost twice.

* fix type annotations

* rewrite changelog entry
2021-02-26 13:43:22 -08:00
Yuma Mihira
d3ca6af982 Insert new line before "More details about these changes can be found on our GitHub repo." (#8645)
Fixing #8634. It's my first time contributing to this repository, if there's something wrong please let me know.

Before this fix

```
$ python3 extract_changelog.py 1.12.0
...
### Fixed
* Fixed the apache component on openSUSE Tumbleweed which no longer provides
  an apache2ctl symlink and uses apachectl instead.
* Fixed a typo in `certbot/crypto_util.py` causing an error upon attempting `secp521r1` key generation
More details about these changes can be found on our GitHub repo.
```

After this fix

```
$ python3 extract_changelog.py 1.12.0
...
### Fixed
* Fixed the apache component on openSUSE Tumbleweed which no longer provides
  an apache2ctl symlink and uses apachectl instead.
* Fixed a typo in `certbot/crypto_util.py` causing an error upon attempting `secp521r1` key generation

More details about these changes can be found on our GitHub repo.
```
2021-02-25 16:30:48 -08:00
Mads Jensen
540fd6db93 Dictionaries are ordered by insert by default on Python 3.6. (#8678) 2021-02-25 15:41:05 -08:00
Mads Jensen
b0e35c694e Remove import fallback of urllib2 in tests/modification-check. (#8677) 2021-02-25 14:59:11 -08:00
Mads Jensen
67c2b27af7 Stop inheriting from object. It's unneeded on Python 3+. (#8675) 2021-02-25 14:59:00 -08:00
Mads Jensen
135187f03e Python 3 obsoletes explicit __ne__ methods (#8676)
This shouldn't be needed as of Python 3+.

https://stackoverflow.com/questions/4352244/should-ne-be-implemented-as-the-negation-of-eq-in-python#30676267
2021-02-25 14:50:54 -08:00
Brad Warren
e742cfaa21 dont set required to False (#8689) 2021-02-26 08:39:55 +11:00
alexzorin
f71298f661 cli: make key_path and cert_path always be a str (#8687)
There is some code in [`_paths_parser`](ae3ed200c0/certbot/certbot/_internal/cli/paths_parser.py (L17-L34)) which has the effect of varying the value type of `config.cert_path` and `config.key_path` based on the CLI verb. When the verb is `revoke`, the type is a tuple `(path: str, contents: bytes)`, otherwise it is a single `str` representing the file path. (I wasn't able to find a written reason as to why it works this way).

This commit removes that special `revoke` case and ensures it is always a `str`.

Why change it now?

I am trying to write some changes and there's some code in `cert_manager` which only works if the verb is `revoke`, you hack `config.cert_path` to be a tuple beforehand, or you [(not actually in `master`) try sniff for the value type](49911afaa6/certbot/certbot/_internal/cert_manager.py (L224-L227)). I have a bad feeling about such workarounds. I would prefer to just make these variables simpler to use, but I'm open to opinions.

In addition to the test suites, I've manually tested `revoke` (including by `--key-path`) and `install`. Are there other places I may have missed?

Unblocks #8636 and #8671.
2021-02-25 11:32:21 -08:00
alexzorin
025eb16c7a docs: rewrite "Revoking certificates" (#8657)
* docs: rewrite "Revoking certificates"

- `--cert-name` is supported since a long time ago
- `--delete-after-revoke` is default
- Mention that non-default `--server` must be specified
- Document difference between acme key/cert key revocation methods
- Reshuffle text to keep more important things earlier

* minor edits

* remove revocation note

* remove "preauthorization" revocation method

* rewrite deletion note
2021-02-25 10:22:40 -08:00
ohemorange
ae3ed200c0 Remove check for 'fake' in issuer name when renewing certs (#8685)
Fixes #8680.

We seem to have no existing testing code anywhere in this vicinity, so figured I'd get this up quickly then work on that. Manual tests (renew staging certificate, should allow it; renew non-staging cert as staging, should error) passed.

* Remove check for 'fake' in issuer name when renewing certs

* Change fake issuer name to make sure we're not relying on it anywhere
2021-02-24 14:51:57 -08:00
Adrien Ferrand
c3d6fca3eb Make certbot constraint file independent from certbot-auto + update cryptography (#8649)
* Refactor to not depend on certbot-auto dependencies pinning anymore

* Update constraints

* Replaces references

* Upgrade AWS dependencies pinning

* Fix script

* Fix Windows installer builds

* Fixing sdists letstest script

* Pin cryptography on 3.1.1 specifically for RHEL/CentOS 7 to avoid build failures during test_sdists test.

* Finish fix

* Fix VERSION_ID in RHEL 7
2021-02-23 15:29:52 -08:00
Brad Warren
c43f4fe518 upgrade to 3.8.8 (#8682)
Fixes https://github.com/certbot/certbot/issues/8681. https://python-security.readthedocs.io/vuln/ctypes-buffer-overflow-pycarg_repr.html is the best resource I found linking to the original Python bug, when each Python branch was fixed, etc.
2021-02-23 13:20:04 -08:00
Mads Jensen
0f3f07b5cb Removed backport of unittest.assertLogs (#8673)
* Removed backport of unittest.assertLogs

* Update parser_test.py
2021-02-22 09:34:56 +11:00
Mads Jensen
ef265eccaf Remove import fallback for collections.abc (#8674) 2021-02-22 09:23:42 +11:00
Adrien Ferrand
c0eccdd358 Deprecate certbot-auto specific flags (#8641)
This PR deprecates the certbot-auto specific CLI flags, in the perspective of removing them in a future release as said in #8483.

* Deprecate certbot-auto specific flags

* Update changelog

* Clean tests

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-02-12 16:14:46 -08:00
Adrien Ferrand
c59775c3c0 Disable certbot-auto upgrade on RHEL-like systems (#8653)
Fixes #8637

* Disable upgrade for RHEL-like systems

* Remove letstest on Amazon Linux

* Update changelog
2021-02-10 15:17:51 -08:00
Steffen Neumann
cf062f4c6d Fix ubuntu package name (#8654)
Since Ubuntu 18.04 there is python3-certbot-apache which should be the recommended version. 
The Debian package names should probably be updated accordingly.
2021-02-09 12:18:29 -08:00
Brad Warren
3d0dad8718 Remove dependency on six (#8650)
Fixes https://github.com/certbot/certbot/issues/8494.

I left the `six` dependency pinned in `tests/letstest/requirements.txt` and `tools/oldest_constraints.txt` because `six` is still a transitive dependency with our current pinnings.

The extra moving around of imports is due to me using `isort` to help me keep dependencies in sorted order after replacing imports of `six`.

* remove some six usage in acme

* remove six from acme

* remove six.add_metaclass usage

* fix six.moves.zip

* fix six.moves.builtins.open

* six.moves server fixes

* 's/six\.moves\.range/range/g'

* stop using six.moves.xrange

* fix urllib imports

* s/six\.binary_type/bytes/g

* s/six\.string_types/str/g

* 's/six\.text_type/str/g'

* fix six.iteritems usage

* fix itervalues usage

* switch from six.StringIO to io.StringIO

* remove six imports

* misc fixes

* stop using six.reload_module

* no six.PY2

* rip out six

* keep six pinned in oldest constraints

* fix log_test.py

* update changelog
2021-02-09 11:43:15 -08:00
sommersoft
edad9bd82b Fix Sphinx manpage Building (#8646)
* certbot docs: include & orphan 'man/cerbot.rst'; fixes manpage building

* acme docs: include & orphan 'man/jws.rst'; fixes manpage building
2021-02-09 11:29:31 +01:00
Matt W
2a16aa16c3 Update cli.ini (#8603)
* Update cli.ini

Sharing back some extended examples I desired, did not find,  and derived on my own

* Update cli.ini

Alex,
ok - simplified as requested
Matt

* Update cli.ini

removed trailing quote on line 32

* Update certbot/examples/cli.ini

Co-authored-by: alexzorin <alex@zor.io>

* Update certbot/examples/cli.ini

Co-authored-by: alexzorin <alex@zor.io>

* Update certbot/examples/cli.ini

Co-authored-by: alexzorin <alex@zor.io>

* remove stray newline

Co-authored-by: alexzorin <alex@zor.io>
2021-02-07 18:19:49 +11:00
Brad Warren
711cc95dc4 Remove mock dependency (#8630)
Fixes https://github.com/certbot/certbot/issues/7913.

I only added the deprecation warning to `certbot.tests.util` because that's the only place where I think someone could be using the `mock` module through our API.

* remove external mock from acme

* update Certbot's mock usage

* remove mock dependency in plugins

* remove external mock from compatibility test

* add changelog entry
2021-02-05 15:51:18 -08:00
Brad Warren
c2ee0d2938 Remove requests[security] dependency (#8626)
Fixes https://github.com/certbot/certbot/issues/7901.

* stop using requests[security]

* add changelog entry

* remove unused import
2021-02-05 15:33:45 -08:00
Brad Warren
c668172ef0 merge dev and dev3 (#8639) 2021-02-04 21:31:47 +11:00
Brad Warren
666ee35e29 remove crufty pytest warning (#8638) 2021-02-04 21:04:03 +11:00
Brad Warren
13af3f7ec2 Cleanup venv scripts (#8629)
Fixes https://github.com/certbot/certbot/issues/8387.

* update _venv_common.py

* delete venv.py scripts

* rename venv script

* update relevant venv3 references

* remove set_python_envvars
2021-02-03 12:03:09 -08:00
Brad Warren
5ad0c254ca Merge pull request #8624 from certbot/external-mock
Fixes #8616.

Add tests with external mock
2021-02-03 12:02:43 -08:00
Brad Warren
236062c2d2 Merge pull request #8632 from certbot/candidate-1.12.0
Release 1.12.0
2021-02-02 13:11:27 -08:00
Erica Portnoy
2bcd8c59db Bump version to 1.13.0 2021-02-02 11:06:48 -08:00
Erica Portnoy
57cba3690d Add contents to certbot/CHANGELOG.md for next version 2021-02-02 11:06:47 -08:00
Erica Portnoy
786a130b7d Release 1.12.0 2021-02-02 11:06:40 -08:00
Erica Portnoy
df866b907b Update changelog for 1.12.0 release 2021-02-02 10:58:41 -08:00
Brad Warren
f0b32783f0 Start disabling certbot-auto upgrades (#8623)
* add amazon linux to auto targets

* disable updates outside of debian and rhel

* test certbot-auto with disabled upgrades

* try new approach to testing

* remove bad space

* tweak error text

* add changelog entry

* fix bad certbot-auto commit

* test new error text

* update changelog

* update error text
2021-02-01 13:11:04 -08:00
Brad Warren
534af33a50 add external-mock tests to azure config 2021-01-29 15:32:04 -08:00
Brad Warren
2e33aec8a8 add tests with external mock library 2021-01-29 15:31:11 -08:00
ohemorange
bdfb9f19c4 Remove deprecated options as early as possible using an explicit list (#8617)
* Remove deprecated options as early as possible using an explicit list

* add deprecated options to cli init import list

* use correct dict comprehension syntax for py3

* lint

* add test for renewal reconstitution code

* add test to ensure we're not saving deprecated values

* comment code
2021-01-28 12:34:50 -08:00
Brad Warren
b4e955a60e Switch away from ubuntu-latest (#8606)
I noticed warnings on Azure like [this](https://dev.azure.com/certbot/certbot/_build/results?buildId=3311&view=logs&j=d74e04fe-9740-597d-e9fa-1d0400037dfd) which say:

> ##[warning]Ubuntu-latest pipelines will use Ubuntu-20.04 soon. For more details, see https://github.com/actions/virtual-environments/issues/1816

I was worried about us suddenly switching to Ubuntu 20.04 and things breaking so I tested that `ubuntu-20.04` works and am opening this PR to switch things over explicitly now. I'd rater have our VM images pinned to specific versions than a generic version specification like `latest` which might see an upgrade and break our tests unexpectedly.

I ran the notification code on Ubuntu 20.04 at https://dev.azure.com/certbot/certbot/_build/results?buildId=3315&view=results and you can see the notification at https://opensource.eff.org/eff-open-source/pl/ojjhde5j4jyw7dcurd5zfduymr.
2021-01-25 15:20:51 -08:00
Adrien Ferrand
7399807ff2 Drop Python 2 support (#8591)
Fixes #8389 #8584.

This PR makes the necessary modifications to officially drop Python 2 support in the Certbot project.

I did not remove the specific Python 2 compatibility branches that has been added in various places in the codebase, to reduce the size of this PR and this will be done in a future one

* Update classifiers and python_requires in setup.py

* Remove warnings about Python 2 deprecation

* Remove Azure jobs on Python 2.7

* Remove references to python 2 in documentation

* Pin dnspython to 2.1.0

* Update changelog

* Remove warning ignore
2021-01-25 15:07:43 -08:00
Brad Warren
00235d3807 Switch oldest tests to Python 3 (#8590)
Fixes https://github.com/certbot/certbot/issues/8580.

With this PR, it should now be possible to run the oldest tests natively on Linux, at least when using an older version of Python 3, which hasn't been possible in a long time. Unfortunately, this isn't possible on macOS which I opened https://github.com/certbot/certbot/issues/8589 to track.

You can see the full test suite running with these changes at https://dev.azure.com/certbot/certbot/_build/results?buildId=3283&view=results.

I took the version numbers for the packages I updated by searching for the oldest version of the dependency I think we should try and support based on the updated comments at the top of `oldest_constraints.txt`. While kind of annoying, I think it'd be a good idea for the reviewer to double check that I didn't make a mistake with the versions I used here.

To find these versions, I used https://packages.ubuntu.com, https://packages.debian.org, and a CentOS 7 Docker image with EPEL 7 installed. For the latter, not all packages are available in Python 3 yet (which is something Certbot's EPEL package maintainers are working on) and in that case I didn't worry about the system because I think they can/will package the newest version available. If they end up hitting any issues here when trying to package Certbot on Python 3, we can always work with them to fix it.

* remove py27 from oldest name

* update min cryptography version

* remove run_oldest_tests.sh

* upgrade setuptools and pyopenssl

* update cffi, pyparsing, and idna

* expand oldest_constraints comments

* clarify oldest comment

* update min configobj version

* update min parsedatetime version

* quote tox env name

* use Python 3.6 in the oldest tests

* use Python 3.6 for oldest integration tests

* properly pin asn1crypto

* update min six version

* set basepython for a nicer error message

* remove outdated python 2 oldest constraints
2021-01-25 12:59:14 -08:00
Brad Warren
adb7e5e62f remove unused pyicu pinning (#8607) 2021-01-16 07:13:59 +11:00
Miltos
261b5a76d8 Minor fix to logging message (#8605)
* Minor fix to logging message

the `if socket_kwargs` will always evaluate to `true`.

* Update acme/acme/crypto_util.py

Co-authored-by: alexzorin <alex@zor.io>
2021-01-14 20:39:42 +11:00
Aaron Gable
2fca48caaa --preferred-chain: only match root name (#8596)
* --preferred-chain: only match root name

Currently, when certbot is given the `--preferred-chain='Some Name'`
flag, it iterates through all alternate chains offered by the ACME
server until it finds any certificate which has `'Some Name'` as its
Issuer Common Name. Unfortunately, this means that if the desired
alternate chain is a strict subset of any earlier chain (e.g. the
default chain is 'EE <-- Int <-- Root1 <-- Root2', but the desired
chain is 'EE <-- Int <-- Root1'), there is no name which can be
provided by the user which will allow the client to select the desired
chain.

This change makes it so that the `find_chain_with_issuer` logic only
cares about the Issuer Common Name found in the last certificate in
each chain. In the example above, the user would then be able to get
their desired chain by specifying `--preferred-chain='Root1'`: although
that name appears in the default chain, it does not appear in the
highest certificate of that chain.

This change is technically backwards-incompatible. However, the only
advice that has been given to users of certbot (and the only usecase
that we believe has existed so far) involved setting the flag to a
value that is the name of a root, not an intermediate, so we don't
expect any real-world configurations or use-cases to be broken.

Fixes #8577

* Update interfaces.py
2021-01-14 12:12:48 +11:00
Adrien Ferrand
c0917a0302 Use os.path.normcase to have Windows compatible challenge paths on Windows (#8599)
* Use os.path.normcase to have Windows compatible challenge paths on Windows.

* Add integration test and fix lint
2021-01-13 14:38:57 -08:00
alexzorin
13d4a99251 test: certbot-ci crash due to no p521 on boulder (#8602)
* test: certbot-ci crash due to no p521 on boulder

The bugfix in #8598 added an integration test to request a certificate
for an EC P-521 key, which is unsupported when ACME_SERVER=boulder,
failing our nightly integration tests.

* add an integration test for all EC curves
2021-01-12 16:08:32 -08:00
Brad Warren
b9de48e93e Always sign certbot-auto with a yubikey (#8600)
* always sign certbot-auto with the yubikey

* remove tools/offline-sigrequest.sh
2021-01-12 13:45:26 -08:00
Brad Warren
7a02deeeba Modify release script to support yubikey sig (#8574)
Using `tools/offline-sigrequest.sh` is annoying. A while ago I looked into how we could use our yubikeys for our Windows code signing signatures and in the process of doing that learned how to use them for the certbot-auto signature. The certbot-auto signature won't be needed once https://github.com/certbot/certbot/issues/8526 is resolved and we've implemented that plan which will hopefully be in 2-3 months, but despite that, doing this still felt worth it to me.

The script still defaults to using `tools/offline-sign.sh`, but you can set an environment variable to use the yubikey instead. I tested both branches here and it worked.
2021-01-11 15:41:55 -08:00
Daniel Almasi
42f20455cd Fix EC curve name typo in crypto_util (#8598)
* Fix EC curve name typo in crypto_util

Fix typo of secp521r1 in crypto util module.
- secp521r1 is to be supported by certbot, but a typo of "SECP521R1" in the input validation section of the make_key function results in an error being thrown

* Add myself to authors.md 

Add myself to authors.md ^^

* Add test for secp521r1 key generation

Add test for secp521r1 key generation to cli-tests
2021-01-11 13:40:12 -08:00
Antonio Larrosa
434ca1985f Change the SUSE override to use apachectl (#8592)
For some time, SUSE distributions have had both an apachectl
executable and an apache2ctl compat symlink so both could be used
but apachectl is preferred since that's the official upstream name.
This is currently the case in SLE 15 SP2 and openSUSE Leap 15.2
(and every release since SLE 12 SP1)

OTOH, openSUSE Tumbleweed removed the apache2ctl compat symlink
some weeks ago and both SLE/Leap will follow in one of the next
releases so it's better to change certbot to use the official name,
apachectl.
2021-01-08 09:49:21 -08:00
Brad Warren
4a9748ace5 Add matching route53 readme (#8583)
Building on https://github.com/certbot/certbot/pull/8581, our other DNS plugins have a simple `README.rst` file and this PR adds a matching one for the route53 plugin.
2021-01-07 11:30:52 -08:00
sommersoft
fb8cd063eb Automatically Catch Sphinx Errors (#8530)
* clean up some Sphinx warnings

* first attempt at a doc-test pipeline job

* fix formatting

* fix test name

* set env for bash

* try bash vs script

* maybe it didn't like me setting 'PATH'...derp

* drop use of venv

* sphinx-build isn't a py script

* try activating venv

* docs: remove unused html_static tags

* clean up final sphinx build errors for certbot

* clean up final sphinx build errors for acme

* better names for docs pipeline

* fix spelling

* add docs_extras to setup.py

* remove temp doc-testing pipeline; add template to main.yml

* rearrange pipeline execution; run sphinx builds in one job

* add documentation note to compat.os

* add uninstall.rst as a sub-toctree to avoid build error
2021-01-07 20:26:59 +01:00
Brad Warren
e602736bda remove route53 readme (#8581) 2021-01-07 08:08:15 +01:00
Adrien Ferrand
ccde1eef64 Enable Python 3.8 for Certbot on Windows (#8465)
Now that we have a new pipstrap script with recent version of pip, dependencies for Windows can be resolved correctly on Python 3.8.

This PR enables tests on Python 3.8, and package Certbot for Windows on Python 3.8 also. I do not move up to Python 3.9 since some dependencies (`cryptography`, `pynacl`) do not provide wheels for Python 3.9 yet on Windows, which would require a complete C++ build system to compile them.

* Enable windows tests on Python 3.8 and package it on Python 3.8 also.

* Upgrade pynsist, nsis and pywin32, remove old workarounds

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-01-06 16:17:34 -08:00
Brad Warren
c44a5a7701 Fix plugin param type (#8578)
* Fix plugin param type in updater

The command used to do this was:

sed -i 's/\(:type .*plugins:\) `list` of `str`/\1 certbot._internal.plugins.disco.PluginsRegistry/g' certbot/certbot/_internal/updater.py

* fix plugin param type in main.py

The command used to do this was:

sed -i 's/\(:type .*plugins:\) `list` of `str`/\1 plugins_disco.PluginsRegistry/g' certbot/certbot/_internal/main.py
2021-01-06 18:26:01 +11:00
Brad Warren
6e1d042f76 mock out plugin discovery in test_plugins (#8576) 2021-01-06 18:14:43 +11:00
Brad Warren
daf989fc21 skip meta creation to speed up tests (#8575) 2021-01-06 17:47:25 +11:00
ohemorange
5c3fd7d9ee Merge pull request #8573 from certbot/candidate-1.11.0
Update files from 1.11.0 release
2021-01-05 13:25:11 -08:00
Brad Warren
fc6c238bf9 Bump version to 1.12.0 2021-01-05 09:51:11 -08:00
Brad Warren
a49b84d64e Add contents to certbot/CHANGELOG.md for next version 2021-01-05 09:51:10 -08:00
Brad Warren
7567e8d8db Release 1.11.0 2021-01-05 09:51:09 -08:00
Brad Warren
02a5d000cb Update changelog for 1.11.0 release 2021-01-05 09:37:05 -08:00
Adrien Ferrand
98fb9d2d93 Forbid os.readlink() (#8472)
The method `os.readlink()` has a significant behavior change with Python 3.8+ on Windows. 

Starting with this version, it will return the resolved path in its "extended-style" form unconditionally, a form which allows to use more than 259 characters in a Windows path, and its string representation is prepended with "\\\\?\\".

See https://docs.microsoft.com/fr-fr/windows/win32/fileio/naming-a-file?redirectedfrom=MSDN#maximum-path-length-limitation

Problem is that `os.readlink()` does it for any path, including paths that could be represented with the normal form. As a consequence, any string comparison with a path provided in the normal form will fail even if it represents the same path. This makes Certbot partially break on Windows with Python 3.8.

My proposition in this PR is to forbid `os.readlink()`, and provide `certbot.compat.filesystem.readlink()` which serves the same purpose at resolving the pointed path of a link, and has a consistent behavior over supported Python versions.

* Forbid os.readlink()

* Use readlink

* Raise error with long paths on Windows

* Add unit tests

* Update certbot/certbot/compat/filesystem.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2021-01-05 09:34:12 -08:00
alexzorin
32fb89df7e docs: add missing /directory to ACMEv2 server URL (#8564) 2020-12-22 15:10:59 -08:00
Brad Warren
d3b82a4e8e Fix test farm tests by using a local Pebble instance (#8561)
[As discussed in Mattermost](https://opensource.eff.org/eff-open-source/pl/yhtp4qu4zpfczm5wxmzxhndrto), our Apache test farm tests are failing because the CA certificate in the old version of boulder we have pinned expired over the weekend. This PR fixes that by running a local Pebble instance instead of an external boulder instance.

* switch from external boulder to local pebble

* add --http-01-port to run_acme_server
2020-12-22 10:24:20 -08:00
Jacob Hoffman-Andrews
18faf4f7ab Edit certs -> certificates in user-facing text. (#8541)
* Edit certs -> certificates in user-facing text.

To reduce confusion, we should consistently use the full term.

* Edit certs->certificates in more user-facing text.

* fix failing lint (line too long)

* fix typo

Co-authored-by: Jacob Hoffman-Andrews <github@hoffman-andrews.com>

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2020-12-21 16:00:31 -08:00
Tim Gates
a7c3c0b90c docs: fix simple typo, serveral -> several (#8558)
There is a small typo in certbot/certbot/ocsp.py.

Should read `several` rather than `serveral`.
2020-12-21 15:29:00 -08:00
Brad Warren
421e8b6270 fix fix_test_non_systemd_os_info (#8539) 2020-12-21 13:31:37 -08:00
Brad Warren
8e7353900c Add certbot-auto uninstall docs (#8552)
This is part of #8545.

* add certbot-auto uninstall docs

* add uninstall.rst

* write a more aggressive sed command
2020-12-21 09:02:22 -08:00
Lorenzo Fundaró
1146f35519 Fix TTL mismatch leading to HTTP 412 (#8549)
* Fix TTL mismatch leading to HTTP 412

This PR is a follow up from #8521 where we address the
issue of potentially having a mismatch of TTL when executing
a DNS change (transaction = deletion + additions). Let's say
we have a record `foo.org 30 IN TXT foo-content` with TTL 30s,
when creating challenge or cleaning we might need to perform
a deletion operation in the transaction. Currently certbot
would ask Google API to delete the foo record like this:
`foo.org 60 in TXT foo-content` ignoring the record's original
TTL and using 60s instead. This leads to HTTP 412 as Google would
expect a perfect match of what we want to delete with what it is
on the DNS. See also #8523

* remove ttl from default data to avoid confusions

* Refactor tests and add a missing case

This commit adds a test that covers the case when we are
deleting a TXT record which contains a single rrdatas. Also,
refactoring a couple of tests.

* Make get_existing_txt_rrset documentation more precise about return value

* Add missing assertions in tests.

* fix linting issues

* Mention fix on changelog

* Explain fix around user impact

* Explain what happens when no records are returned

* Update certbot/CHANGELOG.md

* Update certbot/CHANGELOG.md
2020-12-21 17:17:29 +11:00
Warren White
198f7d66e6 Flag that DNS plugins are distributed separately from Certbot (#8479)
* Added note to each DNS documentation index page to mention that plugins need to be installed and are not included as standard.

* Resolved issue with white space in doc files

* Changed wording as discussed in PR.

* Changing URL to new wildcard instructions link

* Update certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py
2020-12-19 16:44:31 +11:00
Brad Warren
e9bdfcc94b Pin DNS plugin snap build dependencies (#8553)
Fixes https://github.com/certbot/certbot/issues/8544 by taking the approach in https://github.com/certbot/certbot/pull/8443.
2020-12-18 15:02:23 -08:00
alexzorin
a8b6a1c98d update_account: print correct message for -m "" (#8537)
* update_account: print correct message for -m ""

When -m "" was passed on the CLI, Certbot would print that it updated
the email to '' (an empty string) rather than printing that it removed
the contact details.

This commit also refactors the update_account tests to be a bit more
modern.

* use addCleanup instead of tearDown in tests
2020-12-19 07:30:17 +11:00
Lorenzo Fundaró
d714ccec05 Fix fetch of existing records from Google DNS (#8521)
* Fix fetch of existing records from Google DNS

There has been many complaints regarding `certbot_dns_google` plugin
failing with:
   * HTTP 412 - Precondition not met
   * HTTP 409 - Conflict
See #6036. This PR fixes that situation. The bug lies on how we
fetch the TXT records from google. For large amount of records
the Google API paginates the result but we ignore the subsequent
pages and assume that if the record is not in the first response then
it doesn't exist. This leads to either HTTP 409, or HTTP 412 or both.
In this PR we leverage the use of filters on the API to get exactly
the records we are looking for. Apart from fixing the problem stated
above, it has the extra benefit of making the process faster by
reducing the amount of API calls and it doesn't require us to handle
any pagination logic

* Explain changes on CHANGELOG

* Edit AUTHORS.md

* make execute static

* Update certbot/CHANGELOG.md

Being more specific for which plugin this fix bug is meant for.

Co-authored-by: alexzorin <alex@zor.io>

* Fix if expression to be more python-idiomatic

Co-authored-by: alexzorin <alex@zor.io>

* Sort AUTHORS.md

* Simplify tests

Make rrs_mock modeling simpler and refactor

* Revert "Simplify tests"

This reverts commit 9de9623ba7466bf76a7d9075d4eba6980cbe0b62.

* Reimplement conditional mock

We still want to use a conditional mock by make it more
simple to understand by using MagicMock.

* Revert "Sort AUTHORS.md"

This reverts commit b3aa35bcf16f393b2e08ca22278d4c0cfe6c7282.

* Add name in AUTHORS.md

Co-authored-by: alexzorin <alex@zor.io>
2020-12-17 21:22:12 +11:00
alexzorin
0465643d0a certbot-ci: fix integration-external tests (#8547)
In 96a05d9, mypy testing was added to certbot-ci, but introduced an
undeclared dependency on acme.magic_typing, resulting in a crash when
run under the integration-external tox environment.

This change uses the typing module in certbot-ci in place of
acme.magic_typing. It is already provided via dev_constraints.
2020-12-17 09:06:21 +01:00
Brad Warren
cbf42ffae1 Clean up certbot-auto docs (#8532)
Fixes https://github.com/certbot/certbot/issues/8519.

I left the `certbot-auto` docs in `install.rst` to avoid breaking links and to help propagate information about our changes there. I moved it closer to the bottom of the doc though since I think our documentation about OS packages and Docker is more helpful to most people.

* clean up certbot-auto docs

* add more info to changelog

* remove more certbot-auto references
2020-12-16 12:42:51 -08:00
Brad Warren
fcdfed9c2c remove reference to letsencrypt(-auto) (#8531) 2020-12-16 11:43:32 -08:00
Mads Jensen
96a05d946c Added certbot-ci to lint section. Silenced and fixed linting warnings. (#8450) 2020-12-16 20:34:12 +01:00
Adrien Ferrand
d38766e05c Enable again build isolation with proper pinning of build dependencies (#8443)
Fixes #8256

First let's sum up the problem to solve. We disabled the build isolation available in pip>=19 because it could potential break certbot build without a control on our side. Basically builds are not reproductible. Indeed the build isolation triggers build of PEP-517 enabled transitive dependencies (like `cryptography`) with the build dependencies defined in their `pyproject.toml`. For `cryptography` in particular these requirements include `setuptools>=40.6.0`, and quite logically pip will install the latest version of `setuptools` for the build. And when `setuptools` broke with the version 50, our build did the same.

But disabling the build isolation is not a long term solution, as more and more project will migrate on this approach and it basically provides a lot of benefit in how dependencies are built.

The ideal solution would be to be able to apply version constraints on our side on the build dependencies, in order to pin `setuptools` for instance, and decide precisely when we upgrade to a newer version. However for now pip does not provide a mechanism for that (like a `--build-constraint` flag or propagation of existing `--constraint` flag).

Until I saw https://github.com/pypa/pip/issues/9081 and https://github.com/pypa/pip/issues/8439.

Apart the fact that https://github.com/pypa/pip/issues/9081 shows that pip maintainers are working on this issue, it explains how pip works regarding PEP-517 and infers which workaround can be used to still pin the build dependencies. It turns out that pip invokes itself in each build isolation to install the build dependencies. It means that even if some flags (like `--constraint`) are not explicitly passed to the pip sub call, the global environment remains, in particular the environment variables.

Thus it is known that every pip flag can alternatively be set by environment variable using the following pattern for the variable name: `PIP_[FLAG_NAME_UPPERCASE]`. So for `--constraint`, it is `PIP_CONSTRAINT`. And so you can pass a constraint file to the pip sub call through that mechanism.

I made some tests with a constraint file containing pinning for `setuptools`: indeed under isolation zone, the constraint file has been honored and the provided pinned version has been used to build the dependencies (I tested it with `cryptography`).

Finally this PR takes advantage of this mechanism, by setting `PIP_CONSTRAINT` to `pip_install`, the snap building process, the Dockerfiles and the windows installer building process.

I also extracted out the requirements of the new `pipstrap.py` to be reusable in these various build processes.

* Use workaround to fix build requirements in build isolation, and renable build isolation

* Clean imports in pipstrap

* Externalize pipstrap reqs to be reusable

* Inject pipstrap constraints during pip_install

* Update docker build

* Update snapcraft build

* Prepare installer build

* Fix pipstrap constraints in snap build

* Add back --no-build-cache option in Docker images build

* Update snap/snapcraft.yaml

* Use proper flags with pip

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-12-16 10:49:31 -08:00
osirisinferi
c5a0b1ae5d Add path to certbot executable in debug log (#8538) 2020-12-16 15:40:49 +11:00
Brad Warren
fcc8b38c02 remove CentOS 6 cruft from test farm tests (#8534) 2020-12-15 12:00:14 +01:00
Brad Warren
7febc18bb0 Make our test farm tests instances self-destruct (#8536)
* remove unused user data

* have instance self-destruct in case cleanup fails

* correct kwargs

* fix param order
2020-12-15 12:00:00 +01:00
Brad Warren
5151e2afee add OS package warning (#8533) 2020-12-15 10:36:42 +11:00
Adrien Ferrand
3889311557 Setup a timeout to the remote snap build process (#8484)
This PR adds a `--timeout` flag to `tools/snap/build_remote.py` in order to fail the process if the time execution reaches the provided timeout. It is set to 5h30 on the relevant Azure job, while the job itself has a timeout of 6h managed on Azure side. This allows a slightly better output for these jobs when the snapcraft build stales for any reason.
2020-12-11 12:33:11 -08:00
Brad Warren
6d71378c05 Add finish_release flags and CLI parsing (#8522) 2020-12-10 15:13:48 -08:00
Adrien Ferrand
e9a96f5e2a Deprecate support of Apache 2.2 in certbot-apache (#8516)
Fixes #8462

* Deprecate support of Apache 2.2 in certbot-apache

* Add a changelog
2020-12-10 12:57:13 -08:00
Adrien Ferrand
878c3e396f Avoid --system-site-packages during the snap build by preparing a venv with pipstrap that already includes wheel (#8445)
This PR proposes an alternative configuration for the snap build that avoid the need to use `--system-site-package` when constructing the virtual environment in the snap.

The rationale of `--system-site-package` was that by default, snapcraft creates a virtual environment without `wheel` installed in it. However we need it to build the wheels like `cryptography` on ARM architectures. Sadly there is not way to instruct snapcraft to install some build dependencies in the virtual environment before it kicks in the build phase itself, without overriding that entire phase (which is possible with `parts.override-build`).

The alternative proposed here is to not override the entire build part, but just add some preparatory steps that will be done before the main actions handled by the `python` snap plugin. To do so, I take advantage of the `--upgrade` flag available for the `venv` module in Python 3. This allows to reuse a preexisting virtual environment, and upgrade its component. Adding a flag to the `venv` call is possible in snapcraft, thanks to the `SNAPCRAFT_PYTHON_VENV_ARGS` environment variable (and it is already used to set the `--system-site-package`).

Given `SNAPCRAFT_PYTHON_VENV_ARGS` set to `--upgrade` , we configure the build phase as follows:
* create the virtual environment ourselves in the expected place (`SNAPCRAFT_PART_INSTALL`)
* leverage `tools/pipstrap.py` to install `setuptools`, `pip`, and of course, `wheel`
* let the standard build operations kick in with a call to `snapcraftctl build`: at that point the `--upgrade` flag will be appended to the standard virtual environment creation, reusing our crafted venv instead of creating a new one.

This approach has also the advantage to invoke `pipstrap.py` as it is done for the other deployable artifacts, and for the PR validations, reducing risks of shifts between the various deployment methods.
2020-12-10 12:05:32 -08:00
Brad Warren
148246b85b Add reminders to update documentation (#8518)
* Add documentation PR checklist item.

* Update contributing doc
2020-12-09 19:02:53 +11:00
Adrien Ferrand
9045c03949 Deprecate support for Python 2 (#8491)
Fixes #8388

* Deprecate support for Python 2

* Ignore deprecation warning

* Update certbot/CHANGELOG.md

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-12-08 12:19:42 -08:00
Adrien Ferrand
447b6ffaef Completely deprecate certbot-auto (#8489)
Fixes #8296

* Completely deprecate certbot-auto

* Add changelog
2020-12-07 15:18:00 -08:00
alexzorin
38017473c5 add coverage testing to dns-rfc2136 integration (#8469)
* add coverage testing to dns-rfc2136 integration

* add coverage rule for certbot/* as well
2020-12-06 09:23:33 +01:00
alexzorin
dc3ac13750 snap: disable the "user site-packages directory" (#8509)
Although Certbot is a classic snap, it shouldn't load Python code from
the host system. This change prevents packages being loaded from the
"user site-packages directory" (PEP-370). i.e. Certbot will no longer
load DNS plugins installed via `pip install --user certbot-dns-*`.
2020-12-06 09:10:03 +01:00
Mads Jensen
5871de0c07 Removed some unused imports. (#8424)
These were not annotated as something that should be ignored, and the test-suite
passes with these changes.
2020-12-04 14:29:58 +01:00
alexzorin
356e8d84d6 dns-google: improve credentials error message (#8482)
This adds a 'Error parsing credentials file ...' wrapper to any errors
raised inside certbot-dns-google's usage of oauth2client, to make it
obvious to the user where the problem lies.
2020-12-04 14:09:10 +01:00
Adrien Ferrand
d476aa4389 Update both main VA and remote VA to use the provided DNS server (#8467) 2020-12-04 12:00:32 +11:00
alexzorin
22cf94f930 cli: clean up certbot renew summary (#8503)
* cli: clean up `certbot renew` summary

- Unduplicate output which was being sent to both stdout and stderr
- Don't use IDisplay.notification to buffer output
- Remove big "DRY RUN" guards above and below, instead change language
  to "renewal" or "simulated renewal"
- Reword "Attempting to renew cert ... produced an unexpected error"
  to be more concise.

* add newline to docstring

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

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2020-12-03 16:38:59 -08:00
ohemorange
d3166d7072 Merge pull request #8505 from certbot/candidate-1.10.1
Candidate 1.10.1
2020-12-03 12:29:26 -08:00
Brad Warren
67fecbe1e0 Merge branch 'master' into candidate-1.10.1 2020-12-03 11:01:46 -08:00
Brad Warren
1dfac955c7 Bump version to 1.11.0 2020-12-03 10:33:32 -08:00
Brad Warren
38f3d3d185 Add contents to certbot/CHANGELOG.md for next version 2020-12-03 10:33:32 -08:00
Brad Warren
64543d4970 Release 1.10.1 2020-12-03 10:33:30 -08:00
Brad Warren
4c896fd87c Update changelog for 1.10.1 release 2020-12-03 10:20:11 -08:00
Brad Warren
a71e22678f Fix add deprecated argument (#8500) (#8501)
Fixes https://github.com/certbot/certbot/issues/8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See https://github.com/certbot/certbot/issues/6164 for one other example.

In this case, were bitten by the code d1e7404358/certbot/certbot/_internal/cli/helpful.py (L393-L395)

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by https://github.com/certbot/certbot/issues/4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry

(cherry picked from commit 5f73274390)
2020-12-03 09:06:05 +01:00
Mads Jensen
45e48b565d Fix changelog typo (#8497)
Co-authored-by: Adrien Ferrand <ferrand.ad@gmail.com>
2020-12-02 15:12:27 -08:00
Brad Warren
5f73274390 Fix add deprecated argument (#8500)
Fixes https://github.com/certbot/certbot/issues/8495.

To further explain the problem here, `modify_kwargs_for_default_detection` as called in `add` is simplistic and doesn't always work. See https://github.com/certbot/certbot/issues/6164 for one other example.

In this case, were bitten by the code d1e7404358/certbot/certbot/_internal/cli/helpful.py (L393-L395)

The action used for deprecated arguments isn't in `ZERO_ARG_ACTIONS` so it assumes that all deprecated flags take one parameter.

Rather than trying to fix this function (which I think can only realistically be fixed by https://github.com/certbot/certbot/issues/4493), I took the approach that was previously used in `HelpfulArgumentParser.add_deprecated_argument` of bypassing this extra logic entirely. I adapted that function to now call `HelpfulArgumentParser.add` as well for consistency and to make testing easier.

* Rename deprecated arg action class

* Skip extra parsing for deprecated arguments

* Add back test of --manual-public-ip-logging-ok

* Add changelog entry
2020-12-02 15:08:07 -08:00
Brad Warren
87386769f7 Merge pull request #8499 from certbot/remove-centos6-tests-1.10.x
Remove centos6 tests 1.10.x
2020-12-02 13:08:03 -08:00
Brad Warren
7497c51f34 Undo certbot-auto changes and remove centos6 tests
* Don't deprecate certbot-auto quite yet

* Remove centos6 test farm tests

* undo changes to test farm test scripts

(cherry picked from commit e5113d5815)
2020-12-02 12:37:43 -08:00
Adrien Ferrand
1a3c96a955 Deprecate certbot-auto and remove tests
* Completely deprecate certbot-auto

* DeaDeactivate centos6/oraclelinux6 tests

* Remove tests assets

* Remove another test

* Revert "Remove tests assets"

This reverts commit e603afe6c4.

(cherry picked from commit ff3a07dca3)
2020-12-02 12:37:38 -08:00
Brad Warren
d1e7404358 Merge pull request #8498 from certbot/remove-centos6-tests
Remove CentOS 6 tests
2020-12-02 12:35:55 -08:00
Brad Warren
e5113d5815 Undo certbot-auto changes and remove centos6 tests
* Don't deprecate certbot-auto quite yet

* Remove centos6 test farm tests

* undo changes to test farm test scripts
2020-12-02 10:22:44 -08:00
Adrien Ferrand
ff3a07dca3 Deprecate certbot-auto and remove tests
* Completely deprecate certbot-auto

* DeaDeactivate centos6/oraclelinux6 tests

* Remove tests assets

* Remove another test

* Revert "Remove tests assets"

This reverts commit e603afe6c4.
2020-12-02 09:48:57 -08:00
Brad Warren
31b5f1310e Fix changelog typo (#8488)
* fix changelog typo

* remove empty entry
2020-12-02 08:57:04 +11:00
ohemorange
faa8d230c7 Merge pull request #8487 from certbot/candidate-1.10.0
Update files from 1.10.0 release
2020-12-01 12:25:10 -08:00
Brad Warren
baab69e653 Bump version to 1.11.0 2020-12-01 10:35:58 -08:00
Brad Warren
7b687611a4 Add contents to certbot/CHANGELOG.md for next version 2020-12-01 10:35:57 -08:00
Brad Warren
adacc4ab6d Release 1.10.0 2020-12-01 10:35:55 -08:00
Brad Warren
43ee2993f1 Update changelog for 1.10.0 release 2020-12-01 10:22:39 -08:00
alexzorin
f5a88ade54 nginx: fix Unicode crash on Python 2 (#8480)
* nginx: fix py2 unicode sandwich

The nginx parser would crash when saving configuraitons containing
Unicode, because py2's `str` type does not support Unicode.

This change fixes that crash by ensuring that a string type supporting
Unicode is used in both Python 2 and Python 3.

* nginx: add unicode to the integration test config

* update CHANGELOG
2020-11-27 18:15:27 +01:00
Mads Jensen
aea416f654 Fix link typo in README (#8476) 2020-11-25 10:11:51 +01:00
Brad Warren
9a4e95e25a Add Python 3.9 support and tests (#8460)
Fixes https://github.com/certbot/certbot/issues/8134.

* Test on Python 3.9.

* Mention Python 3.9 support in changelog.

* s/\( *'Pro.*3\.\)8\(',\)/\18\2\n\19\2/

* undo changes to tox.ini

* Move more tests to Python 3.9

* Update PyYAML and packages which pinned it back

* Upgrade typed-ast

* Use <= to "pin" dnspython

* Fix lint by telling pylint it cannot be trusted

* Disable mypy on RFC plugin

* add comment about <= support
2020-11-19 12:48:36 -08:00
Brad Warren
9ca7f76505 Merge pull request #8444 from certbot/ecdsa
Integrate the ECDSA certificates feature on master
2020-11-19 11:54:24 -08:00
Brad Warren
a8cede6ae1 Flesh out ECDSA documentation (#8464)
* Changelog tweaks.

* Add ECDSA documentation

* Fix typo
2020-11-19 09:10:56 +01:00
Mads Jensen
be3d0d872f Read files as binary in crypto_util for crypto.load_certificate. (#8371) 2020-11-17 16:02:35 -08:00
Brad Warren
5a85825493 Merge pull request #8458 from certbot/fix-py2-integration
Fix Python 2 integration tests
2020-11-17 15:39:01 -08:00
Alex Zorin
e8139e80be certbot-ci: fix py2 crash in dns_server 2020-11-17 14:58:29 -08:00
Brad Warren
7ba35b4407 import print_function 2020-11-17 11:51:27 -08:00
alexzorin
90557921e3 Add certbot-dns-rfc2136 integration testing (#8448)
* tests: add certbot-dns-rfc2136 integration tests

* dont use 'with' form of socket.socket

fixes py2 crash

* address some feedback:

- conftest: make DNS server a global resource
- conftest: add dns_xdist parameter into node config
- conftest: add --dns-server=bind flag
- conftest: if configured, point the ACME server to the DNS server
- dnsserver: make it sort-of compatible with xdist (future-proofing)
- context: parameterize dns-rfc2136 credentials file (future proofing)
- context: reduce dns-rfc2136 propagation time to speed up tests
- tox: add a integration-dns-rfc2136 target
- rfc2136: add a test/zone for subdelegation
- rfc2136: skip tests if no DNS server is configured

* try add integration-dns-rfc2136 to CI

* mock recursive dns via RPZ

* update --dns-server args and tox.ini args

* address more feedback:

- dns_server: rename rfc2136 creds file to .tpl
- dns_server: dont vary dns server port, instead we will vary zone names (#8455)
- dns_server: log error if bind9 fails to stop cleanly
- dns_server: replace assert with raise
- context: remove redundant _worker_id
- context: remove redundant cleanup override
- context: fix seek/flush in credentials context manager
- context: rename skip_if_no_server -> ...bind_server
- context: add newline EOF

* conftest: document _setup_primary_node sideeffects

* ci: rfc2136-integration from standard->nightly

* fix _stop_bind (function was renamed to stop)

* ignore errors from shutil.rmtree during cleanup

* dns_server: check for crash while polling

* remove --dry-run from rfc2136 test
2020-11-17 09:27:27 +01:00
alexzorin
78edb2889e cli: improve Obtaining/Renewing wording (#8395)
* cli: improve Obtaining/Renewing wording

* dont use logger, and use new phrasing

* .display_util.notify: dont wrap

As this function is supposed to be an analogue for print, we do not want
it to wrap by default.
2020-11-12 16:09:29 -08:00
Adrien Ferrand
553d3279c6 Add --dns-server option in run_acme_server (#7722)
Fixes #7717

This PR adds a `--dns-server` option to the `run_acme_server` test tool, in order to provide an arbitrary DNS server to Pebble or Boulder for the integration tests.

I also take this occasion to make `run_acme_server` a real CLI tool using argparse, and set the `--server-type` (default `pebble`) option as well.

* Set --dns-server flag in run_acme_server

* Default to pebble

* Add documentation

* Configure also Boulder
2020-11-12 15:31:32 -08:00
Mads Jensen
b742b60c4d Use better asserts. Added notes to style guide. (#8451) 2020-11-12 23:33:02 +01:00
Adrien Ferrand
2132cf7f04 Use Python 3.8 for Linux integration tests (#8449)
Do we have any specific reason to run the standard Linux integration tests on Python 2.7?

If not, we should move to a more recent version of Python. This PR does it for Python 3.8.
2020-11-12 12:44:05 -08:00
Brad Warren
f15f4f9838 Add certbot renew --key-type test (#8447)
* Test certbot renew --key-type

* Fix typo
2020-11-12 00:06:50 +01:00
Adrien Ferrand
2a118f3e83 Close the session once snap connections are acquired (#8438)
This PR uses the context manager available for `requests.Session` to close properly the `session` once snap connections have been acquired.
2020-11-11 12:54:29 -08:00
Adrien Ferrand
8f5787008d Handle unexpected key type migration. (#8435)
Fixes #8365

This PR adds a control when `certbot certonly` or `certbot run` are called for a certificate that already exists and would eventually be replaced. As described in #8365, this control is here to ensure that the user will not modify the key type of their certificate (eg. ECDSA to RSA) without an explicit approval (set explicitly `--cert-name` and `--key-type`), since RSA is the default if not specified.

* Handle unexpected key type migration.

* Update certbot-ci/certbot_integration_tests/certbot_tests/test_main.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-11-11 12:36:16 -08:00
alexzorin
db2ffea351 Fix #8436 & #8432 tests (#8440)
* tests: fix leaking patch in eff_test.py

* tests: PrintTest->NotifyTest in .display.util

The function was renamed during #8432. This change renames the test as
well.
2020-11-10 14:42:51 -08:00
alexzorin
bf20f39ceb cli: miscellaneous IReporter removals (#8436)
* certbot delete: use undecorated print

* certbot revoke: use undecorated print

* certbot revoke: remove ireporter usages

* eff: remove IReporter usages

* certbot unregister: remove IReporter usage

* certbot update_account: remove IReporter usages

* certbot run: remove IReporter in duplicate prompt

* fix test_revoke_multiple_lineages
2020-11-09 15:31:27 -08:00
alexzorin
11a4882128 certbot.display: add new method to print CLI messages (#8432)
* IDisplay.notification: add `decorate` param.

The flag allows the caller to control whether the message will be
printed in a decorated way (wrapped by hlines) or in an undecorated
way (similar to print).

It is set to true by default, to reflect the existing behavior of the
function.

* IDisplay.notification: write message to debug log

In the same vein as IReporter, this ensures that all notifications which
are shown to the user also make an appearance in the debug log, which
will aid in troubleshooting.

* restore accidentally deleted newline in decoration

* add helper function for printing status messages

* register: use notify rather than logger

Undoes the change in #8393 in favor of the new helper

* comment .display and ._internal.log

Describing when it is suitable to use each

* add more comments to log.py

* make IDisplay.notification decorate arg private

* rename notify->print and move to .display.util

* rename .display.print back to .display.notify

because linters complain about print being a redefined builtin
2020-11-06 16:47:07 -08:00
Brad Warren
c102ca66c3 Write a replacement for pipstrap (#8401)
* Add a new, simplified version of pipstrap.

* Use tools/pipstrap.py

* Uncomment code

* Refactor pip_install.py and provide hashes.

* Fix test_sdists.sh.

* Make code work on Python 2.

* Call strip_hashes.py using Python 3.

* Pin the oldest version of httplib2 used in distros

* Strip enum34 dependency.

* Remove pip pinnings from dev_constraints.txt

* Correct pipstrap docstring.

* Don't set working_dir twice.

* Add comments
2020-11-06 11:17:41 +01:00
Brad Warren
75365f1d4e Remove python_version setting from mypy.ini (#8426)
* Remove python_version from mypy.ini.

* Fix magic_typing

* Ignore msvcrt usage.

* make mypy happier

* clean up changes

* Add type for reporter queue

* More mypy fixes

* Fix pyrfc3339 str.

* Remove unused import.

* Make certbot.util mypy work in both Pythons

* Fix typo
2020-11-05 15:28:35 -08:00
Adrien Ferrand
198f5a99bc Merge pull request #8431 from atombrella/ec_dsa_2163
Implements support for ECDSA keys. Fixes #2163.
2020-11-04 23:43:46 +01:00
Mads Jensen
47c1045f6d Implements support for ECDSA keys. Fixes #2163.
Thanks to @pahrohfit and @Tomoyuki-GH for previous efforts to implement
suport for this.

Co-Authored-By: Robert Dailey <rob@wargam.es>
Co-Authored-By: Tomoyuki-GH <55397638+Tomoyuki-GH@users.noreply.github.com>
2020-11-04 15:16:48 +01:00
Brad Warren
e570e8ad32 Generate plugin snap configs as needed (#8411)
While reviewing https://github.com/certbot/certbot/pull/8404, it occurred to me that we're keeping both the generated files and the script used to generate them in `git`. Keeping both around seems unnecessary and is almost asking for the files to get out of sync at some point in the future. I fixed that by removing the files, adding them to `.gitignore`, and updating `build_remote.py` to generate them as needed.

* Remove generated files.

* Add generated files to gitignore.

* Reuse generate_dnsplugins_all.sh in build_remote
2020-10-30 14:12:57 -07:00
Brad Warren
df138d0027 Document that logs aren't always created. (#8410) 2020-10-30 13:15:47 -07:00
Brad Warren
9567352002 Update tools/snap/generate_* comments. (#8412) 2020-10-30 13:08:57 -07:00
Brad Warren
6c7b99f7e0 Remove fedora test farm tests (#8415)
While working on https://github.com/certbot/certbot/issues/8400, I noticed our Fedora AMIs are quite out of date. I considered updating them and what we could do to avoid the AMIs becoming so out-of-date in the future, but I think we don't actually need these tests.

I pulled a new count of Certbot users by OS and we have less than 7,000 Fedora users meaning only ~0.26% of Certbot users run Fedora. (I think Fedora is a popular desktop OS, but not as popular of a server OS which is where Certbot normally runs.)

Also, Certbot is regularly updated on Fedora including Fedora Rawhide or the rolling release version of Fedora which is similar to Debian sid/unstable. Rawhide changes far too frequently for it to make sense for us to run tests there in my opinon, but that also means that many problems such as Certbot's unit tests failing to run because of Fedora changes will be caught there by our Fedora maintainers before we'd even see it. This is how https://github.com/certbot/certbot/issues/7106 became an issue and how I learned [Certbot worked on Python 3.9 before we could run tests on it](https://github.com/certbot/certbot/issues/8134#issuecomment-655106169).

Because of all this, I think we should just simplify things and remove these tests. If a problem arises in the future, we can always add them back.
2020-10-28 15:52:20 -07:00
ohemorange
3673ca77a5 Fix LXD setup in snap README (#8416)
Fixes #8409.

Change the line in the README to allow `sudo /snap/bin/lxd.migrate -yes` to fail (for example, if there's nothing to migrate), but the whole command to succeed.

I tested this on a clean Focal install and confirmed it works.
2020-10-28 15:51:16 -07:00
Brad Warren
bb45c9aa41 Add Ubuntu 20.10 test farm tests (#8414)
Fixes https://github.com/certbot/certbot/issues/8400.

I had to switch the package installed in `apacheconftest` to `libapache2-mod-wsgi-py3` because Ubuntu 20.10 removed the Python 2 version of this module.

I didn't add this AMI to `tests/letstest/auto_targets.yaml` because like Ubuntu 20.04, `certbot-auto` has never worked on the OS.

* Add Ubuntu 20.20 test farm tests

* Try Python 3 WSGI
2020-10-28 15:08:16 -07:00
Brad Warren
4c347f5576 Switch to using python directly (#8413)
Windows installer tests failed last night because they suddenly switched to Python 3.9.

This is happening despite bf07ec20b0/.azure-pipelines/templates/jobs/packaging-jobs.yml (L92-L95) just a few lines earlier than what I modified in the PR here.

I think what's going on is `py -3` is finding and preferring the newer version of Python 3, Python 3.9, which was [just recently added to the image](https://github.com/actions/virtual-environments/issues/1740#issuecomment-717849233).

The [documentation for UsePythonVersion](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/tool/use-python-version?view=azure-devops) says that:

> After running this task with "Add to PATH," the `python` command in subsequent scripts will be for the highest available version of the interpreter matching the version spec and architecture.

So let's just use `python` instead of `py`.
2020-10-28 14:12:32 -07:00
alexzorin
bf07ec20b0 run: dont report new certs when only re-installing (#8392) 2020-10-27 12:48:07 -07:00
ohemorange
fc864543a7 Simplify/document snap creation (#8404)
This PR adds the following documentation improvements to fix https://github.com/certbot/certbot/issues/7958:

- Simplify building external plugins
- Separate out certbot snap instructions from plugin instructions
- Mention that dnsimple is just an example for the plugin instructions
- Mention remote build for other architectures
- Mention snap doc exists elsewhere in developer guide (`contributing.rst`)

* Set up generate_dnsplugins_all.sh for all files and parametrize snapcraft and postrefreshhook files

* Create constraints file in the generate_dnsplugins_all script

* Separate out plugin and certbot snaps and update instructions

* Add remote build instructions

* Add pointers to the README to contributing.rst
2020-10-27 10:22:40 -07:00
Mark Dumay
4fa1df3075 Added links for gehirn and sakuracloud DNS plugins (#8406) 2020-10-26 17:22:00 -07:00
Adrien Ferrand
cfd0a6ff1f Remove usage of buildkit (#8408)
Fixes #8355 

During the troubleshooting of #8355, I came to the conclusion that using buildkit was creating the problem. Without it all docker images are built correctly. Initially buildkit was enabled to avoid a building problem in Azure Pipeline, but I also found in my recent tests that this problem was not there anymore.

You can find more details about the troubleshooting and reasoning in #8355.

As a consequence, I disable the usage of buildkit in this PR which will solve the issue.
2020-10-26 15:20:27 -07:00
Adrien Ferrand
00ed56afd6 Execute basic integration tests against Certbot dockers during CI (#8396)
Fixes #8202

This PR adds an Azure Pipeline job to execute certbot plugins --prepare for each Docker image created during the CI on amd64.

* Prepare basic integration tests for certbot dockers

* Add a displayName for the integration tests task
2020-10-23 11:02:35 -07:00
alexzorin
b6e3a3ad02 register: remove report_new_account, use logger (#8393) 2020-10-22 17:33:45 -07:00
Brad Warren
c250957ab0 Add .envrc. (#8382) 2020-10-22 14:01:30 -07:00
alexzorin
4eb0b560c5 manual: deprecate --manual-public-ip-logging-ok (#8381)
* manual: deprecate --manual-public-ip-logging-ok

* remove unused cli.report_config_interaction code

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2020-10-22 12:12:54 -07:00
Brad Warren
cb916a0682 Deprecate certbot-auto on Debian systems (#8354)
Fixes #8294.

* Deprecate certbot-auto on Debian systems.

* Add changelog entry.

* Remove le_auto_xenial test.

* Update certbot-auto test farm tests.

* Add comments explaining expected behavior.
2020-10-20 16:25:20 -07:00
Brad Warren
88386e8c82 Add external snap docs and clean up dev docs (#8356)
* Add external snap docs and clean up dev docs

* Correctly refer to content identifiers

* Expand plugin interface docs and add line breaks
2020-10-19 15:30:30 -07:00
alexzorin
a64e1f0129 changelog: move entry to the right section (#8378) 2020-10-15 13:23:36 -07:00
osirisinferi
fea176449c Add confirmation before certificate delete (#8349)
* Ask confirmation before deleting cert

* Changelog

* Fix lint and preserve non-interactively deletetion

* Improve English

* Integrate message into yesno() without logger

* Reduce if/else into oneliner

* Expand "certificate(s)" in `get_certnames`

* Address comments

* Update certbot/certbot/_internal/cert_manager.py
2020-10-16 06:18:01 +11:00
Nobuki Fujii
ff03e34c70 Enable dns-inwx link in third-party plugins (#8374) 2020-10-14 12:31:25 -07:00
Nobuki Fujii
6fc832677e Add dns-lightsail to third-party plugins (#8372) 2020-10-13 14:59:51 -07:00
osirisinferi
725870d558 Add query timeout for dns-rfc2136 plugin (#8268)
* Add timeout to DNS query function calls

* Modify tests to account for new timeout variable

* Add change to CHANGELOG

* Add `dns.exception.Timeout` to exception handler

* Move changelog to 1.10.0
2020-10-09 13:13:46 -07:00
Brad Warren
631c88b209 Remove PPA instructions from docs. (#8364)
We're doing what we can to keep the PPA working in the most basic sense, but it is essentially deprecated and new users should not use it.
2020-10-09 12:44:09 -07:00
Brad Warren
6a093bd35a Move status message (#8361) 2020-10-08 16:38:05 -07:00
Brad Warren
afb07cf50d Automate publishing snaps to the stable channel (#8351)
Fixes https://github.com/certbot/certbot/issues/8171.

See the comment at the top of the script to learn how to set things up and run this. Running the script between releases will have no effect on our snaps and it should fail when creating the GitHub release. The latter is described at https://github.com/certbot/certbot/pull/8189#discussion_r466707114.

* Rename create_github_release to finish_release

* Add initial version of snap release automation.

* Handle snapcraft login.

* Catch OSError raised when snapcraft doesn't exist.

* Update documentation.

* Only publish the Certbot snap for now.

* Fix typo.

* Document other exceptions.

* Document assertion

* Add status message before getting revisions.

* Publish all snaps.
2020-10-08 15:18:09 -07:00
alexzorin
aa61e6ad4e certbot.util: suppress Popen CLI output (#8341)
* certbot.util: suppress Popen CLI output

Fixes #8326

* can't use subprocess.DEVNULL in py2
2020-10-08 13:27:36 -07:00
ohemorange
8a3aed0476 add status messages to create_github_release script (#8353)
It took long enough to do all the downloading and uploading that I found myself wishing I could be sure things were happening.
2020-10-07 08:31:37 -07:00
Brad Warren
afc5baad4a Merge pull request #8352 from certbot/candidate-1.9.0
Release 1.9.0
2020-10-06 15:41:21 -07:00
Erica Portnoy
eff761ab1e Bump version to 1.10.0 2020-10-06 12:15:29 -07:00
Erica Portnoy
5f040a8e32 Add contents to certbot/CHANGELOG.md for next version 2020-10-06 12:15:29 -07:00
Erica Portnoy
5173ab6b90 Release 1.9.0 2020-10-06 12:15:27 -07:00
Erica Portnoy
448fd9145a Update changelog for 1.9.0 release 2020-10-06 11:39:49 -07:00
Brad Warren
ac8798e818 Give DNS plugin snaps grade stable. (#8350)
With more and more of our wildcard instructions on https://certbot.eff.org telling people to use these plugins, I think we should get ready to move our DNS plugins to the stable channel. This PR removes grade: devel so the snap store doesn't prevent us from doing that when we want to. See #8128 where we did this to the Certbot snap for more info.

You can see the snap tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=2797&view=results.
2020-10-05 15:55:01 -07:00
Adrien Ferrand
34694251dd Reuse key renewal params (#8343)
* Ensure key params are stored in renewal config when --reuse-key is set.

* Fix mypy definition

* Add unit test

* Clean code.
2020-10-05 20:50:45 +02:00
Brad Warren
cc76906712 Set Certbot snap version from __init__.py (#8344)
Fixes https://github.com/certbot/certbot/issues/8166 following the feedback in https://github.com/certbot/certbot/pull/8337.

I took the command to get Certbot's version from: ef8c481634/snap/snapcraft.yaml (L90)

You can see the snap tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=2785&view=results.
2020-10-05 08:37:01 -07:00
Brad Warren
ef8c481634 Add snap log files to gitignore. (#8336) 2020-10-01 14:44:12 +02:00
Mads Jensen
c12404451d Converted dict comprehensions to use literals. (#8342) 2020-10-01 14:42:37 +02:00
Brad Warren
e378931eda Upgrade httplib2 (#8289)
* Upgrade httplib2.

* Add changelog entry.
2020-09-30 17:15:06 -07:00
Brad Warren
160b209394 Automatically retry test farm tests (#8325)
Fixes #8317.

* move retry to script

* Retry test farm tests.

* Fix retry path.
2020-09-30 17:05:52 -07:00
Brad Warren
cac9d8f75e Deprecate certbot-auto outside of Debian and RHEL (#8324)
Fixes https://github.com/certbot/certbot/issues/8292.

This uses the same approach that worked well for us in https://github.com/certbot/certbot/pull/7926. I'm sure we could delete more code or refactor things here, but I think we should make the most conservative changes we can to certbot-auto until we can just delete the entire thing.

I ran the full test suite on these changes at https://dev.azure.com/certbot/certbot/_build/results?buildId=2773&view=results and manually tested things on OpenSUSE and it worked as expected. certbot-auto refused to create new installations and refused to update old ones while continuing to allow the old version of Certbot to run.

* Deprecate cb-auto outside of Debian and RHEL.

* Don't deprecate Amazon Linux yet.
2020-09-30 17:03:59 -07:00
Adrien Ferrand
7f0fa18c57 Refactor certbot snap wrapper (#8313)
Partial fix for #8280

This PR refactors the bash script wrapper for snap (`/certbot.wrapper`) into certbot python codebase. Here are the keypoints of this refactoring:
* the wrapping is applied when `main` function from `certbot._internal.main` is called if environment variable `CERTBOT_SNAPPED` is `True`, which is set during the snap build
* the initial bash script wrapper  is removed, simplifying `snap/snapcraft.yaml` by removing the `certbot.wrapper` part
* the dependency to `curl` and `jq` binaries are removed
* the failure during requesting the snapd socket is correctly handled, and displays an informative message in order to correct the situation, as required by #8280

One side note about the modifications done to `app.certbot.command` in `snapcraft.yaml`. Normally calling `bin/certbot` should be sufficient and it is effectively under a normal situation (`core` snap up-to-date). However in the same situation than when the problem occurs in #8280, using `bin/certbot` makes the snap raise an exception about `certbot.main` module that cannot be found.

It seems that when `core` snap is not up-to-date (in Debian for instance with default `snapd` installation), the shebang `/usr/bin/env python3` in the `bin/certbot` wrapper is wrongly resolved to the host Python, instead of the snap Python. It is working as expected if `core` snap is up-to-date. One way to fix that is to keep a bash script wrapper, because in this case, it is the `PATH` value that matters to resolve the Python interpreter, and `PATH` is correctly set up to resolve it from the snap first.

However to keep the simplification provided by the wrapper removal, I prefered to use `bin/python3 $SNAP/bin/certbot` as `command` to explicitly target the correct Python interpreter. Again normally it is not needed because everything is working correctly with a `core` snap up-to-date, but since the root purpose of all of this is to target bad situations, well, it is better to have a snap that is effectively able to start to display the informative message...

* Refactor the bash wrapper for snap execution as Python code into certbot

* Remove wrapper, finalize the python logic

* Organize code

* Improve error handling

* Update command

* Setup basic certbot logging before running the snap prepare logic

* Improve instructions

* Use logging facility

* Handle properly an exception in snap_config

* Use the python script call approach

* Update instructions to keep sync with https://github.com/certbot/website/pull/650
2020-09-30 13:24:56 -07:00
ohemorange
fca7ec896a Improve error message for prepare-plug-plugin hook when certbot isn't installed (#8338)
Provides a partial fix for #8182 by improving the error message.
2020-09-30 12:43:24 -07:00
Brad Warren
e066766cc9 Revert "Disable build isolation during snap dns plugins build (#8319)" (#8323)
This reverts commit feca125437.

Since this change landed, ARM builds for many of the DNS plugins have failed every night. See https://dev.azure.com/certbot/certbot/_build?definitionId=5 or our public Mattermost channel.

I quickly tried to fix this myself and wasn't trivially able to do so. I tried setting `SNAPCRAFT_PYTHON_VENV_ARGS: --system-site-packages` and adding `python3-wheel` as a build dependency, but it didn't work for some reason. The `python3-wheel` package didn't seem to be installed.

I still suspect something like this is the approach we should take, however, I want to fix the failing tests now so things are no longer broken in `master` and those of us on the Certbot team at EFF stop getting spammed with 54 (!!) emails about failed builds from launchpad every night.

Unfortunately, while I was working on this the queue for ARM machines on Launchpad jumped up to an estimated ~20 hour wait, but I confirmed that this fixes the problem by building on an ARM AMI using the instructions at https://github.com/certbot/certbot/blob/master/tools/snap/README.md#use-testing-and-development. If whoever reviews this would like an ARM machine to test on themselves, please let me know.
2020-09-28 14:27:29 -07:00
ohemorange
be6c890874 Retry Snap upload in pipeline (#8300)
* add set -e to all bash instances in deploy-stage.yml

* retry uploading snap if we fail

* Add the rest of the set -e calls for bash in azure while we're here

* use retry based on travis_retry

* add set -e to the script: sections that run on macOS/Linux

* actually don't fail on result

* reset result before running command because bash short circuits or conditionals

* remove inapplicable comment
2020-09-25 15:31:13 -07:00
Adrien Ferrand
feca125437 Disable build isolation during snap dns plugins build (#8319)
Partial fix for #8256

This PR disable the build isolation for snap dns plugins similarly to what is done for the certbot snap.
2020-09-25 11:24:29 -07:00
Brad Warren
1be005289a Print more output from snapcraft remote-build (#8321)
* Print more output from snapcraft remote-build.

* Include the build target in the output.
2020-09-25 18:58:04 +02:00
Adrien Ferrand
79297ef5cb Invoke pipstrap in tox and during the CI (#8316)
Partial fix for #8256

This PR makes tox calls pipstrap before any commands is executed, and Azure Pipelines calls pipstrap when appropriate (when an actual call to pip is done). 

* Invoke pipstrap in tox and during the CI

* Set default value for PYTHON_VERSION and always set python interpreter

* Set Python for snaps_build also

* Fix the build for Windows installer

* Add a warning comment for pinned versions in pipstrap

* Rebuild letsencrypt-auto

* Same version than the installer build

* Let's update to latest pip for installer tests
2020-09-24 17:12:12 -07:00
alexzorin
5ec29ca60b suppress tracebacks in ErrorHandler recovery (#8310)
The ErrorHandler context manager could produce very verbose CLI output
when handling long exception chains (PIP 3134 enhanced reporting).

Rather than logging every exception with its traceback to the CLI, this
commit changes ErrorHandler so that only the final exception in the
chain, without traceback, is logged to the CLI.

This is consistent with a previous change made in the global except
hook (#8000).
2020-09-24 14:22:38 -07:00
Cameron Steel
9a72db5b9b Convert http links to https (#8287)
* Convert http links to https

* Fix remaining links
2020-09-23 19:36:55 +02:00
alexzorin
14cbf67d65 tests: remove Ubuntu 19.10 (#8312)
EOL since July 2020.
2020-09-23 09:42:37 -07:00
alexzorin
b20aaff661 remove unused ssllabs-related code (#8307) 2020-09-21 12:42:00 -07:00
Mads Jensen
a66f4e1150 Added an .editorconfig file. (#8297)
https://editorconfig.org/ is meant as a guideline for editors how to format
files.
2020-09-19 11:39:13 +02:00
Mads Jensen
501df0dc4e Use in dict rather than "in dict.keys()". Fix linting warnings about "not in". (#8298)
* Fixed a few linting warnings for if not x in y.

These should have been caught by pylint, but weren't.

* Replaced "x in y.keys()" with "x in y".

It's much faster, and more Pythonic.
2020-09-19 11:35:49 +02:00
Mads Jensen
b551b6ee73 Removed unnecessary unittest.TestCase.setUp/tearDown calls. (#8264) 2020-09-19 10:38:40 +02:00
alexzorin
71d9dfa86e nginx: reduced CLI logging when reloading nginx (#8237)
* nginx: reduced CLI logging when reloading nginx

Hides the output of `nginx -s reload` from the CLI, moving it to
debug-level logging.

Additionally, fixes an issue where Certbot did not properly capture the
output of the nginx reload and restart commands.

Fixes #8231

* remove leftover debugging

* reorder CHANGELOG

* don't use bare asserts
2020-09-16 12:22:15 -07:00
alexzorin
6628bc0e9b certbot-compat: remove dupe random25863 nginx name (#8286)
random25863.example.org appears in multiple port 80 virtualhosts in the
nginx testdata tarball and also is in the nginx-roundtrip-testdata.
Certbot doesn't handle these properly, which results in random test
failures.

This commit ensures that random25863.example.org only appears in a
single virtualhost and should ensure that the tests pass consistently.
2020-09-16 10:00:38 -07:00
alexzorin
f43fa12fc0 cli: add --preconfigured-renewal packaging flag (#8274)
* cli: add --preconfigured-renewal packaging flag

* fix rst formatting

* snap: make the flag postfixed
2020-09-15 15:45:36 -07:00
Brad Warren
2b425110dc Delete conflicting server_names for random28524. (#8278) 2020-09-11 12:16:55 -07:00
Adrien Ferrand
55d411f1eb Remove deprecated python setup.py test call and update packager guide (#8262)
Fixes #7585

This PR removes the specific configuration to configure the test runner included in `setuptools` to use pytest, the deprecated parameters related to setuptools testing in `setup.py`, and update the packaging guide to use `python -m pytest` instead of `python setup.py test`.

The farm test `test_sdist.sh` is also updated to use directly pytest. This test is designed to reproduce the steps used by OS integrators when they package `certbot`, and ensure that we are not breaking something that will impact their work. We discussed with integrators from RHEL/CentOS and Debian, and they are fine with us testing sdist directly with pytest.

One execution of the `test_sdist.sh` farm test with the modifications made by this PR can be seen here: https://dev.azure.com/certbot/certbot/_build/results?buildId=2606&view=results

* Remove setuptools deprecated features about testing

* Updating packaging guide

* Add changelog entry
2020-09-10 15:57:59 -07:00
Mads Jensen
7ddd327f63 Removed unneeded chmod-call in a test. (#8244)
* Removed unneeded chmod-call in a test.

* Trigger CI.

Co-authored-by: Adrien Ferrand <ferrand.ad@gmail.com>
2020-09-11 00:11:51 +02:00
Brad Warren
3a615176c5 fix Certbot acme dep (#8279) 2020-09-10 09:37:10 +02:00
alexzorin
e79af1b1de changelog: move #8263 to the right section (#8271) 2020-09-09 16:16:53 -07:00
Brad Warren
c8828dab30 Move compatibility tests off of certbot-auto and Python 2 (#8248)
Fixes https://github.com/certbot/certbot/issues/8162.

I had to update the base of the Dockerfile to get a new enough version of Python 3. I also simplified things a lot and removed a lot of the comments that were essentially just describing how Dockerfiles work.

The most complicated changes here are in `testdata`. You can find a diff of the changes to `nginx.tar.gz` at https://gist.github.com/c7727db0cecf3f15f02439f085c73848.

The first problem was that there were some complaints from the new Apache/nginx/OpenSSL version about the 1024 bit RSA key so I updated `empty_cert.pem` both inside and outside of the tarball as well as the corresponding private key in the tarball to use a 2048 bit key.

The 2nd problem is trickier to understand. If you look at the output from nginx after loading the config from `lots/` you'll see it complaining about conflicting `server_name` directives for the directives I deleted. See https://dev.azure.com/certbot/certbot/_build/results?buildId=2578&view=logs&j=250aa146-b243-5f8f-bf86-17a529c9fb7e&t=9baa2014-9673-5e78-8f4f-7a463caf2bfa&l=1516.

After switching the tests to Python 3, tests on that domain started failing. What I believe to be happening is we were just lucky these tests were passing to begin with. In both the Apache and Nginx plugin, if there are conflicting virtual hosts like this, we just arbitrarily pick one. The relevant code here for nginx is 575092d603/certbot-nginx/certbot_nginx/_internal/configurator.py (L455)

I played around with a debugger and confirmed that before I removed the conflicting server names, there were two exact matches for the domain we were searching for here.

I think all that's going on is with the switch to Python 3, the vhost we happen to choose changes and "breaks" the test. I suspect this to be due to something like getting values out of a dict somewhere where the order of items in a dict while iterating over it is different between Python 2 and 3. I didn't track where this difference happens down, but I personally don't think it's a good use of time since I think the real problem here is that the nginx config being tested was invalid with conflicting `server` blocks.

I removed all references to the `server_name` causing conflicts in that nginx configuration because both server blocks had other domains that are being tested, but I could add either back if you prefer. You can see the `nginx_compat` test passing with these changes at https://dev.azure.com/certbot/certbot/_build/results?buildId=2587&view=logs&j=250aa146-b243-5f8f-bf86-17a529c9fb7e.

* update Dockerfile

* Fix apache_compat on py3.

* Update empty_cert.pem.

The command used here was `openssl req -key
certbot/certbot/tests/testdata/rsa2048_key.pem -new -subj '/CN=example.com'
-x509 >
certbot-compatibility-test/certbot_compatibility_test/testdata/empty_cert.pem`.

* update nginx.tar.gz

* Remove conflicting server_names
2020-09-09 15:16:52 -07:00
Xebax
f85b738e2f Fix filename in example (#8275) 2020-09-09 18:01:04 +02:00
alexzorin
95a6b61cdc nginx: fix server_name case-sensitivity in parser (#8263)
This commit fixes an issue with the nginx parser where it would perform
case-sensitive matching against server_name.

This would cause the authenticator and installer to ignore existing
virtualhosts containing uppercase characters, resulting in duplicate
virtualhosts and broken configurations.

"Exact" and "wildcard" matching is now case-insensitive. Regex-based
matching will continue to respect the case mode of the pattern.

Fixes #6776.
2020-09-08 14:14:54 -07:00
Brad Warren
21b320ef42 Add TODO to certbot.wrapper. (#8270)
I'm adding this comment as part of the resolution of #8251. I think rewriting the script in Python is something we really should only worry about if we're working on the script in the future. Because of this, I personally prefer a code comment rather than an issue here.
2020-09-08 12:54:00 -07:00
Brad Warren
8c81a1aaf8 Merge pull request #8269 from certbot/candidate-1.8.0
Release 1.8.0
2020-09-08 11:45:54 -07:00
Brad Warren
ec147740ee Bump version to 1.9.0 2020-09-08 09:59:33 -07:00
Brad Warren
b7b0ec321e Add contents to certbot/CHANGELOG.md for next version 2020-09-08 09:59:33 -07:00
Brad Warren
7fe7a965f5 Release 1.8.0 2020-09-08 09:59:31 -07:00
Brad Warren
9f243c768f Update changelog for 1.8.0 release 2020-09-08 09:41:49 -07:00
osirisinferi
b841f0f307 Change ACME spec link to RFC 8555 (#8266) 2020-09-06 14:14:33 +02:00
osirisinferi
8e736479f7 Lower heading level of "Changing a certs domain" (#8267) 2020-09-06 14:03:15 +02:00
alexzorin
2ceabadb81 snap: use snap REST API in certbot.wrapper (#8260)
In order to avoid potentially breaking changes in the snap CLI on the
host, this commit changes certbot.wrapper to use the snap REST API (via
curl and jq) to list connected Certbot plugins.
2020-09-04 23:55:21 +02:00
alexzorin
a2951b4db1 snap: Fix "stack smashing" error in wrapper (#8249)
* snap: Fix "stack smashing" error in wrapper

certbot.wrapper had implicit dependencies on sed, awk and coreutils,
which were being accidentally provided through the host system. Because
certbot.wrapper modifies LD_LIBRARY_PATH, this was causing some systems
to load an incompatible combination of shared libraries, resulting sed
crashing.

This commit reduces the dependencies of this script to just gawk, and
explicitly stages it as part of the Certbot snap.

It additionally moves invocations of all host system programs to a
moment prior to the modification of LD_LIBRARY_PATH, and the invocation
of snapped programs to after the modification.

Fixes #8245

* snap: Don't modify LD_LIBRARY_PATH

* leftover tracing

* snap: revert curl/jq in wrapper, use gawk for now
2020-09-04 20:51:01 +02:00
alexzorin
98615564ed log: Don't print backtrace on ^c/KeyboardInterrupt (#8259) 2020-09-04 12:57:46 +02:00
Adrien Ferrand
3ce87d1fcb Test PIP_NO_BUILD_ISOLATION (#8255)
Fixes #8252

With @bmw we digged quite a lot on why the failure happens on ARM snap, and here we what we understood:
* the failure occurs since the version 50 of setuptools is available
* normally, we should not be impacted because the setuptools version used in the snap build should be the one installed by the `core20` base snap, because the build occurs in a `venv` created with `--system-site-packages`
* BUT associated with the build isolation provided by recent versions of pip (to implement PEP 517), a bad interaction happens: following the definition of the build system provided by `cryptography`, pip installs the most recent version of setuptools on a separate path for the build (because `cryptography` just asks for a minimal version of `setuptools`), then features of this version conflict with the old version of `setuptools` initially present
* the exact interaction is described here: https://github.com/pypa/pip/issues/6264#issuecomment-685230919. Basically the new version of `setuptools` triggers some hacks, that are then applied at runtime on the old version of `setuptools` that is also still available in `sys.path` at this point, and breaks the build.

To fix that, one can disable the isolation build on cryptography, by passing `PIP_NO_ISOLATION_BUILD=no` to pip. It is the purpose of this PR.

This will have the consequence to not be PEP 517 compliant: if needed the `cryptography` library will be built using the `setuptools` available in the system. In general I think it makes sense for the snap build purpose, since we control precisely the build environment, and makes consistent build that will not be broken by a new version of a build system if library maintainers did not provide a strict version of it in their build requirements. However we need now to take care about having a compatible build system for all libraries that may have specific requirements in their build system using the PEP 517 definition in `pyproject.toml`.

I think as of now that it is a safe move if we keep using the most recent version of `setuptools` available in Ubuntu 20.04, and it is the case here for snap builds. It may however be problematic if some libraries require another build system than `setuptools` and do not provide a fallback to a `setuptools` build. For the record, `dns-lexicon`, that I maintain, uses `poetry` and so a PEP 517 compliant definition of a build system, but provides also this fallback (https://github.com/AnalogJ/lexicon/blob/master/setup.py).

Full test suite compiling the snaps for the 3 architectures using this PR is available here: https://dev.azure.com/certbot/certbot/_build/results?buildId=2596&view=results
2020-09-02 11:45:38 -07:00
Brad Warren
d62d853ea4 Clean up --register-unsafely-without-email docs (#8223)
* Clean up --register-unsafely text.

* update unsafe_suggestion

* remove unused import

* Expand scary message.
2020-08-27 13:25:57 -07:00
Daniel Drexler
70731dd75b Move changes to the right section of the changelog (#8236)
Fixing a mistake in pull request #8212 where I recorded my changes in an already released version 😳.

- Moving new changes out of a previous changelog and into the next
  releases' changelog
2020-08-27 09:45:10 -07:00
Daniel Drexler
ae7b4a1755 Support Register Unsafely in Update (#8212)
* Allow user to remove email using update command

Fixes #3162. Slight change to control flow to replace current email
addresses with an empty list. Also add appropriate result message when
an email is removed.

* Update ACME to allow update to remove fields

- New field type "UnFalseyField" that treats all non-None fields as
  non-empty
- Contact changed to new field type to allow sending of empty contact
  field
- Certbot update adjusted to use tuple instead of None when empty
- Test updated to check more logic
- Unrelated type hint added to keep pycharm gods happy

* Moved some mocks into decorators

* Restore default to `contact` but do not serialize

- Add `to_partial_json` and `fields_to_partial_json` to Registration
- Store private variable noting if the value of the `contact` field was
  provided by the user.
- Change message when updating without email to reflect removal of
  all contact info.
- Add note in changelog that `update_account` with the
  `--register-unsafely-without-email` flag will remove contact
  from an account.

* Reverse logic for field handling on serialization

Now forcably add contact when serilizing, but go back to base `jose`
field type.

* Responding to Review

- change out of date name
- update several comments
- update `from_data` function of `Registration`
- Update test to remove superfluous mock

* Responding to review

- Change comments to make from_data more clear
- Remove code worried about None (omitempty has got my back)
- Update test to be more reliable
- Add typing import with comment to avoid pylint bug
2020-08-26 15:22:51 -07:00
Brad Warren
f66a592e37 Try switching to the buster ARM image. (#8234) 2020-08-26 14:04:37 -07:00
Brad Warren
e8518bf206 Fix finding Augeas in the ARM snaps (#8230)
* Find Augeas on all architectures.

* Add changelog entry.

* add comment
2020-08-26 14:03:15 -07:00
Emily Bowman
2a047eb526 Update docs link in certbot unsupported error (#8168)
* Update docs link in certbot unsupported error

Co-authored-by: Adrien Ferrand <ferrand.ad@gmail.com>
2020-08-20 11:33:56 -07:00
Brad Warren
bc137103a3 Don't recommend using certbot-auto. (#8222)
Fixes https://github.com/certbot/certbot/issues/8165.

I moved `prerequisites` up to the "Running a local copy of the client" `contributing.html#prerequisites` still links to information about installing Cerbot's dependencies.

I left all certbot-auto documentation that wasn't explicitly encouraging its use. I think we can rip that out once the script is deprecated.
2020-08-20 11:13:35 -07:00
Brad Warren
085967ad29 Fix test farm tests on macOS and update macOS images (#8219)
* Run one of the test farm tests on macOS.

* it break with 38?

* Remove LOGDIR global

* add comment

* include macOS in name

* Update macOS image.
2020-08-19 18:26:28 -07:00
Brad Warren
4e9d3afcc4 Docker build improvements (#8218)
Fixes https://github.com/certbot/certbot/issues/8208.
Fixes https://github.com/certbot/certbot/issues/8198.

In addition to those two linked issues, this PR:

* Splits both the build and deploy steps based on architecture for performance. The Docker builds should no longer be the bottleneck in any stage of the pipeline.
* Skips building Docker images for ARM on `test-` branches like [we do for snaps](e8a232297d/.azure-pipelines/templates/jobs/packaging-jobs.yml (L67-L71)). I initially didn't want to do this, but the ARM builds take ~18 minutes which is significantly longer than any other job currently running on our `test-` branches.

You can see tests running on my fork at:

* [Release pipeline](https://dev.azure.com/bmw0523/bmw/_build/results?buildId=387&view=results)
* [Test pipeline](https://dev.azure.com/bmw0523/bmw/_build/results?buildId=388&view=results)
* [Nightly pipeline](https://dev.azure.com/bmw0523/bmw/_build/results?buildId=390&view=results)

* update script intro

* update readme

* ParseRequestedArch

* build all arch in Azure

* Build docker images during testing/packaging.

* require global variable?

* Error if TAG_BASE is empty.

* prepare build job

* change variable syntax

* Update deploy stage.

* remove old dockerTag param

* add displayName

* fix docker images command

* split docker_build by arch

* Allow deploying a subset of architectures.

* deploy in parallel

* Skip ARM builds on test- branches.

* fix spacing
2020-08-18 10:48:01 -07:00
ohemorange
acb6d34c5f Update test farm tests to stop using certbot-auto (#8207)
* Create bootstrap script

* Delete a whole bunch of the bootstrap script

* modify test_tests to use new script

* put python version checking in back in

* add x

* call the venv creation from inside the bootstrap

* add targets back

* modify test_apache2 to use new format

* shouldn't need virtualenv on rhel

* readd targets

* Update test_sdists to use new script

* move setting up venv back out of script so it's not run with sudo

* take venv3.py call out of bootstrap in all scripts

* add additional python3-devel pkg name

* fix test_sdists

* enable additional rhel7 repos

* clean up code and comments

* Update tests and instructions to use auto_targets.yaml with test_leauto_upgrades.sh and test_letsencrypt_auto_certonly_standalone.sh

* only install python3-devel.x86_64 for rhel7

* Upgrade python version for debian in test_apache2.sh

* don't run test_tests or test_sdists on debian 9 or ubuntu 16.04

* Add 20.04 and 20.04 arm images to targets.yaml

* use pyenv to upgrade to python3.5

* remove arm64 instance because it's having auth trouble

* correct pyenv usage on ubuntu

* add arm64 target to targets.yaml

* replace debian 9 arm64 with ubuntu 20

* don't try to upgrade a perfectly good python version

* let's just add ubuntu20 to apache2_targets while we're here

* uncomment test_apache2

* move adding python3-devel.x86_64 to bootstrap_os_packages to avoid potential race condition

* no need to specify the arch once extra rhel7 repos enabled

* explicitly specify python3

* don't fail if we can't enable rhel7 extras

* capture python36-devel as well
2020-08-18 10:07:27 -07:00
Brad Warren
63ec74276c Clean up our Docker scripts (#8214)
* rewrite build step

* rewrite deploy script

* fix exit status

* clean up comments

* fix typo

* correct comment
2020-08-18 10:51:30 +02:00
Brad Warren
e8a232297d Pin non-cb-auto dependencies in our plugin snaps (#8217)
This PR fixes our [Azure failures](https://dev.azure.com/certbot/certbot/_build/results?buildId=2492&view=results) by pinning our Python dependencies that are not included in certbot-auto.

This is done using the same approach as our [snap README](575092d603/tools/snap (build-the-snaps)) and [Docker images](575092d603/tools/docker/core/Dockerfile (L24-L25)) with some minor details changed to hopefully make the Python code more readable.

You can see tests passing with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=2495&view=results.
2020-08-17 11:54:29 -07:00
Brad Warren
575092d603 Drop Python 3.5 support (#8206)
* delete classifiers

* update python_requires

* Update py35 Azure jobs

* Revert "Add warnings about Python 3.5 deprecation in Certbot (#8154)"

This reverts commit 270b5535e2.

* Update other Python 3.5 references.

* update changelog

* bump MIN_PYTHON_3_VERSION
2020-08-16 13:19:08 -07:00
Brad Warren
2d62dec7ec Fix certbot.compat.os docs (#8209)
* Don't document stdlib os.

* Move isort out of docstring.

* fix os import

* fix comment length
2020-08-13 17:24:31 -07:00
Brad Warren
f93b90f87a You don't need to set --server (#8200) 2020-08-12 13:27:53 -07:00
Brad Warren
f40e5bdefe Automate Docker builds in Azure (#8193)
Fixes https://github.com/certbot/certbot/issues/8022, https://github.com/certbot-docker/certbot-docker/issues/25, and https://github.com/certbot-docker/certbot-docker/issues/20.

This PR builds on https://github.com/certbot/certbot/pull/8192 to set up similar builds in Azure to what we currently have at release time as well as nightly builds allowing us to catch problems in these images before a release. It also fully automates our Docker deployments removing a manual step from our release process. We'll need to update our release instructions once this PR lands.

If you're not familiar with our `certbot-docker` setup, you can read about how these scripts customized the build process on Docker Hub at https://docs.docker.com/docker-hub/builds/advanced/.

You can see the process working properly at:

* Nightly build on my fork: https://dev.azure.com/bmw0523/bmw/_build/results?buildId=345&view=logs&j=70ac378a-cb1f-50d1-b328-169807afbcfa
* Release build on my fork: https://dev.azure.com/bmw0523/bmw/_build/results?buildId=346&view=logs&j=70ac378a-cb1f-50d1-b328-169807afbcfa
* Nightly build on Certbot's Azure setup: https://dev.azure.com/certbot/certbot/_build/results?buildId=2426&view=logs&j=70ac378a-cb1f-50d1-b328-169807afbcfa

The builds on my fork pushed to https://hub.docker.com/u/certbottest. The credentials for this account are in our shared vault in 1password if you want to play around with this.

While the scripts will (almost?) always be run in CI, I tested the scripts successfully on macOS, Ubuntu 18.04, and Ubuntu 20.04, however, **the scripts do not seem to work when using the Docker snap, at least on Ubuntu 20.04.** It does work with the `docker.io` packages from `apt`. I was able to make things work by no longer setting `DOCKER_BUILDKIT`, but as I described in the code comments, this breaks things on Azure.

When writing this PR, I tried to make the minimal modifications to our current set up to get the behavior we want. I'm planning on working on splitting the Docker builds into different Azure jobs so it doesn't increase the overall build time, but this isn't trivial so I figured it would be best done in a separate PR.

* Remove license.

* update build scripts

* write deploy code

* Remove unused READMEs.

* rewrite readme

* Make testing on a fork easier.

* Set up Azure automation.

* fix typo

* Make output more verbose.

* clean up cleanup...everybody everywhere

* separate build and deploy

* Document docker-hub credentials

* Use Docker BuildKit when building.

* Remove unneeded .gitignore files.

* Fix tools/docker/README.md grammar

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

* Clarify <TAG> in README.

* no docker snap

* rename docker job

Co-authored-by: Erica Portnoy <ebportnoy@gmail.com>
2020-08-11 13:09:38 -07:00
Brad Warren
9bbcc0046c fix --archs default (#8195) 2020-08-11 12:52:24 -07:00
Brad Warren
b3dd2c09ba Remove release branch notification cruft. (#8196) 2020-08-11 12:50:54 -07:00
Brad Warren
8574313841 Remove final jessie references outside of cb-auto. (#8194) 2020-08-07 13:35:21 -07:00
kden
a677534462 Delete or update references to Debian 8 Jessie (#8065)
* Delete or update references to Debian 8 Jessie

* Don't delete oldest constraints from Jessie, but document in comments.

* Update tools/oldest_constraints.txt

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

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
Co-authored-by: ohemorange <ebportnoy@gmail.com>
2020-08-07 12:21:52 -07:00
ohemorange
22730dc0ac Merge pull request #8192 from certbot/docker-base
Add certbot-docker files to this repository preserving history
2020-08-06 16:46:17 -07:00
ohemorange
086e6c46b6 Improve github release creation process (#8189)
* Improve github release creation process

* Comment file

* Update tools/create_github_release.py

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

* run chmod +x on tools/create_github_release.py

* Add description of create github release method

* remove references to unnecessary azure credential

* remove unnecessary import

* Add reminders to update other file to definitions in .azure-pipelines

* Raise an error if we fail to fetch the artifact from azure

* Create github release as a draft, upload artifacts, then un-draft, for hooks to be run at the right point

* get the version number from the release

* add new packages to dev3_extras so they're installed by tools/venv3.py

* remove unnecessary import

* fun fact: tempdirs behave differently when used as a context manager

* Move comment to construct.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-08-06 16:32:57 -07:00
osirisinferi
bc0ed3cb01 [Docs] Remove obsolete Gentoo installation instructions and add packages. (#8184)
It seems my old instruction isn't required any longer for Gentoo. To be honest, I don't have a clue since when, but my own Gentoo server isn't even using the workaround mentioned currently in the documentation at the moment. So it seems the Apache plugin works just fine without this workaround 🤦 

Also, the Gentoo repository obviously also includes the nginx since a long time. I guess my original text is ancient.. It also includes *one* of the many DNS plugins, with a different maintainer than the other "main" packages. It currently only has version 0.39.0, so I don't have a clue if it's being maintained officially.

* Remove obsolete Gentoo instructions and add packages.

* Capitalize note

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-08-06 14:13:53 -07:00
Felix Yan
220cc07239 Correct a typo in acme/tests/client_test.py (#8186) 2020-08-05 11:44:23 -07:00
Brad Warren
271be07267 Merge /Users/bmw/Development/github.com/certbot-docker/certbot-docker into master 2020-08-04 15:14:06 -07:00
ohemorange
48a0cc0c42 Merge pull request #8188 from certbot/candidate-1.7.0
Release 1.7.0
2020-08-04 13:09:34 -07:00
Erica Portnoy
5415fc201c Release version v1.7.0 2020-08-04 12:33:20 -07:00
Erica Portnoy
b08fdc7dfb Bump version to 1.8.0 2020-08-04 11:33:04 -07:00
Erica Portnoy
6eb5954f0e Add contents to certbot/CHANGELOG.md for next version 2020-08-04 11:33:04 -07:00
Erica Portnoy
6ec83d52b5 Release 1.7.0 2020-08-04 11:33:03 -07:00
Erica Portnoy
403ded5c58 Update changelog for 1.7.0 release 2020-08-04 11:20:15 -07:00
Brad Warren
4d3f6c23be Document how to revoke snapcraft credentials. (#8187) 2020-08-03 11:42:42 -07:00
Brad Warren
6d73b21dcf Automatically publish snaps to the beta channel (#8183)
* Expand documentation of snapcraft.cfg.

* Push to the beta channel on releases.

* fix template expansion

* update comment
2020-07-31 13:17:07 -07:00
Brad Warren
072c070c0c Do not run tests on pushes to *.x for performance. (#8185) 2020-07-31 12:47:47 -07:00
Brad Warren
df1ca726f9 Remove text about snap beta status. (#8178)
Part of #8140.
2020-07-29 13:18:45 -07:00
ohemorange
086c8b1b3e Mention the availability of DNS plugin snaps in our docs under certbot/docs. (#8176)
Part of #8142.

* Mention that DNS plugins are available as snaps

* Mention snaps in guide to writing third-party plugins
2020-07-27 13:21:31 -07:00
alexzorin
09ab4aea01 nginx: add --nginx-sleep-seconds (#8163)
* nginx: add --nginx-sleep-seconds

As described in #7422, reloading nginx is an asynchronous process and
Certbot does not know when it is complete. In an environment where this
reload takes a long time, the nginx plugin suffers from an issue where
it responds to and fails the ACME challenge before the nginx server is
ready to serve it.

Following the discussion in a previous PR #7740, this commit introduces
a new flag, --nginx-sleep-seconds, which may be used to increase the
duration that Certbot will wait for nginx to reload, from its previously
hard-coded value of 1s.

Fixes #7422

* update CHANGELOG

* nginx: update docstring for nginx_restart
2020-07-27 12:52:12 -07:00
Adrien Ferrand
a6f2061ff7 Improve log dump in snaps remote builds when an unexpected behavior is detected. (#8173)
Fixes #8169

This PR improves snaps remote builds script by dumping the output of `snapcraft remote-build` when unexpected behavior is detected:
* when all builds for a project finish with a zero status code, and none of them are marked as failed, we expect to have all the associated snap files available locally.
* when some builds are marked as failed, we expect to have a build output for each of them available locally.

In these two situations, if the expectation are not matched, then the script will display the output of `snapcraft remote-build` itself. I added also a control error to handle nicely the absence of an expected build output on the local machine.

* Improve log dump in snaps remote builds when an unexpected behavior is detected

* Use the manager

* Update tools/snap/build_remote.py

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-07-27 12:01:51 -07:00
Adrien Ferrand
02c1339753 Document that snaps are available for 3 architectures (#8174)
Part of #8051
2020-07-27 09:56:55 -07:00
Brad Warren
a1cd909247 remove old snapcraft files (#8167) 2020-07-23 02:29:32 +02:00
ohemorange
9ee4831f78 Make externally snapped plugin updates more stable (#8145)
Fixes #7863.

Connect command is `sudo snap connect certbot-dns-dnsimple:certbot-metadata certbot:certbot-metadata`
Logs are `cat /var/snap/certbot-dns-dnsimple/current/debuglog`
Echos in hook are only printed to terminal when it exits 0; otherwise, check logs in `debuglog` mentioned above.

Manual tests include all iterations of connected, unconnected, installed for the first, second time, etc, with passing and failing version checks.

* Make dnsimple not update if certbot is too old

* create an interface to read cb version

* add missing newline

* fix syntax

* trying to figure out the consumer syntax

* trying to figure out the consumer syntax, again

* only check post first install

* valid setting name

* test for first install differently

* snapctl doesn't error if it fails I guess

* time to do some print debugging

* continue playing with syntax

* once again, fooled by bash int vs string comparisons!

* debugging

* if we use post and pre together we can do this

* is this how content interface syntax works

* it's a directory?

* more debug

* what's that error message again?

* try other syntax

* if it's not documented just guess at syntax

* actually, I think this is the syntax

* oops didn't set for new hook

* test passing information along connection

* interface attributes can only be set during the execution of prepare hooks

* just do it with main connection

* undo last few test changes

* Add some printing to make sure we understand what's going on

* create empty directory to bind to

* put mkdir in the correct part

* let's inspect the environment

* it can't run bash directly.

* perhaps only directories can be shared via the contente interface

* update name of folder

* echo to debug log to understand what's going on exactly. we have file access though!

* update grep for new file

* more printing

* echo to the debug log

* ok NOW all print statements are going to the log

* why does echo need two >s

* remove unnecessary extra check, just check if the init file is available

* check if certbot version will be available post-refresh after all

* pre-refresh hook is not necessary to get certbot version

* update mkdir so we don't have to clean each time

* try comparing version numbers in python

* it's python3

* we need different prints for if we succeed or if we fail.

* improve bash syntax

* remove some debugging code

* Remove debug script

* remove spaces for clarity

* consolidate parts and remove more test code

* s/certbot-version/certbot-metadata/g

* use sys.exit instead of exit

* find and save certbot version on the certbot side

* change presence test to new file

* switch to using packaging.version.parse instead of LooseVersion

* switch to requiring certbot version >= plugin version

* add plugin snap changes to generate script

* Add comment to generation file saying not to edit generated files manually

* Create post-refresh hook for all plugins with script

* generate files using new script

* update snapcraft.yaml files for plugins

* bin/sh comes first

* Add packaging to install_requires

* Check that refresh is allowed in integration test

* switch plug and slot names in integration test

* Update tools/generate_dnsplugins_postrefreshhook.sh

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

* small bash fixes

* Update snap readme with new instructions

* Run tools/generate_dnsplugins_postrefreshhook.sh

* Update tools/snap/generate_dnsplugins_postrefreshhook.sh

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

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-07-22 17:07:07 -07:00
Adrien Ferrand
14dfbdbea5 Build snaps using the remote-build feature (#8153)
Snapcraft has a feature name `remote-build`. It allows to compile snaps using the Canonical dedicated build architecture for several architectures. Compared to the QEMU-enabled Docker approach used currently, the remote build has several advantages:
* the builds are done on the native architecture, making them basically faster than what can be achieved on QEMU
* it avoids to depend on `adferrand/snapcraft` (which could be otherwise be fixed with the merge of https://github.com/snapcore/snapcraft/pull/3144, but this will not happen in the short term)
* when everything is good, all snaps build can be run in parallel and then can be orchestrated by one single Azure Pipeline job, since the heavy tasks are done remotely.

This PR makes the necessary ajustements to use the remote build feature instead of the QEMU-enabled docker approach.

One complex task was to be able to compile the `certbot` snap on `arm64` and `armhf`. Indeed on these architectures the pre-compiled wheel for `cffi` is not available. So it needs to be compiled during the snap build. Sadly, the current version of the python plugin in snapcraft is limited by the fact that `wheels` is not installed in the virtual environment set up to build the python packages, and there is no easy way to change that except by overridding the whole build process.

In the long term, I think I will open a PR on `snapcraft` Git repository to provide a consistent solution. But for the short term, I used the possibility to provide arguments to the `venv` module, to add the flag `--system-site-packages`. With it, the virtual environment can use the system site package, where `wheel` is available.

The other significant additions are in `tools/snap/build_remote.py` script. If invoking the remote build on a local machine is quite straight-forward, it is another story on the CI because we need build auditability and resiliency during these non-interactive actions. In particular we should avoid as possible inconsistent results on the nightly pipeline and the release pipeline.

So this script wraps the `snapcraft` call into a retry logic, and improves its logs in the context of parallel builds.

For the minor modifications, it is mainly about ensuring that plugins can be built (some of them also need `cffi` for instance), and simplify the Azure Pipeline since all snaps are retrieved in one go.

Please note that the `test-` branches still run only the `amd64` architecture. Indeed I noticed that builds on `arm64` and `armhf` are tending to be very slow to start (up to 40 min) while the `amd64` ones wait at max 10 mins, and usually 30 seconds only when the overall load on Canonical side is low.

To work on `certbot/certbot` repository, one secured file needs to be added, because `snapcraft` needs to be authenticated against Launchpad with credentials allowing remote builds. To do so, from a local machine that have this capability, one can extract the existing file at `$HOME/.local/share/snapcraft/provider/launchpad/credentials`, and register it as a secured file in Azure Pipeline with the name `snapcraftRemoteBuildCredentials`.

* Define scripts

* Setup pipeline to use remote builds

* Focus on packaging builds

* Set credentials

* Setup git

* Launch all builds in parallel

* Add dev dependencies to build cffi and cryptography

* Convert to a python logic

* Reorganize the pipeline

* Handle the fact that snap builds may be taken from cache

* Generate constraints

* Exit code

* Check existence

* Try to handle better non zero exit code

* Add --system-site-packages to get wheel in the venv

* Add executable permissions

* Troubleshoot

* Dynamic display, take the maximum timeout for snap build job

* Allow retries if the remote build does not start

* Trigger only amd64 builds for test branches

* Exit properly

* Update snapcraft.yaml

* Fix snap run

* Set secured file name

* Update .azure-pipelines/templates/jobs/packaging-jobs.yml

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

* Update .azure-pipelines/templates/jobs/packaging-jobs.yml

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

* Update .azure-pipelines/templates/jobs/packaging-jobs.yml

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

* Move order in deps

* Reactivate all builds

* Use Manager() as a context manager

* Use Pool as a context manager

* Some nice refactorings

* Check snapcraft execution interruption with exit codes

* Use f-string and format expressions

* Start log

* Consistent use of single/double quotes

* Better loop to extract lines

* Retry on build failures

* Few optimizations

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-07-22 16:05:20 -07:00
Adrien Ferrand
270b5535e2 Add warnings about Python 3.5 deprecation in Certbot (#8154)
Fixes #8149

This PR adds warnings to warn about the incoming deprecation of Python 3.5 in Certbot.

* Add warnings about Python 3.5 deprecation in Certbot

* Update certbot/certbot/__init__.py

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

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-07-20 17:46:38 -07:00
Adrien Ferrand
74b0340a13 Use a specific tag of adferrand/snapcraft to build QEMU snaps and avoid failures (#8158)
The latest builds of snapcraft introduced somehow several failures when snaps are built on QEMU for armhf. See https://dev.azure.com/certbot/certbot/_build/results?buildId=2326&view=logs&j=7c548e18-6053-5a42-b366-e6480da09a69&t=a7c7ca26-ae0c-54e6-0355-3bfcd7bab03c for instance.

This PR uses a specific tags from `adferrand/snapcraft`, extracted from the last known working `nightly` pipeline, to avoid these failures until a more permanent fix is done. Very likely the fix will be the move to snapcraft remote builds.

* Use a specific tag of adferrand/snapcraft to build snaps and avoid an error on QEMU for armhf.

* Update tools/snap/build.sh

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

* Update tools/snap/build_dns.sh

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

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-07-20 17:17:10 -07:00
Adrien Ferrand
b13dfc6437 Do not create the symlink for test assets on Windows if the asset path is already a symlink (#8159) 2020-07-21 01:01:09 +02:00
schoen
c5bab9b07c Merge pull request #8157 from stefantalpalaru/linodedns
certbot_dns_linode: decrease the default propagation interval
2020-07-20 13:22:18 -07:00
Ștefan Talpalaru
b6964cae2e certbot_dns_linode: decrease the default propagation interval
«When you add or change DNS zones or records, your changes will now be
reflected at our authoritative nameservers in under 60 seconds. This is
down from the previous “every quarter hour” approach that we had for so
long.» - https://www.linode.com/blog/linode/linode-turns-17/
2020-07-19 16:44:37 +02:00
Brad Warren
ebf1349b15 Update to IPython with Python 3.8 support. (#8152) 2020-07-17 13:01:04 -07:00
Brad Warren
9d2e0ac013 Specify the Certbot snap grade. (#8147) 2020-07-17 12:47:11 -07:00
Thomas
05dbda4b51 added inwx plugin (#8115)
* added inwx plugin

* Update using.rst

fixed convention naming
2020-07-15 13:41:15 -07:00
Brad Warren
40a2a5b99f Release version v1.6.0 2020-07-14 17:17:36 -07:00
Adrien Ferrand
68b3b048b9 Use 3rd party plugins without prefix + set a deprecation path for the prefixed version (#8131)
Fixes #4351

This PR proposes a solution to use the third party plugins with the prefix `pip_package_name:` in the plugin name, plugin specific flags and keys in dns plugin credential files.

A first solution has been proposed in #6372, and a more advanced one in #7026. In #7026 was also added a deprecation warning when the old plugin name `pip_package_name:plugin_name` was used.

However there were some limitations with #7026, in particular the fact that existing flags of type `pip_package_name:dns_plugin_option` or keys like `pip_package_name:key` in dns plugin credential files were not read anymore. This would have led to silent failures during renewals if the configuration was not explicitly updated by the user.

I tried to fix that based on #7026, but the changes needed are complex, and create new problems on their own, like unexpected erasure of values in the renewal configurations.

Instead I try in this PR a new approach: the `PluginsRegistry` in `certbot._internal.plugins.disco` module register two plugins for a given entrypoint refering to a third party plugin when `find_all()` is called:
* one plugin with the name `plugin_name`
* one plugin with the name `pip_package_name:plugin_name` (like before)

This way, every existing configuration continues to work without any change (credentials, renewal configuration, CLI flags). And new configurations can refer to the new plugin name without prefix, and use the approriate CLI flags, credentials without this prefix.

On top of it I added the deprecation path given in #7026 (thanks @coldfix!):
* the plugin named `pip_package_name:plugin_name` is hidden from `certbot plugins` output
* the help for this plugin is still displayed, and a deprecation warning is displayed in the description
* when invoked, the same deprecation warning is displayed in the terminal

* Support both prefixed and not prefix third party plugins

* Adapt tests

* Add deprecation path

* Named parameters

* Add deprecation warning in CLI

* Add a changelog
2020-07-10 09:16:21 -07:00
Adrien Ferrand
d434b92945 Build the DNS plugins snaps (#8129)
Fixes #8041

This PR makes Azure Pipeline build the DNS plugins snaps for the 3 architectures during the CI.

It leverages the existing logic for building the Certbot snap in order to deploy a QEMU environment with Docker, and leverages the local PyPI index to speed up the build when installing `cffi` and `cryptography`.

All DNS plugins snaps are constructed in one unique docker container, in order to save the time required to install the system dependencies upon first start of `snapcraft`, and so speed up significantly the build.

Finally, all `amd64` DNS plugins snaps are built within 6 minutes. For `arm64` and `armhf`, it is around 40 mins: this is quite fast in fact, considering that 14 DNS plugins snaps are built.

However, this is still an extremely heavy task to make the full 3 architectures builds, even for Azure Pipelines and its 10 parallel jobs capability. That is why I make the `arm64` and `armhf` builds be skipped for the `full-test-suite`, and let them run only for `nightly` and `release`. This means however that these builds will not be done for the release branches. If this is a problem, I can put a more elaborate suspend condition to triggers the builds in this case.

All snaps are stored in the pipeline artifacts storage, making them available for publication during a `release` pipeline.

The PR is set as Draft for now, because I use temporarily `pr_test-suite` to validate the packaging jobs when commits are pushed. Once the PR is ready, I will revert it back to the normal configuration (run the standard tests).

* Configure a script to build DNS snaps

* Focus on packaging

* Trigger all architectures

* Add extra index

* Prepare conditional suspend

* Set final suspend logic

* Set final suspend value

* Loop for publication

* Use python3

* Clean before build

* Add a test

* Add test job in Azure

* Preserve env

* Apply normal config for pipelines

* Skip QEMU jobs only for test branches

* Makes snap run tests depends also on the Certbot snap build

* Update .azure-pipelines/templates/jobs/packaging-jobs.yml

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

* Update .azure-pipelines/templates/stages/deploy-stage.yml

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

* More accurate way to get the plugin snap name

* Integrate DNS snap tests into certbot-ci

* Fixes

* Update certbot-ci/snap_integration_tests/conftest.py

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

* Update certbot-ci/snap_integration_tests/conftest.py

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

* Clean an _init_.py file

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-07-09 11:33:25 -07:00
bdeweygit
1697d66ba7 Be more informative about reasons for using Docker (#28)
People who are considering running Certbot with Docker are probably doing so because their webserver is to be run with Docker. These changes to the README should help them to understand that doing so will require knowledge of Docker volumes and that the architectural justification for running Certbot in a separate container is the "one service per container" best practice.
2020-07-09 09:47:38 -07:00
J0WI
a6a998d11b Upgrade to Alpine 3.12 (#27) 2020-07-08 18:32:32 +02:00
Brad Warren
f82e2cc714 s/snapcraft push/snapcraft upload/g (#8137) 2020-07-08 08:05:33 +02:00
Brad Warren
433c6f391c Merge pull request #8136 from certbot/candidate-1.6.0
Update files from 1.6.0 release
2020-07-07 11:45:39 -07:00
Brad Warren
d64bb81864 Fix typo (#26) 2020-07-07 20:18:06 +02:00
Brad Warren
88e183e69e Release version 1.6.0 2020-07-07 11:04:52 -07:00
Brad Warren
590eeca38a Bump version to 1.7.0 2020-07-07 10:33:16 -07:00
Brad Warren
b9a25c3987 Add contents to certbot/CHANGELOG.md for next version 2020-07-07 10:33:15 -07:00
Brad Warren
41b99eba79 Release 1.6.0 2020-07-07 10:33:13 -07:00
Brad Warren
de39a42e6a Update changelog for 1.6.0 release 2020-07-07 10:13:21 -07:00
Adrien Ferrand
183ccc64b1 Some improvements (#8132)
Short PR to improve some things during snap builds:
* cleanup snapcraft assets before a build, in order to avoid some weird errors when two builds are executed consecutively without cleanup
* use python3 explicitly in `tools/simple_http_server.py` because on several recent distributions, `python` binary is not exposed anymore, only `python2` or `python3`.
2020-07-06 16:04:59 -07:00
Brad Warren
6bca930752 Remove unnecessary symlink (#8135)
This isn't needed anymore thanks to the line:
```
AWS_EC2_PEM_FILE: $(testFarmPem.secureFilePath)
```
2020-07-06 15:31:24 -07:00
Brad Warren
cd993cdfb1 Remove grade devel from Certbot snap. (#8128)
If you go to a URL like https://snapcraft.io/certbot/releases and try to move the Certbot snap into the candidate or stable channels, you cannot do so. There is a tooltip which says that revisions with the grade devel cannot be promoted to candidate or stable channels.

The documentation for `grade` can be found at https://snapcraft.io/docs/snapcraft-yaml-reference where it says the value is optional and

> Defines the quality grade of the snap.
Type: enum
Can be either devel (i.e. a development version of the snap, so not to be published to the stable or candidate channels) or stable (i.e. a stable release or release candidate, which can be released to all channels)
Example: [stable or devel]

I'm working on a proposal for our next steps for snaps which involves moving the Certbot snap to the stable channel. I of course won't make those changes without giving others a chance to share their opinion, but I'd like to avoid the situation where we're technically unable to move the Certbot 1.6.0 snap to the stable channel despite wanting to do so.

I started to make the same changes to the DNS plugins, but I personally think it's too soon to propose stable versions of those yet and `grade` is a simple way to ensure we don't accidentally promote something there.

You can see the snap being built and run successfully with this change at https://dev.azure.com/certbot/certbot/_build/results?buildId=2246&view=results.
2020-07-06 12:31:55 -07:00
Brad Warren
9f994d7a50 Run at 4:30 UTC to have Azure reparse YAML file. (#8133) 2020-07-06 20:41:26 +02:00
Brad Warren
4f3dc8862d Switch build status to nightly pipeline. (#8127)
The advanced pipeline no longer exists.
2020-07-02 16:05:28 -07:00
Brad Warren
48139f382d Do not build pushes to master. (#8126) 2020-07-03 01:00:35 +02:00
Adrien Ferrand
8a3a8c7097 Migrate the CI pipeline from Travis to Azure Pipeline (#8098)
Fixes #8071 and fixes https://github.com/certbot/certbot/issues/8110.

This PR migrates every job from Travis in Azure Pipeline.

This PR essentially converts the Travis jobs into Azure Pipeline with a complete iso-fonctionality (or I made a mistake). The jobs are added in the relevant existing pipelines (`main`, `nightly`, `advanced-test`, `release`). A global refactoring thanks to the templating system is done to reduce greatly the verbosity of the pipeline descriptions.

A specific feature (not present in Travis) is added: the stage `On_Failure`. Using directly the Mattermost API, it allows to notify pipeline failure in a Mattermost channel with a link to the failed pipelines without the need to authenticate to Microsoft.

See https://github.com/certbot/certbot/pull/8098#issuecomment-649873641 for the post merge actions to do at the end of this work.
2020-07-02 15:01:21 -07:00
ohemorange
cb3ff9ef18 Set up CentOS 8 test farm tests (#8122)
Fixes #7420.

* Set up CentOS 8 test farm tests

* Don't add to apache2_targets until 7273 is resolved

* Start upgrade test from a version that works on centos 8

* remove when possible from targets
2020-07-01 17:07:41 -07:00
alexzorin
f743dbec3a certbot: add --preferred-chain (#8080)
* acme: add support for alternative cert. chains

* certbot: add --preferred-chain

* remove support for issuer SKI matching

* show --preferred-chain in "run" help

* warn if no chain matched and it's not a dry-run

* fix existing failing tests

* add unit, integration tests

* bump acme dependency to dev version

* simplify test to avoid py2.7 recursion bug

* add preferred_chain to STR_CONFIG_ITEMS

* reduce preferred_chain warning to info level

* acme: fix some docstrings in .messages

* certbot: fix docstring in crypto_util

* try to fix certbot-nginx acme dep problem
2020-06-30 17:45:39 -07:00
ohemorange
2af297d72f Make each DNS plugin respect EXCLUDE_CERTBOT_DEPS (#8117)
* Don't include certbot deps when EXCLUDE_CERTBOT_DEPS is set

* import os
2020-06-29 16:58:26 -07:00
Brad Warren
95ef53e5d5 Add missing spaces to manual plugin help. (#8116) 2020-06-29 13:34:24 -07:00
Brad Warren
24c5fab8b6 Add awscli to requirements.txt (#8113) 2020-06-25 16:52:56 -07:00
ohemorange
713b91495b Fix paths when calling out to programs outside of snap (#8108)
Fixes #8093.

This PR modifies and audits all uses of `subprocess` and `Popen` outside of tests, `certbot-ci/`, `certbot-compatibility-test/`, `letsencrypt-auto-source/`, `tools/`, and `windows-installer/`. Calls to outside programs have their `env` modified to remove the `SNAP` components of paths, if they exist. This includes any calls made from hooks, calls to `apachectl` and `nginx`, and to `openssl` from `ocsp.py`.

For testing manually, rsync flags will look something like:

```
rsync -avzhe ssh root@focal.domain:/home/certbot/certbot/certbot_*_amd64.snap .
rsync -avzhe ssh certbot_*_amd64.snap root@centos7.domain:/root/certbot/
```

With these modifications, `certbot plugins --prepare` now passes on Centos 7.

If I'm wrong and we package the `openssl` binary, the modifications should be removed from `ocsp.py`, and `env` should be passed into `run_script` rather than set internally in its calls from nginx and apache.

One caveat with this approach is the disconnect between why it's a problem (packaging) and where it's solved (internal to Certbot). I considered a wrapping approach, but we'd still have to audit specific calls. I think the best way to address this is robust testing; specifically, running the snap on other systems.

For hooks, all calls will remove the snap paths if they exist. This is probably fine, because even if the hook intends to call back into certbot, it can do that, it'll just create a new snap.

I'm not sure if we need these modifications for the Mac OS X/ Darwin calls, but they can't hurt.

* Add method to plugins util to get env without snap paths

* Use modified environment in Nginx plugin

* Pass through env to certbot.util.run_script

* Use modified environment in Apache plugin

* move env_no_snap_for_external_calls to certbot.util

* Set env internally to run_script, since we use that only to call out

* Add env to mac subprocess calls in certbot.util

* Add env to openssl call in ocsp.py

* Add env for hooks calls in certbot.compat.misc.

* Pass env into execute_command to avoid circular dependency

* Update hook test to assert called with env

* Fix mypy type hint to account for new param

* Change signature to include Optional

* go back to using CERTBOT_PLUGIN_PATH

* no need to modify PYTHONPATH in env

* robustly detect when we're in a snap

* Improve env util fxn docstring

* Update changelog

* Add unit tests for env_no_snap_for_external_calls

* Import compat.os
2020-06-25 15:36:29 -07:00
dmmortimer
0f4c31c9c7 Generalize renewal rate limit UI warning message (#3456) (#8061)
- Old text hard codes the rate limit
 - Let's Encrypt CA might change its rate limit
 - Other CAs might have different rate limits

Update CHANGELOG.md
2020-06-25 11:43:08 -07:00
dkp
b9a8248541 Remove SSL Labs From Certbot Output (#8109)
The Apache plugin expects clients to support SNI, but
SSL Labs tries without SNI and includes the results
in their score.

Closes certbot/certbot#7728
2020-06-25 11:42:07 -07:00
Brad Warren
8027430625 Correct plugin constraints. (#8104) 2020-06-23 14:16:41 -07:00
ohemorange
bce14ae65f Make DNS plugin snaps use core20 (#8106)
Fixes #8103.

* Update the DNS plugin generator script to core20 syntax

* Generate new snapcraft.yamls for the DNS plugins

* Update certbot.wrapper to search for python3.8 paths
2020-06-23 09:31:08 -07:00
Adrien Ferrand
25d1977d4f Add script and generated snapcraft.yaml files (#8096)
This PR adds a proper snapcraft.yaml file for each DNS plugin, and provides a shell script to generate them.
2020-06-22 17:07:08 -07:00
Brad Warren
46eb4ec7e3 Remove unneeded step to create constraints file. (#8102) 2020-06-22 16:48:50 -07:00
Brad Warren
3ae8fa640b Remove snap-plugin from README (#8101) 2020-06-22 15:49:18 -07:00
Adrien Ferrand
035b6514db Build the constraints file insided snapcraft (#8097)
Fixes #7957

This PR makes snapcraft generate itself the dependencies constraints file during the snap build, instead of having an external script that does it before calling snapcraft.
2020-06-22 13:54:18 -07:00
Brad Warren
f151099342 Do not unstage generated cffi augeas (#8094)
This line seems to refer to [augeas.py](https://github.com/hercules-team/python-augeas/blob/v0.5.0/augeas.py) from the version of Augeas we normally have pinned. This was necessary when we were installing each Certbot component in separate parts and only combining them later to ensure that the Augeas fork (which uses cffi) was used instead of the unmodified pinned version of Augeas.

Since everything is installed in one part and we're removing the Augeas pinning now though, this line is no longer necessary. You can see the snap being built and tested successfully with this change at https://travis-ci.com/github/certbot/certbot/builds/172134518.

* Do not unstage generated cffi augeas

* Add comment about deleted line.
2020-06-22 12:11:38 -07:00
Florian Klink
25e79e4aca tree-wide: use LooseVersion instead of StrictVersion (#8081)
According to `distutils/version.py`, StrictVersion is pretty strict in
what version numbers to accept:

> A version number consists of two or three dot-separated numeric
> components, with an optional "pre-release" tag on the end.  The
> pre-release tag consists of the letter 'a' or 'b' followed by a number.

This assumption already fails for some pretty basic python libraries
itself, like setuptools, also available in `46.1.3.post20200610`, a
completely valid version number according to
https://www.python.org/dev/peps/pep-0440/#post-releases.

There doesn't seem to be a particular reason on why StrictVersion has
been used here, so let's use LooseVersion, to be compatible with these
versions.

Co-authored-by: Adrien Ferrand <adferrand@users.noreply.github.com>
2020-06-19 17:11:35 +02:00
Brad Warren
db064a4109 Use certbot.wrapper during renewal. (#8095) 2020-06-18 16:41:12 -07:00
Adrien Ferrand
860af81fef Deferred EFF subscription until the first certificate is successfully issued (#8076)
* Base logic

* Various controls when email is None

* Adapt eff tests

* Forward compatibility

* Also for csr

* Explicit regr or meta updates in account objects

* Adapt logic to ask for eff subscription during registering

* Adapt tests

* Move dry-run control

* Add some relevant controls on handle_subscription call checks
2020-06-18 15:58:19 -07:00
ohemorange
70c8481fd8 Don't include certbot deps when EXCLUDE_CERTBOT_DEPS is set in plugins (#8091)
This will allow DNS plugin snaps to build if they rely on unreleased acme/certbot, and remove other copy of Certbot from externally snapped plugins. Fixes #8064 and fixes #7946. Implementation is based on the design [here](https://github.com/certbot/certbot/issues/8064#issuecomment-645513120).

To test, see reverted commit 8632064. Steps taken:

- added changes to setup.py and snapcraft.yaml
- successfully snapped, connected, ran `sudo certbot plugins --prepare`
- added temporary changes to have both certbot and certbot-dns-dnsimple use DNSAuthenticator2
- snapped and installed certbot, `certbot plugins` failed as expected.
- snapped and installed certbot-dns-dnsimple, `sudo certbot plugins --prepare` succeeded
- Inspected dns plugin's `bin` and `lib`; no `certbot` or `acme`, as expected.
```
$ ls /snap/certbot-dns-dnsimple/current/lib/python3.6/site-packages/
OpenSSL                                         future-0.18.2.dist-info        requests_file.py
PyYAML-5.3.1.dist-info                          idna                           setuptools
_cffi_backend.cpython-36m-x86_64-linux-gnu.so   idna-2.9.dist-info             setuptools-47.3.1.dist-info
certbot_dns_dnsimple                            lexicon                        six-1.15.0.dist-info
certbot_dns_dnsimple-1.6.0.dev0-py3.6.egg-info  libfuturize                    six.py
certifi                                         libpasteurize                  tldextract
certifi-2020.4.5.1.dist-info                    past                           tldextract-2.2.2.dist-info
cffi                                            pip                            urllib3
cffi-1.14.0.dist-info                           pip-20.1.1.dist-info           urllib3-1.25.9.dist-info
chardet                                         pkg_resources                  wheel
chardet-3.0.4.dist-info                         pyOpenSSL-19.1.0.dist-info     wheel-0.34.2.dist-info
cryptography                                    pycparser                      yaml
cryptography-2.8.dist-info                      pycparser-2.20.dist-info       zope
dns_lexicon-3.3.26.dist-info                    requests                       zope.interface-5.1.0-py3.6-nspkg.pth
easy_install.py                                 requests-2.23.0.dist-info      zope.interface-5.1.0.dist-info
future                                          requests_file-1.5.1.dist-info
$ ls /snap/certbot-dns-dnsimple/current/bin/
chardetect  futurize  lexicon  pasteurize  tldextract
```
- reset to HEAD^
- snapped and installed certbot to not have the DNSAuthenticator2 changes, `certbot plugins` failed as expected.

* Don't include certbot deps when EXCLUDE_CERTBOT_DEPS is set

* Set EXCLUDE_CERTBOT_DEPS in certbot-dns-dnsimple/snap/snapcraft.yaml
2020-06-18 15:24:10 -07:00
ohemorange
c5e5594ac3 Switch to using snap-constraints in dns plugin so the .gitignore catches it. (#8092) 2020-06-18 15:15:41 -07:00
ohemorange
ad8ffc1bf0 Merge pull request #8089 from certbot/squashed-snap-plugin
Merge the snap-plugin branch into master
2020-06-18 12:59:04 -07:00
Erica Portnoy
29a23d3148 Update README instructions for merged master
Use Focal when following README instructions

Discard changes to disco.py

bring README up to date with current knowledge

Move README to snap/local/
2020-06-18 12:49:58 -07:00
Brad Warren
44b1bd8e0e Update README
fix branch name

grammar

remove readme link

Remove links to Robie's repo.

say you cant use docker

Commands not command

Update publishing permissions section.

Have Certbot trust plugins.

Do not run snapcraft with sudo.
2020-06-18 12:20:57 -07:00
Robie Basak
0bebdedcbc Initial revision
Fix headings

Fix error in build instructions
2020-06-18 12:20:56 -07:00
Brad Warren
8ccc96bbdd update certbot-dns-dnsimple snapcraft.yml.
update dnsimple snapcraft.yml
2020-06-18 12:20:53 -07:00
Robie Basak
4a2618d415 Add CERTBOT_PLUGIN_PATH support
Initial revision

Fix interface export path

Switch to strict confinement

Normalise slot parameters
2020-06-18 12:20:18 -07:00
ohemorange
bcb3554836 Merge pull request #8086 from certbot/core20-squashed
Upgrade snap to be based on core20

This PR makes several changes to be built on top of the core20 base snap. Fixes #7854.

The main changes are to `snapcraft.yaml`. With Snapcraft 4.0/core20 base, the python plugin is a thin wrapper, basically creating a `venv` and installing the packages from the source. The trouble with this is that it runs pycache, creating caches that conflict from the different parts. So to solve that, we put everything in a single part. Other changes include:

- We use classic confinement, so we need to specify a bunch of python packages to `stage-packages`, as mentioned [here](https://forum.snapcraft.io/t/trouble-bundling-python-with-classic-confinement-in-core20-4-0-4/18234/2).
- The certbot executable now lives in `bin`, so specify running `certbot/bin`.
- Since `python-augeas` is now being pulled into the single part, remove the pinning from constraints so we can use the latest version directly from github.
- Precompile our `cryptography` and `cffi` wheels to be based on python3.8.

Separately, we had to upgrade the snapcraft docker image to be based on focal, due to the thin wrapper situation. This was accomplished [here](https://github.com/adferrand/snapcraft/pull/1).
2020-06-17 17:20:46 -07:00
Erica Portnoy
0f53e8ad4e Upgrade snap to be based on core20
* Get rid of a whole bunch of error message
* Remove some more overlaps
* don't use certbot from nginx and apache
* use python3 from bin
* certbot needs to be in bin
* try to exclude just the certbot folder
* try a couple things to use the python from the venv bin
* play around with which versions of things we want from each package
* ok, certbot-nginx does need to stage bin
* certbot needs to not stage bin. why does certbot not put certbot in bin?
* fail to inspect more versions of things in the container shell
* take cffi backend from python-augeas
* if we use certbot from bin things should work?
* why is bin not in path? no idea, but let's get it compiled then inspect things in the snap shell
* use snap.certbot instead of bin/certbot
* it does require bin/certbot. I don't know why.
* let's see if we can stick it all in one step
* try installing local subdirectories
* move python-augeas into the single part
* remove after
* put back python-augeas part for now; ERROR: Could not satisfy constraints for 'python-augeas': installation from path or url cannot be constrained to a version
* how was this previously working without git installed? install git.
* maybe it needs to already have python3-wheel installed
* maybe wheel will install first if I change it to -e
* no -e
* maybe try a different python3 package to stage
* this last change wasn't necessary
* remove the bin/ from renew
* nope, it does need bin/certbot
* back to wget
* stage a bare python3
* add all necessary python packages to stage-packages
* pretty sure we don't actually need wheel. let's try removing it!
* remove python-augeas, since we have it pinned to an older version in cb-auto that might work
* stage augeas
* still need libaugeas-dev
* ok let's try building
* combining into one part works! just make sure to unpin python-augeas when generating snap-constraints.txt
* change our scripts to unpin python-augeas
* Use ubuntu 20 in compile_native_wheels.sh
* .travis.yml should use python3-dev instead of python-dev
* jk! we don't need python3-dev in travis
* Update cffi and cryptography wheels for ubuntu20 version of python
* looks like we need python3-dev to build things
* Remove deprecated i386 wheels
2020-06-17 16:57:51 -07:00
Brad Warren
2a18ae6d57 Trivially upgrade to core20? 2020-06-17 16:52:17 -07:00
Brad Warren
3c4b922197 Remove the need for TRAVIS to be set. (#8084)
I initially added this when the script was doing things like migrating all LXD containers to the snap. I think the external side effects are now pretty minimal thought so I think we can remove the need for this environment variable which makes it easier to use outside of CI for manual testing.
2020-06-17 14:41:11 -07:00
Adrien Ferrand
0bb1f0b2ce Drop i386 architecture on snap build (#8083)
This PR remove the i386 architecture in the snap build process, because the base snap `core20` is not available for this architecture.
2020-06-16 15:57:05 -07:00
Brad Warren
ba192f321d Remove on_cancel which isn't recognized by Travis. (#8077) 2020-06-15 13:27:23 -07:00
Cameron Steel
961c573864 dns-cloudflare: Update docs and error messages to reflect new API permissions (#8015)
* Tweaks for improved Cloudflare API

* Update docs for dns-cloudflare

* Update tests and changelog

* Fix bad merge

* Fix error code for record add

* Improve error message

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

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

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

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

* Fix build deps

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

* Configure jobs on Travis

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

* Specify TARGET_ARCH during snap build

* Do not do anything if TOXENV is not set

* Various optimizations

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

* Add up to date wheels

* Organizing scripts

* Set dest dir

* Get back original configuration for Travis

* Add comments

* Update common_libs.sh

* Use adferrand/snapcraft

* Test build

* Stable snapcraft

* Update build_and_install.sh

* Move back snap builds to the cron/release pipeline

* Update snap/local/compile_native_wheels.sh

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

* Update snap/local/compile_native_wheels.sh

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

* Update snap/local/compile_native_wheels.sh

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

* Update snap/local/build_and_install.sh

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

* Enable i386 builds, various optimizations

* Update dependencies

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

* Fix wheels compilation

* Relax permissions

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

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

(cherry picked from commit 7076a55fd82116d068e2aca7239209b7203917d2)

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

* Update certbot-wrapper to python3.6 version

* add source field

* Update changelog

* Use bash instead of sh

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

* Exit if something goes wrong

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

* No leading : when modifying empty PYTHONPATH

* Improve bash handling of PYTHONPATH manipulation

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

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

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

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

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

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

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

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

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

* Implement umask for Windows

* Set umask at the beginning of tests

* Fix lint, update local oldest requirements

* Update certbot-apache/setup.py

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

* Improve tests

* Adapt filesystem.makedirs for Windows

* Fix

* Update certbot-apache/setup.py

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

* Changelog entries

* Fix lint

* Update certbot/CHANGELOG.md

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

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

* add user to lxd

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

* AUTHORS.md: add Brian Heim

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

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

* Update the CHANGELOG.

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

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

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

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

Fixes #8030

* Pin cryptography and parsedatetime

* Upgrade dependencies

* Remove authoritative constraint

* Upgrade dependencies

* Rebuild certbot-auto

* Update letsencrypt-auto-source/rebuild_dependencies.py

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

* Honor specific requirements in the AUTHORITATIVE_CONSTRAINTS

* Fix injection

* Update dependencies

* Update rebuild_dependencies.py

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

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

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

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

* export PIP_NO_BINARY in pip install subshell in test_sdists.sh

* set environment variable on the line that installs most packages

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

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

* Update changelog

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

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

* only delete key if it exists

* Check OpenSSL version before trying to set PIP_NO_BINARY

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

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

* Normalise slot parameters

(cherry picked from commit 810941979bcf609c1e0be18e9263abf046b90e82)

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

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

Implements the plan described in #7667.

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

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

* Add plugin connection hook to accept root trust

* snapctl requires a configure hook to set options

* Add sh notice

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

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

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

* Add a test

* Add comment

* Update certbot/certbot/compat/filesystem.py

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

* Update to MisconfigurationError and add/fix tests

* Remove error cases we no longer hit and associated test

* mock out function to have consistent error across machines

* improve changelog message

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

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

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

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

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

* Update certbot/tests/compat/filesystem_test.py

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

* Fix docstring

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2020-05-06 12:33:50 -07:00
Brad Warren
d8e9f558c2 Release version v1.4.0-2 2020-05-05 17:11:24 -07:00
Adrien Ferrand
3a997a5631 Install pipstrap to pin setuptools/pip/wheels, since setuptools and pip continues to play with us. (#23)
So, setuptools broke the installation setup, by removing a deprecated API that is still used by some of our dependencies (see pypa/setuptools#2017)

This PR fixes the Docker build by using pipstrap to pin pip/setuptools/wheels, like it is done in several critical places (certbot-auto, ...).

An issue in certbot is opened to fix more generally the problem in most recent versions of setuptools: certbot/certbot#7976

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

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

This comment here should help ensure this.

* Add pytest warnings warning.

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

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

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

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

* Run hooks in powershell on Windows

* Fix hook test

* Fallback to unittest.mock

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

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

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

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

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

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

### Travis stages

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

### snap/local

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

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

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

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

### Checklist for landing this PR

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

* Move snapcraft.yml up one level.

* update source

* move test.sh to tox.ini

* use new tox.ini in .travis.yml

* move snap build code

* make script executable

* remove unused python3-dev

* don't use deprecated classic flag

* go back to stable channel

* add nginx in snap addons

* add deploy steps

* Add comments explaining external tox envs.

* error if not in CI

* don't use --depth

* remove old .travis.yml

* Add big comment about SNAP_TOKEN.

* Set all_branches: true.

* Add repo setting.

* run travis on tags

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

* Deprecate certbot-auto on macOS

* Deprecate certbot-auto on FreeBSD

* build le-auto

* Update Changelog

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

* Add snap instructions.

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

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

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

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

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

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

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

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

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

These have not been needed since 5d7969a.

* Work around dev part dependency failure case

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

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

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

* Switch to using lxd for the snapcraft run

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

* Switch build to use core 18

Fixes: #14

* Move constraints into a list

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

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

* version-script -> snapcraftctl set-version

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

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

* Do not explicitly install snapd

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

* Adjust automatic snap deployment configuration

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

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

* Install libaugeas0 in python-augeas part build

This part needs libaugeas0 to build.

* Bump to 0.26.1

* Always act directly on upstream master

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

Closes: #5

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

* Add empty Travis build

* Add Travis automatic snap edge publication

* Add integration test

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

* Point Augeas to its lens library

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

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

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

now it's

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

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

This PR changes things back to the intended behavior.

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

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

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

* use py38 for code coverage in Travis

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

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

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

* update changelog

* error when trying to build wheels with old setuptools

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

* error when trying to build wheels with old setuptools

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

* error when trying to build wheels with old setuptools

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

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

* Conditionally install mock in apache

* error out on newer python and older setuptools

* error when trying to build wheels with old setuptools

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

Part of #7886.

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

* Conditionally install mock in certbot

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

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

* error out on newer python and older setuptools

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

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

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

* Conditionally install mock in acme

* error out on newer python and older setuptools

* error when trying to build wheels with old setuptools

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

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

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

This reverts commit 77871ba71c.

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

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

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

* Conditionally install mock in certbot

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

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

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

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

* Conditionally install mock in acme

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

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

* Remove unused imports

* Remove unnecessary comprehension

* Use literal syntax to create data structure

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

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

* stop using urllib2 in test farm tests

* use six for urllib instead

* remove fabric lcd usage

* correct lcd removal

* remove fabric cd

* convert some remote calls to v2

* move more cxns to v2

* get run working with prefix

* get sudo commands working

* remove final fabric v1 references including local

* update requirements and README

* add new venv to gitignore

* update version used in travis

* remove deploy_script unused kwargs

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

* hardcode the gopath due to broken env manamagement in fabric2

* Update letstest readme

* move the comment about hardcoding the ggopath

* catch BaseException instead of Exception

* work around fabric #2007

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

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

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

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

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

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

* Fix fullchain parsing for CRLF chains.

fullchain parsing now works in two passes:

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

* typo in docstring

* remove redundant group in regex

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

* Print boulder logs if boulder fails to start

* Print description and fix command.

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

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

Fixes #7386

* use idiomatic kwargs default value

* move HTTP01Server lower to fix mypy forward ref.

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

* maybe its not an OSError?

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

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

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

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

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

* Remove interactive redirect ask

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

* update changelog

* remove references to removed display.enhancements

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

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

Closes #7859

* Update CHANGELOG

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

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

* Move augeasnode_test tests to more generic parsernode_test

* Revert "Move augeasnode_test tests to more generic parsernode_test"

This reverts commit 6bb986ef786b9d68bb72776bde66e6572cf505a9.

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

* Don't calculate coverage for skeleton functions

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

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

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

* Check OpenSSL version

* Remove os imports

* debian override still needs os

* Reformat remaining apache tests with modules dict syntax

* Clean up more apache tests

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

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

* warn thoroughly and consistently in openssl_version function

* update tests for new warnings

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

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

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

* string does need to be a literal

* patch builtins open

* add debug, remove space

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

* fix relative test location for cwd

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

* Revert test file to status in master.

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

* fix grep, tail, and mod_ssl location on centos

* strip the trailing whitespace from fedora

* just use LooseVersion in test

* call apache2ctl on debian systems

* Use sudo for apache2ctl command

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

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

* Reduce warnings while testing by setting mock _openssl_version.

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

* test last warning case for coverage

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

* Remove stray pylint link.

* Cleanup more pylint comments

* Cleanup magic_typing imports

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

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

* Fix acme compliance to RFC 8555.

* Working mixin

* Activate back pebble strict mode

* Use mixin for type

* Update dependencies

* Fix also in fields_to_partial_json

* Update pebble

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

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

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

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

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

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

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

* Add potential missing parameter in validation for tls-alpn

* Improve comments

* Make a class private

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

* Add changelog

* Explicitly close the TLS connection by the book.

* Remove unused exception

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

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

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

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

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

* Inject environment variables in manual scripts about remaining challenges

* Adapt tests

* Less variables and less lines

* Update manual.py

* Update manual_test.py

* Add documentation

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

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

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

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

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

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

* stop testing on win2012

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

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

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

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

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

* Check OCSP status as part of determining to renew

* add integration tests

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

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

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

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

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

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

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

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

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

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

Two noteworthy things about this PR are:

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

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

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

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

* remove codecov

* remove unused variable group

* remove codecov.yml

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

The only references I didn't remove were:

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

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

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

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

To accomplish this, I propose splitting the "advanced" pipeline into two cases. One is for builds on protected branches where we want to be notified if they fail while the other is just used to manually run tests on certain branches.
2020-02-27 10:43:41 -08:00
m0namon
f169c37153 Merge pull request #7742 from osirisinferi/force-non-restrictive-umask
Force non restrictive umask when creating challenge directory in Apache plugin
2020-02-26 17:09:20 -08:00
cumul0529
a489079208 Update parser test to better assert logging output 2020-02-25 13:26:36 +09:00
cumul0529
ddf68aea80 Update comment in testdata file 2020-02-25 13:21:10 +09:00
cumul0529
2ae090529e Fixed typo & some trivial documentation change 2020-02-25 13:18:03 +09:00
cumul0529
5b29e4616c Add simple comments 2020-02-24 05:30:54 +09:00
cumul0529
32904d8c9e Add TestCase.assertLogs() backport for Python 2.7 2020-02-24 05:13:34 +09:00
cumul0529
d68f37ae88 Add this change to CHANGELOG.md 2020-02-24 02:56:43 +09:00
cumul0529
b3071aab29 Add my name to AUTHORS.md
:)
2020-02-24 02:52:41 +09:00
cumul0529
2aac24c982 Trivial code clean-up 2020-02-24 02:49:08 +09:00
cumul0529
20df5507ae Add logging test for _parse_files() 2020-02-24 02:48:55 +09:00
cumul0529
36311a276b Add test case for _parse_ssl_options() 2020-02-24 02:46:27 +09:00
cumul0529
22685ef86f Remove unicode_support/ path in test case 2020-02-24 02:23:46 +09:00
cumul0529
c3cfd412c9 Relpace deprecated logger.warn() with logger.warning() 2020-02-24 01:47:12 +09:00
Seth Schoen
0b21e716ca Fix lint problems with long lines 2020-02-24 01:45:23 +09:00
cumul
8b90b55518 Added test for valid/invalid unicode characters 2020-02-24 01:35:00 +09:00
cumul
247d9cd887 Use io module instead of codecs
See https://mail.python.org/pipermail/python-list/2015-March/687124.html
2020-02-24 01:29:37 +09:00
cumul
d6ef34a03e Use UTF-8 encoding for nginx plugin 2020-02-24 01:25:16 +09:00
osirisinferi
9819443440 Add test 2020-02-22 15:22:27 +01:00
Brad Warren
df584a3b90 Remove _internal from docstring. 2020-02-12 13:12:03 -08:00
ohemorange
bca73f9932 Grammar improvements (#18)
Update the README with improved grammar.
2020-02-05 16:08:57 -08:00
OsirisInferi
d3a4b8fd8c Missing import 2020-02-05 22:27:12 +01:00
OsirisInferi
f3ed133744 Wrap makedirs() within exception handelrs 2020-02-05 22:17:29 +01:00
Erica Portnoy
a180d5d5c9 Release version v1.2.0 2020-02-04 15:34:00 -08:00
OsirisInferi
601a114d1b Update changelog 2020-02-04 19:47:27 +01:00
OsirisInferi
86926dff92 Use unrestrictive umask for challenge directory 2020-02-04 19:27:27 +01:00
Brad Warren
097c76f512 Merge branch 'master' into no-client-plugins 2020-01-16 11:56:41 -08:00
Brad Warren
78624a2b8c Release version v1.1.0 2020-01-14 11:49:36 -08:00
J0WI
695107bc98 Update Python to 3.8 (#16)
https://github.com/certbot/certbot/pull/7392
2020-01-13 10:39:36 -08:00
J0WI
fb323e083a Update Alpine to 3.11 (#14) 2020-01-10 17:36:58 -08:00
Brad Warren
5713decf23 Update other links to point to new GH org. (#13) 2020-01-02 21:41:10 +01:00
Guillaume Vincent
c194381f04 Fix broken link (#12) 2020-01-02 14:27:47 -05:00
Brad Warren
b92eb6f620 Release version v1.0.0 2019-12-03 10:17:50 -08:00
Adrien Ferrand
ea44834c41 Fix docker build regarding the new certbot layout (#11)
This PR adds appropriate corrections to certbot dockerfile to work with the new layout (moving certbot python project in its own subdirectory).

This PR has been tested with success using the `build.sh` script on a fake v1.0 version of certbot published on my fork (https://github.com/adferrand/certbot/releases/tag/v1.0) instead of archives from `certbot/certbot`.
2019-12-02 12:39:55 -08:00
Brad Warren
2bc64183a8 fix docstring 2019-11-11 17:11:47 -08:00
Brad Warren
a730b00a36 Release version v0.40.1 2019-11-05 19:47:14 -08:00
Erica Portnoy
5e01467e2c Release version v0.40.0 2019-11-05 15:08:42 -08:00
Oriol Teixidó
e9a9a180bb Multiarch (#3)
* Add one Dockerfile for each supported architecture

* Update multi arch hooks

* Create multi arch scripts

* Update README.md

* WIP. Use build args instead of multiple Dockerfiles in build script

* WIP. Fix typo mistake

* Use build args instead of multiple Dockerfiles in build script

* WIP. Build all the architectures in one DockerHub build

* Add arm64v8 architecture

* WIP. Testing build all the architectures in one DockerHub build

* Revert "WIP. Testing build all the architectures in one DockerHub build"

This reverts commit 94a89398a4120b183d2851ac7cb9c93db0e3d187.

* Refactor tag docker images in hooks/post_push files

* Use variables instead of positional arguments

* Export externally used variables

* Use ${variable//search/replace} instead of echo $variable | sed.

* Update README.md

* Add Cleanup in build.sh script

* Fix tagging error in post_push hook

* Add "-ex" flags to bash script

* Test tagging images in build hook

* Tagging in hook/post_build instead of hook/post_push

* Push built architecture dependent image

* Use Dockerfile argument instead of fixed value

* Fix typo

* Use parameter instead of global variable

* Use custom "hook/push" to prevent duplicated push

* Make a short doctype for each function declared in common
2019-10-11 17:07:45 +02:00
Erica Portnoy
67fddae90d Release version v0.39.0 2019-10-01 13:30:11 -07:00
Brad Warren
7337f64180 Release version v0.38.0 2019-09-03 14:15:44 -07:00
J0WI
d296ef2dcd Update to Python 3.7 (#5)
certbot/certbot#6759
closes #4
2019-08-28 12:43:58 -07:00
Erica Portnoy
f64386c73c Release version v0.37.2 2019-08-21 16:13:06 -07:00
Erica Portnoy
1666e85118 Release version v0.37.1 2019-08-08 17:58:41 -07:00
Brad Warren
db522aa155 Release version v0.37.0 2019-08-07 11:46:22 -07:00
Brad Warren
d0d7521215 Release version v0.36.0 2019-07-24 15:48:45 -07:00
Brad Warren
2fc6f6e619 Make deploy.sh executable. (#3) 2019-07-23 10:32:43 +02:00
J0WI
d8ab321894 Upgrade to Alpine 3.10 (#2)
certbot/certbot#7250
2019-07-22 17:07:14 -07:00
Adrien Ferrand
62b054f265 Create the deployment logic (#1)
* Integrate original adferrand/certbot-docker

* Make build.sh more symetric for readability

* Update README.md

Co-Authored-By: Joona Hoikkala <joohoi@users.noreply.github.com>

* Update README.md

Co-Authored-By: Joona Hoikkala <joohoi@users.noreply.github.com>

* Add post_push hooks to update the latest tag

* Create error on build

* Revert "Create error on build"

This reverts commit d578d67130d3a0c4db7756209b0ade52953041a3.

* Update deploy.sh

* Fix deploy.sh with hotfixes versions

* Fix deploy.sh toward certbot version and release branch

* Enable push
2019-07-18 15:45:27 +02:00
Brad Warren
1d1c096067 add readme 2019-06-03 16:04:45 -07:00
Brad Warren
bcffaab602 add LICENSE.txt 2019-06-03 15:59:05 -07:00
526 changed files with 13699 additions and 11928 deletions

View File

@@ -0,0 +1,14 @@
# Advanced pipeline for running our full test suite on demand.
trigger:
# When changing these triggers, please ensure the documentation under
# "Running tests in CI" is still correct.
- test-*
pr: none
variables:
# We don't publish our Docker images in this pipeline, but when building them
# for testing, let's use the nightly tag.
dockerTag: nightly
stages:
- template: templates/stages/test-and-package-stage.yml

View File

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

View File

@@ -1,12 +1,8 @@
trigger:
# apache-parser-v2 is a temporary branch for doing work related to
# rewriting the parser in the Apache plugin.
- apache-parser-v2
- master
trigger: none
pr:
- apache-parser-v2
- master
- '*.x'
jobs:
- template: templates/tests-suite.yml
- template: templates/jobs/standard-tests-jobs.yml

View File

@@ -0,0 +1,18 @@
# Nightly pipeline running each day for master.
trigger: none
pr: none
schedules:
- cron: "30 4 * * *"
displayName: Nightly build
branches:
include:
- master
always: true
variables:
dockerTag: nightly
stages:
- template: templates/stages/test-and-package-stage.yml
- template: templates/stages/deploy-stage.yml
- template: templates/stages/notify-failure-stage.yml

View File

@@ -1,13 +1,18 @@
# Release pipeline to build and deploy Certbot for Windows for GitHub release tags
# Release pipeline to run our full test suite, build artifacts, and deploy them
# for GitHub release tags.
trigger:
tags:
include:
- v*
pr: none
jobs:
# Any addition here should be reflected in the advanced pipeline.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml
- template: templates/changelog.yml
variables:
dockerTag: ${{variables['Build.SourceBranchName']}}
stages:
- template: templates/stages/test-and-package-stage.yml
- template: templates/stages/changelog-stage.yml
- template: templates/stages/deploy-stage.yml
parameters:
snapReleaseChannel: beta
- template: templates/stages/notify-failure-stage.yml

View File

@@ -1,14 +0,0 @@
jobs:
- job: changelog
pool:
vmImage: vs2017-win2016
steps:
- bash: |
CERTBOT_VERSION="$(cd certbot && python -c "import certbot; print(certbot.__version__)" && cd ~-)"
"${BUILD_REPOSITORY_LOCALPATH}\tools\extract_changelog.py" "${CERTBOT_VERSION}" >> "${BUILD_ARTIFACTSTAGINGDIRECTORY}/release_notes.md"
displayName: Prepare changelog
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
artifact: changelog
displayName: Publish changelog

View File

@@ -1,56 +0,0 @@
jobs:
- job: installer_build
pool:
vmImage: vs2017-win2016
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.7
architecture: x86
addToPath: true
- script: python windows-installer/construct.py
displayName: Build Certbot installer
- task: CopyFiles@2
inputs:
sourceFolder: $(System.DefaultWorkingDirectory)/windows-installer/build/nsis
contents: '*.exe'
targetFolder: $(Build.ArtifactStagingDirectory)
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
artifact: windows-installer
displayName: Publish Windows installer
- job: installer_run
dependsOn: installer_build
strategy:
matrix:
win2019:
imageName: windows-2019
win2016:
imageName: vs2017-win2016
win2012r2:
imageName: vs2015-win2012r2
pool:
vmImage: $(imageName)
steps:
- powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe
displayName: Get Python
- script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3
displayName: Install Python
- task: DownloadPipelineArtifact@2
inputs:
artifact: windows-installer
path: $(Build.SourcesDirectory)/bin
displayName: Retrieve Windows installer
- script: |
py -3 -m venv venv
venv\Scripts\python tools\pip_install.py -e certbot-ci
displayName: Prepare Certbot-CI
- script: |
set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH%
venv\Scripts\python -m pytest certbot-ci\windows_installer_integration_tests --allow-persistent-changes --installer-path $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe
displayName: Run windows installer integration tests
- script: |
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

View File

@@ -0,0 +1,20 @@
jobs:
- job: extended_test
variables:
- name: IMAGE_NAME
value: ubuntu-18.04
- name: PYTHON_VERSION
value: 3.9
- group: certbot-common
strategy:
matrix:
macos-farmtest-apache2:
# We run one of these test farm tests on macOS to help ensure the
# tests continue to work on the platform.
IMAGE_NAME: macOS-10.15
PYTHON_VERSION: 3.8
TOXENV: test-farm-apache2
pool:
vmImage: $(IMAGE_NAME)
steps:
- template: ../steps/tox-steps.yml

View File

@@ -0,0 +1,230 @@
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
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
architecture: x86
addToPath: true
- script: |
python -m venv venv
venv\Scripts\python tools\pipstrap.py
venv\Scripts\python tools\pip_install.py -e windows-installer
displayName: Prepare Windows installer build environment
- script: |
venv\Scripts\construct-windows-installer
displayName: Build Certbot installer
- task: CopyFiles@2
inputs:
sourceFolder: $(System.DefaultWorkingDirectory)/windows-installer/build/nsis
contents: '*.exe'
targetFolder: $(Build.ArtifactStagingDirectory)
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
# If we change the artifact's name, it should also be changed in tools/create_github_release.py
artifact: windows-installer
displayName: Publish Windows installer
- job: installer_run
dependsOn: installer_build
strategy:
matrix:
win2019:
imageName: windows-2019
win2016:
imageName: vs2017-win2016
pool:
vmImage: $(imageName)
steps:
- powershell: |
if ($PSVersionTable.PSVersion.Major -ne 5) {
throw "Powershell version is not 5.x"
}
condition: eq(variables['imageName'], 'vs2017-win2016')
displayName: Check Powershell 5.x is used in vs2017-win2016
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:
artifact: windows-installer
path: $(Build.SourcesDirectory)/bin
displayName: Retrieve Windows installer
- script: |
python -m venv venv
venv\Scripts\python tools\pipstrap.py
venv\Scripts\python tools\pip_install.py -e certbot-ci
env:
PIP_NO_BUILD_ISOLATION: no
displayName: Prepare Certbot-CI
- script: |
set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH%
venv\Scripts\python -m pytest certbot-ci\windows_installer_integration_tests --allow-persistent-changes --installer-path $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe
displayName: Run windows installer integration tests
- script: |
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

@@ -0,0 +1,80 @@
jobs:
- job: test
variables:
PYTHON_VERSION: 3.9
strategy:
matrix:
macos-py36:
IMAGE_NAME: macOS-10.15
PYTHON_VERSION: 3.6
TOXENV: py36
macos-py39:
IMAGE_NAME: macOS-10.15
PYTHON_VERSION: 3.9
TOXENV: py39
windows-py36:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.6
TOXENV: py36
windows-py38-cover:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.8
TOXENV: py38-cover
windows-integration-certbot:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.8
TOXENV: integration-certbot
linux-oldest-tests-1:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.6
TOXENV: '{acme,apache,apache-v2,certbot}-oldest'
linux-oldest-tests-2:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.6
TOXENV: '{dns,nginx}-oldest'
linux-py36:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.6
TOXENV: py36
linux-py39-cover:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.9
TOXENV: py39-cover
linux-py39-lint:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.9
TOXENV: lint
linux-py39-mypy:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.9
TOXENV: mypy
linux-integration:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.8
TOXENV: integration
ACME_SERVER: pebble
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
apacheconftest:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.6
TOXENV: apacheconftest-with-pebble
nginxroundtrip:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.6
TOXENV: nginxroundtrip
pool:
vmImage: $(IMAGE_NAME)
steps:
- template: ../steps/tox-steps.yml
- job: test_sphinx_builds
pool:
vmImage: ubuntu-20.04
steps:
- template: ../steps/sphinx-steps.yml

View File

@@ -0,0 +1,19 @@
stages:
- stage: Changelog
jobs:
- job: prepare
pool:
vmImage: vs2017-win2016
steps:
# If we change the output filename from `release_notes.md`, it should also be changed in tools/create_github_release.py
- bash: |
set -e
CERTBOT_VERSION="$(cd certbot && python -c "import certbot; print(certbot.__version__)" && cd ~-)"
"${BUILD_REPOSITORY_LOCALPATH}\tools\extract_changelog.py" "${CERTBOT_VERSION}" >> "${BUILD_ARTIFACTSTAGINGDIRECTORY}/release_notes.md"
displayName: Prepare changelog
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
# If we change the artifact's name, it should also be changed in tools/create_github_release.py
artifact: changelog
displayName: Publish changelog

View File

@@ -0,0 +1,106 @@
parameters:
- name: snapReleaseChannel
type: string
default: edge
values:
- edge
- beta
stages:
- stage: Deploy
jobs:
# This job relies on credentials used to publish the Certbot snaps. This
# credential file was created by running:
#
# snapcraft logout
# snapcraft login (provide the shared snapcraft credentials when prompted)
# snapcraft export-login --channels=beta,edge snapcraft.cfg
#
# Then the file was added as a secure file in Azure pipelines
# with the name snapcraft.cfg by following the instructions at
# https://docs.microsoft.com/en-us/azure/devops/pipelines/library/secure-files?view=azure-devops
# including authorizing the file in all pipelines as described at
# https://docs.microsoft.com/en-us/azure/devops/pipelines/library/secure-files?view=azure-devops#how-do-i-authorize-a-secure-file-for-use-in-all-pipelines.
#
# This file has a maximum lifetime of one year and the current
# file will expire on 2021-07-28 which is also tracked by
# https://github.com/certbot/certbot/issues/7931. The file will
# need to be updated before then to prevent automated deploys
# from breaking.
#
# Revoking these credentials can be done by changing the password of the
# account used to generate the credentials. See
# https://forum.snapcraft.io/t/revoking-exported-credentials/19031 for
# more info.
- job: publish_snap
pool:
vmImage: ubuntu-18.04
variables:
- group: certbot-common
strategy:
matrix:
amd64:
SNAP_ARCH: amd64
arm32v6:
SNAP_ARCH: armhf
arm64v8:
SNAP_ARCH: arm64
steps:
- bash: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends snapd
sudo snap install --classic snapcraft
displayName: Install dependencies
- task: DownloadPipelineArtifact@2
inputs:
artifact: snaps_$(SNAP_ARCH)
path: $(Build.SourcesDirectory)/snap
displayName: Retrieve Certbot snaps
- task: DownloadSecureFile@1
name: snapcraftCfg
inputs:
secureFile: snapcraft.cfg
- bash: |
set -e
snapcraft login --with $(snapcraftCfg.secureFilePath)
for SNAP_FILE in snap/*.snap; do
tools/retry.sh eval snapcraft upload --release=${{ parameters.snapReleaseChannel }} "${SNAP_FILE}"
done
displayName: Publish to Snap store
- job: publish_docker
pool:
vmImage: ubuntu-18.04
strategy:
matrix:
amd64:
DOCKER_ARCH: amd64
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: docker_$(DOCKER_ARCH)
path: $(Build.SourcesDirectory)
displayName: Retrieve Docker images
- bash: set -e && docker load --input $(Build.SourcesDirectory)/images.tar
displayName: Load Docker images
- task: Docker@2
inputs:
command: login
# The credentials used here are for the shared certbotbot account
# on Docker Hub. The credentials are stored in a service account
# which was created by following the instructions at
# https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#sep-docreg.
# The name given to this service account must match the value
# given to containerRegistry below. "Grant access to all
# pipelines" should also be checked. To revoke these
# credentials, we can change the password on the certbotbot
# Docker Hub account or remove the account from the
# Certbot organization on Docker Hub.
containerRegistry: docker-hub
displayName: Login to Docker Hub
- bash: set -e && tools/docker/deploy.sh $(dockerTag) $DOCKER_ARCH
displayName: Deploy the Docker images

View File

@@ -0,0 +1,19 @@
stages:
- stage: On_Failure
jobs:
- job: notify_mattermost
variables:
- group: certbot-common
pool:
vmImage: ubuntu-20.04
steps:
- bash: |
set -e
MESSAGE="\
---\n\
##### Azure Pipeline
*Repo* $(Build.Repository.ID) - *Pipeline* $(Build.DefinitionName) #$(Build.BuildNumber) - *Branch/PR* $(Build.SourceBranchName)\n\
:warning: __Pipeline has failed__: [Link to the build](https://dev.azure.com/$(Build.Repository.ID)/_build/results?buildId=$(Build.BuildId)&view=results)\n\n\
---"
curl -i -X POST --data-urlencode "payload={\"text\":\"${MESSAGE}\"}" "$(MATTERMOST_URL)"
condition: failed()

View File

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

View File

@@ -0,0 +1,23 @@
steps:
- bash: |
FINAL_STATUS=0
declare -a FAILED_BUILDS
python3 -m venv .venv
source .venv/bin/activate
python tools/pipstrap.py
for doc_path in */docs
do
echo ""
echo "##[group]Building $doc_path"
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}"
fi
echo "##[endgroup]"
done
if [[ $FINAL_STATUS -ne 0 ]]; then
echo "##[error]The following builds failed: ${FAILED_BUILDS[*]}"
exit 1
fi
displayName: Build Sphinx Documentation

View File

@@ -0,0 +1,57 @@
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
- bash: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
python-dev \
gcc \
libaugeas0 \
libssl-dev \
libffi-dev \
ca-certificates \
nginx-light \
openssl
sudo systemctl stop nginx
condition: startswith(variables['IMAGE_NAME'], 'ubuntu')
displayName: Install Linux dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: $(PYTHON_VERSION)
addToPath: true
# tools/pip_install.py is used to pin packages to a known working version
# except in tests where the environment variable CERTBOT_NO_PIN is set.
# virtualenv is listed here explicitly to make sure it is upgraded when
# CERTBOT_NO_PIN is set to work around failures we've seen when using an older
# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also
# set, pip updates dependencies it thinks are already satisfied to avoid some
# problems with its lack of real dependency resolution.
- bash: |
set -e
python tools/pipstrap.py
python tools/pip_install.py -I tox virtualenv
displayName: Install runtime dependencies
- task: DownloadSecureFile@1
name: testFarmPem
inputs:
secureFile: azure-test-farm.pem
condition: contains(variables['TOXENV'], 'test-farm')
- bash: |
set -e
export TARGET_BRANCH="`echo "${BUILD_SOURCEBRANCH}" | sed -E 's!refs/(heads|tags)/!!g'`"
[ -z "${SYSTEM_PULLREQUEST_TARGETBRANCH}" ] || export TARGET_BRANCH="${SYSTEM_PULLREQUEST_TARGETBRANCH}"
env
python -m tox
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
AWS_EC2_PEM_FILE: $(testFarmPem.secureFilePath)
displayName: Run tox

View File

@@ -1,52 +0,0 @@
jobs:
- job: test
strategy:
matrix:
macos-py27:
IMAGE_NAME: macOS-10.14
PYTHON_VERSION: 2.7
TOXENV: py27
macos-py38:
IMAGE_NAME: macOS-10.14
PYTHON_VERSION: 3.8
TOXENV: py38
windows-py35:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.5
TOXENV: py35
windows-py37-cover:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.7
TOXENV: py37-cover
windows-integration-certbot:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.7
TOXENV: integration-certbot
PYTEST_ADDOPTS: --numprocesses 4
pool:
vmImage: $(IMAGE_NAME)
variables:
- group: certbot-common
steps:
- bash: brew install augeas
condition: startswith(variables['IMAGE_NAME'], 'macOS')
displayName: Install Augeas
- task: UsePythonVersion@0
inputs:
versionSpec: $(PYTHON_VERSION)
addToPath: true
- script: python tools/pip_install.py -U tox coverage
displayName: Install dependencies
- script: python -m tox
displayName: Run tox
# We do not require codecov report upload to succeed. So to avoid to break the pipeline if
# something goes wrong, each command is suffixed with a command that hides any non zero exit
# codes and echoes an informative message instead.
- bash: |
curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash"
chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash"
./codecov-bash -F windows || echo "Codecov did not collect coverage reports"
condition: in(variables['TOXENV'], 'py37-cover', 'integration-certbot')
env:
CODECOV_TOKEN: $(codecov_token)
displayName: Publish coverage

View File

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

View File

@@ -8,5 +8,4 @@
.git
.tox
venv
venv3
docs

18
.editorconfig Normal file
View File

@@ -0,0 +1,18 @@
# https://editorconfig.org/
root = true
[*]
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.py]
indent_style = space
indent_size = 4
charset = utf-8
max_line_length = 100
[*.yaml]
indent_style = space
indent_size = 2

12
.envrc Normal file
View File

@@ -0,0 +1,12 @@
# This file is just a nicety for developers who use direnv. When you cd under
# the Certbot repo, Certbot's virtual environment will be automatically
# activated and then deactivated when you cd elsewhere. Developers have to have
# direnv set up and run `direnv allow` to allow this file to execute on their
# system. You can find more information at https://direnv.net/.
. venv/bin/activate
# direnv doesn't support modifying PS1 so we unset it to squelch the error
# it'll otherwise print about this being done in the activate script. See
# https://github.com/direnv/direnv/wiki/PS1. If you would like your shell
# prompt to change like it normally does, see
# https://github.com/direnv/direnv/wiki/Python#restoring-the-ps1.
unset PS1

22
.gitignore vendored
View File

@@ -4,13 +4,12 @@
build/
dist*/
/venv*/
/kgs/
/.tox/
/releases*/
/log*
letsencrypt.log
certbot.log
letsencrypt-auto-source/letsencrypt-auto.sig.lzma.base64
poetry.lock
# coverage
.coverage
@@ -31,11 +30,6 @@ tags
# auth --cert-path --chain-path
/*.pem
# letstest
tests/letstest/letest-*/
tests/letstest/*.pem
tests/letstest/venv/
.venv
# pytest cache
@@ -50,3 +44,17 @@ tests/letstest/venv/
.certbot_test_workspace
**/assets/pebble*
**/assets/challtestsrv*
# snap files
.snapcraft
parts
prime
stage
*.snap
snap-constraints.txt
qemu-*
certbot-dns*/certbot-dns*_amd64*.txt
certbot-dns*/certbot-dns*_arm*.txt
/certbot_amd64*.txt
/certbot_arm*.txt
certbot-dns*/snap

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

@@ -8,7 +8,10 @@ jobs=0
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=
# CERTBOT COMMENT
# This is needed for pylint to import linter_plugin.py since
# https://github.com/PyCQA/pylint/pull/3396.
init-hook="import pylint.config, os, sys; sys.path.append(os.path.dirname(pylint.config.PYLINTRC))"
# Profiled execution.
profile=no
@@ -254,7 +257,7 @@ ignore-mixin-members=yes
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=pkg_resources,confargparse,argparse,six.moves,six.moves.urllib
ignored-modules=pkg_resources,confargparse,argparse
# import errors ignored only in 1.4.4
# https://bitbucket.org/logilab/pylint/commits/cd000904c9e2

View File

@@ -1,271 +0,0 @@
language: python
dist: xenial
cache:
directories:
- $HOME/.cache/pip
before_script:
# On Travis, the fastest parallelization for integration tests has proved to be 4.
- 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi'
# Use Travis retry feature for farm tests since they are flaky
- 'if [[ "$TOXENV" == "travis-test-farm"* ]]; then export TRAVIS_RETRY=travis_retry; fi'
- export TOX_TESTENV_PASSENV=TRAVIS
# Only build pushes to the master branch, PRs, and branches beginning with
# `test-`, `travis-test-`, or of the form `digit(s).digit(s).x`. This reduces
# the number of simultaneous Travis runs, which speeds turnaround time on
# review since there is a cap of on the number of simultaneous runs.
branches:
# When changing these branches, please ensure the documentation under
# "Running tests in CI" is still correct.
only:
# apache-parser-v2 is a temporary branch for doing work related to
# rewriting the parser in the Apache plugin.
- apache-parser-v2
- master
- /^\d+\.\d+\.x$/
- /^(travis-)?test-.*$/
# Jobs for the main test suite are always executed (including on PRs) except for pushes on master.
not-on-master: &not-on-master
if: NOT (type = push AND branch = master)
# Jobs for the extended test suite are executed for cron jobs and pushes to
# non-development branches. See the explanation for apache-parser-v2 above.
extended-test-suite: &extended-test-suite
if: type = cron OR (type = push AND branch NOT IN (apache-parser-v2, master))
matrix:
include:
# Main test suite
- python: "2.7"
env: ACME_SERVER=pebble TOXENV=integration
<<: *not-on-master
# This job is always executed, including on master
- python: "2.7"
env: TOXENV=py27-cover FYI="py27 tests + code coverage"
- python: "3.7"
env: TOXENV=lint
<<: *not-on-master
- python: "3.5"
env: TOXENV=mypy
<<: *not-on-master
- python: "2.7"
# Ubuntu Trusty or older must be used because the oldest version of
# cryptography we support cannot be compiled against the version of
# OpenSSL in Xenial or newer.
dist: trusty
env: TOXENV='py27-{acme,apache,apache-v2,certbot,dns,nginx}-oldest'
<<: *not-on-master
- python: "3.5"
env: TOXENV=py35
<<: *not-on-master
- python: "3.8"
env: TOXENV=py38
<<: *not-on-master
- sudo: required
env: TOXENV=apache_compat
services: docker
before_install:
addons:
<<: *not-on-master
- sudo: required
env: TOXENV=le_auto_xenial
services: docker
<<: *not-on-master
- python: "2.7"
env: TOXENV=apacheconftest-with-pebble
<<: *not-on-master
- python: "2.7"
env: TOXENV=nginxroundtrip
<<: *not-on-master
# Extended test suite on cron jobs and pushes to tested branches other than master
- sudo: required
env: TOXENV=nginx_compat
services: docker
before_install:
addons:
<<: *extended-test-suite
- python: "2.7"
env:
- TOXENV=travis-test-farm-apache2
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
<<: *extended-test-suite
- python: "2.7"
env:
- TOXENV=travis-test-farm-leauto-upgrades
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
git:
depth: false # This is needed to have the history to checkout old versions of certbot-auto.
<<: *extended-test-suite
- python: "2.7"
env:
- TOXENV=travis-test-farm-certonly-standalone
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
<<: *extended-test-suite
- python: "2.7"
env:
- TOXENV=travis-test-farm-sdists
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
<<: *extended-test-suite
- python: "3.7"
env: TOXENV=py37 CERTBOT_NO_PIN=1
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration-certbot-oldest
# Ubuntu Trusty or older must be used because the oldest version of
# cryptography we support cannot be compiled against the version of
# OpenSSL in Xenial or newer.
dist: trusty
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration-certbot-oldest
# Ubuntu Trusty or older must be used because the oldest version of
# cryptography we support cannot be compiled against the version of
# OpenSSL in Xenial or newer.
dist: trusty
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration-nginx-oldest
# Ubuntu Trusty or older must be used because the oldest version of
# cryptography we support cannot be compiled against the version of
# OpenSSL in Xenial or newer.
dist: trusty
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration-nginx-oldest
# Ubuntu Trusty or older must be used because the oldest version of
# cryptography we support cannot be compiled against the version of
# OpenSSL in Xenial or newer.
dist: trusty
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.6"
env: TOXENV=py36
<<: *extended-test-suite
- python: "3.7"
env: TOXENV=py37
<<: *extended-test-suite
- python: "3.5"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.5"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.6"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.6"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.8"
env: ACME_SERVER=boulder-v1 TOXENV=integration
<<: *extended-test-suite
- python: "3.8"
env: ACME_SERVER=boulder-v2 TOXENV=integration
<<: *extended-test-suite
- sudo: required
env: TOXENV=le_auto_jessie
services: docker
<<: *extended-test-suite
- sudo: required
env: TOXENV=le_auto_centos6
services: docker
<<: *extended-test-suite
- sudo: required
env: TOXENV=le_auto_oraclelinux6
services: docker
<<: *extended-test-suite
- sudo: required
env: TOXENV=docker_dev
services: docker
addons:
apt:
packages: # don't install nginx and apache
- libaugeas0
<<: *extended-test-suite
# container-based infrastructure
sudo: false
addons:
apt:
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
- python-dev
- gcc
- libaugeas0
- libssl-dev
- libffi-dev
- ca-certificates
# For certbot-nginx integration testing
- nginx-light
- openssl
# tools/pip_install.py is used to pin packages to a known working version
# except in tests where the environment variable CERTBOT_NO_PIN is set.
# virtualenv is listed here explicitly to make sure it is upgraded when
# CERTBOT_NO_PIN is set to work around failures we've seen when using an older
# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also
# set, pip updates dependencies it thinks are already satisfied to avoid some
# problems with its lack of real dependency resolution.
install: 'tools/pip_install.py -I codecov tox virtualenv'
# Most of the time TRAVIS_RETRY is an empty string, and has no effect on the
# script command. It is set only to `travis_retry` during farm tests, in
# order to trigger the Travis retry feature, and compensate the inherent
# flakiness of these specific tests.
script: '$TRAVIS_RETRY tox'
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
notifications:
email: false
irc:
if: NOT branch =~ ^(travis-)?test-.*$
channels:
# This is set to a secure variable to prevent forks from sending
# notifications. This value was created by installing
# https://github.com/travis-ci/travis.rb and running
# `travis encrypt "chat.freenode.net#certbot-devel"`.
- secure: "EWW66E2+KVPZyIPR8ViENZwfcup4Gx3/dlimmAZE0WuLwxDCshBBOd3O8Rf6pBokEoZlXM5eDT6XdyJj8n0DLslgjO62pExdunXpbcMwdY7l1ELxX2/UbnDTE6UnPYa09qVBHNG7156Z6yE0x2lH4M9Ykvp0G0cubjPQHylAwo0="
on_cancel: never
on_success: never
on_failure: always

View File

@@ -1,6 +1,7 @@
Authors
=======
* [Aaron Gable](https://github.com/aarongable)
* [Aaron Zirbes](https://github.com/aaronzirbes)
* Aaron Zuehlke
* Ada Lovelace
@@ -21,6 +22,7 @@ Authors
* [Andrzej Górski](https://github.com/andrzej3393)
* [Anselm Levskaya](https://github.com/levskaya)
* [Antoine Jacoutot](https://github.com/ajacoutot)
* [April King](https://github.com/april)
* [asaph](https://github.com/asaph)
* [Axel Beckert](https://github.com/xtaran)
* [Bas](https://github.com/Mechazawa)
@@ -35,7 +37,8 @@ Authors
* [Blake Griffith](https://github.com/cowlicks)
* [Brad Warren](https://github.com/bmw)
* [Brandon Kraft](https://github.com/kraftbj)
* [Brandon Kreisel](https://github.com/kraftbj)
* [Brandon Kreisel](https://github.com/BKreisel)
* [Brian Heim](https://github.com/brianlheim)
* [Cameron Steel](https://github.com/Tugzrida)
* [Ceesjan Luiten](https://github.com/quinox)
* [Chad Whitacre](https://github.com/whit537)
@@ -58,7 +61,9 @@ Authors
* [DanCld](https://github.com/DanCld)
* [Daniel Albers](https://github.com/AID)
* [Daniel Aleksandersen](https://github.com/da2x)
* [Daniel Almasi](https://github.com/almasen)
* [Daniel Convissor](https://github.com/convissor)
* [Daniel "Drex" Drexler](https://github.com/aeturnum)
* [Daniel Huang](https://github.com/dhuang)
* [Dave Guarino](https://github.com/daguar)
* [David cz](https://github.com/dave-cz)
@@ -83,6 +88,7 @@ Authors
* [Felix Schwarz](https://github.com/FelixSchwarz)
* [Felix Yan](https://github.com/felixonmars)
* [Filip Ochnik](https://github.com/filipochnik)
* [Florian Klink](https://github.com/flokli)
* [Francois Marier](https://github.com/fmarier)
* [Frank](https://github.com/Frankkkkk)
* [Frederic BLANC](https://github.com/fblanc)
@@ -103,6 +109,7 @@ Authors
* [Henry Chen](https://github.com/henrychen95)
* [Hugo van Kemenade](https://github.com/hugovk)
* [Ingolf Becker](https://github.com/watercrossing)
* [Ivan Nejgebauer](https://github.com/inejge)
* [Jaap Eldering](https://github.com/eldering)
* [Jacob Hoffman-Andrews](https://github.com/jsha)
* [Jacob Sachs](https://github.com/jsachs)
@@ -144,11 +151,13 @@ Authors
* [Lior Sabag](https://github.com/liorsbg)
* [Lipis](https://github.com/lipis)
* [lord63](https://github.com/lord63)
* [Lorenzo Fundaró](https://github.com/lfundaro)
* [Luca Beltrame](https://github.com/lbeltrame)
* [Luca Ebach](https://github.com/lucebac)
* [Luca Olivetti](https://github.com/olivluca)
* [Luke Rogers](https://github.com/lukeroge)
* [Maarten](https://github.com/mrtndwrd)
* [Mads Jensen](https://github.com/atombrella)
* [Maikel Martens](https://github.com/krukas)
* [Malte Janduda](https://github.com/MalteJ)
* [Mantas Mikulėnas](https://github.com/grawity)
@@ -200,6 +209,7 @@ Authors
* [Pierre Jaury](https://github.com/kaiyou)
* [Piotr Kasprzyk](https://github.com/kwadrat)
* [Prayag Verma](https://github.com/pra85)
* [Rasesh Patel](https://github.com/raspat1)
* [Reinaldo de Souza Jr](https://github.com/juniorz)
* [Remi Rampin](https://github.com/remram44)
* [Rémy HUBSCHER](https://github.com/Natim)
@@ -207,6 +217,7 @@ Authors
* [Richard Barnes](https://github.com/r-barnes)
* [Richard Panek](https://github.com/kernelpanek)
* [Robert Buchholz](https://github.com/rbu)
* [Robert Dailey](https://github.com/pahrohfit)
* [Robert Habermann](https://github.com/frennkie)
* [Robert Xiao](https://github.com/nneonneo)
* [Roland Shoemaker](https://github.com/rolandshoemaker)
@@ -232,9 +243,11 @@ Authors
* [Spencer Bliven](https://github.com/sbliven)
* [Stacey Sheldon](https://github.com/solidgoldbomb)
* [Stavros Korokithakis](https://github.com/skorokithakis)
* [Ștefan Talpalaru](https://github.com/stefantalpalaru)
* [Stefan Weil](https://github.com/stweil)
* [Steve Desmond](https://github.com/stevedesmond-ca)
* [sydneyli](https://github.com/sydneyli)
* [taixx046](https://github.com/taixx046)
* [Tan Jay Jun](https://github.com/jayjun)
* [Tapple Gao](https://github.com/tapple)
* [Telepenin Nikolay](https://github.com/telepenin)
@@ -266,5 +279,6 @@ Authors
* [Yomna](https://github.com/ynasser)
* [Yoni Jah](https://github.com/yonjah)
* [YourDaddyIsHere](https://github.com/YourDaddyIsHere)
* [Yuseong Cho](https://github.com/g6123)
* [Zach Shepherd](https://github.com/zjs)
* [陈三](https://github.com/chenxsan)

View File

@@ -11,7 +11,7 @@ to the Sphinx generated docs is provided below.
[1] https://github.com/blog/1184-contributing-guidelines
[2] http://docutils.sourceforge.net/docs/user/rst/quickref.html#hyperlink-targets
[2] https://docutils.sourceforge.io/docs/user/rst/quickref.html#hyperlink-targets
-->

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,13 +8,14 @@ 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/* \
/var/tmp/*
RUN VENV_NAME="../venv3" python3 tools/venv3.py
RUN VENV_NAME="../venv" python3 tools/venv.py
ENV PATH /opt/certbot/venv3/bin:$PATH
ENV PATH /opt/certbot/venv/bin:$PATH

View File

@@ -7,7 +7,7 @@ questions.
## My operating system is (include version):
## I installed Certbot with (certbot-auto, OS package manager, pip, etc):
## I installed Certbot with (snap, OS package manager, pip, certbot-auto, etc):
## I ran this command and it produced this output:

View File

@@ -6,7 +6,6 @@ This module is an implementation of the `ACME protocol`_.
"""
import sys
import warnings
# This code exists to keep backwards compatibility with people using acme.jose
# before it became the standalone josepy package.

View File

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

View File

@@ -4,47 +4,38 @@ import collections
import datetime
from email.utils import parsedate_tz
import heapq
import http.client as http_client
import logging
import re
import sys
import time
from typing import cast
from typing import Dict
from typing import List
from typing import Set
from typing import Text
from typing import Union
import josepy as jose
import OpenSSL
import requests
from requests.adapters import HTTPAdapter
from requests.utils import parse_header_links
from requests_toolbelt.adapters.source import SourceAddressAdapter
import six
from six.moves import http_client
from acme import crypto_util
from acme import errors
from acme import jws
from acme import messages
from acme.magic_typing import Dict
from acme.magic_typing import List
from acme.magic_typing import Set
from acme.magic_typing import Text
from acme.mixins import VersionedLEACMEMixin
logger = logging.getLogger(__name__)
# Prior to Python 2.7.9 the stdlib SSL module did not allow a user to configure
# many important security related options. On these platforms we use PyOpenSSL
# for SSL, which does allow these options to be configured.
# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning
if sys.version_info < (2, 7, 9): # pragma: no cover
try:
requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore
except AttributeError:
import urllib3.contrib.pyopenssl
urllib3.contrib.pyopenssl.inject_into_urllib3()
DEFAULT_NETWORK_TIMEOUT = 45
DER_CONTENT_TYPE = 'application/pkix-cert'
class ClientBase(object):
class ClientBase:
"""ACME client base object.
:ivar messages.Directory directory:
@@ -123,8 +114,9 @@ class ClientBase(object):
"""
return self.update_registration(regr, update={'status': 'deactivated'})
def deactivate_authorization(self, authzr):
# type: (messages.AuthorizationResource) -> messages.AuthorizationResource
def deactivate_authorization(self,
authzr: messages.AuthorizationResource
) -> messages.AuthorizationResource:
"""Deactivate authorization.
:param messages.AuthorizationResource authzr: The Authorization resource
@@ -199,7 +191,7 @@ class ClientBase(object):
when = parsedate_tz(retry_after)
if when is not None:
try:
tz_secs = datetime.timedelta(when[-1] if when[-1] else 0)
tz_secs = datetime.timedelta(when[-1] if when[-1] is not None else 0)
return datetime.datetime(*when[:7]) - tz_secs
except (ValueError, OverflowError):
pass
@@ -258,10 +250,10 @@ class Client(ClientBase):
if net is None:
net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
if isinstance(directory, six.string_types):
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):
@@ -434,7 +426,7 @@ class Client(ClientBase):
"""
assert max_attempts > 0
attempts = collections.defaultdict(int) # type: Dict[messages.AuthorizationResource, int]
attempts: Dict[messages.AuthorizationResource, int] = collections.defaultdict(int)
exhausted = set()
# priority queue with datetime.datetime (based on Retry-After) as key,
@@ -446,7 +438,7 @@ class Client(ClientBase):
heapq.heapify(waiting)
# mapping between original Authorization Resource and the most
# recently updated one
updated = dict((authzr, authzr) for authzr in authzrs)
updated = {authzr: authzr for authzr in authzrs}
while waiting:
# find the smallest Retry-After, and sleep if necessary
@@ -473,7 +465,7 @@ class Client(ClientBase):
exhausted.add(authzr)
if exhausted or any(authzr.body.status == messages.STATUS_INVALID
for authzr in six.itervalues(updated)):
for authzr in updated.values()):
raise errors.PollError(exhausted, updated)
updated_authzrs = tuple(updated[authzr] for authzr in authzrs)
@@ -547,7 +539,7 @@ class Client(ClientBase):
:rtype: `list` of `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
"""
chain = [] # type: List[jose.ComparableX509]
chain: List[jose.ComparableX509] = []
uri = certr.cert_chain_uri
while uri is not None and len(chain) < max_length:
response, cert = self._get_cert(uri)
@@ -585,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):
@@ -635,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
@@ -732,11 +724,13 @@ class ClientV2(ClientBase):
raise errors.ValidationError(failed)
return orderr.update(authorizations=responses)
def finalize_order(self, orderr, deadline):
def finalize_order(self, orderr, deadline, fetch_alternative_chains=False):
"""Finalize an order and obtain a certificate.
:param messages.OrderResource orderr: order to finalize
:param datetime.datetime deadline: when to stop polling and timeout
:param bool fetch_alternative_chains: whether to also fetch alternative
certificate chains
:returns: finalized order
:rtype: messages.OrderResource
@@ -753,8 +747,13 @@ class ClientV2(ClientBase):
if body.error is not None:
raise errors.IssuanceError(body.error)
if body.certificate is not None:
certificate_response = self._post_as_get(body.certificate).text
return orderr.update(body=body, fullchain_pem=certificate_response)
certificate_response = self._post_as_get(body.certificate)
orderr = orderr.update(body=body, fullchain_pem=certificate_response.text)
if fetch_alternative_chains:
alt_chains_urls = self._get_links(certificate_response, 'alternate')
alt_chains = [self._post_as_get(url).text for url in alt_chains_urls]
orderr = orderr.update(alternative_fullchains_pem=alt_chains)
return orderr
raise errors.TimeoutError()
def revoke(self, cert, rsn):
@@ -784,8 +783,22 @@ class ClientV2(ClientBase):
new_args = args[:1] + (None,) + args[1:]
return self._post(*new_args, **kwargs)
def _get_links(self, response, relation_type):
"""
Retrieves all Link URIs of relation_type from the response.
:param requests.Response response: The requests HTTP response.
:param str relation_type: The relation type to filter by.
"""
# Can't use response.links directly because it drops multiple links
# of the same relation type, which is possible in RFC8555 responses.
if 'Link' not in response.headers:
return []
links = parse_header_links(response.headers['Link'])
return [l['url'] for l in links
if 'rel' in l and 'url' in l and l['rel'] == relation_type]
class BackwardsCompatibleClientV2(object):
class BackwardsCompatibleClientV2:
"""ACME client wrapper that tends towards V2-style calls, but
supports V1 servers.
@@ -807,6 +820,7 @@ class BackwardsCompatibleClientV2(object):
def __init__(self, net, key, server):
directory = messages.Directory.from_json(net.get(server).json())
self.acme_version = self._acme_version_from_directory(directory)
self.client: Union[Client, ClientV2]
if self.acme_version == 1:
self.client = Client(directory, key=key, net=net)
else:
@@ -826,16 +840,18 @@ class BackwardsCompatibleClientV2(object):
if check_tos_cb is not None:
check_tos_cb(tos)
if self.acme_version == 1:
regr = self.client.register(regr)
client_v1 = cast(Client, self.client)
regr = client_v1.register(regr)
if regr.terms_of_service is not None:
_assess_tos(regr.terms_of_service)
return self.client.agree_to_tos(regr)
return client_v1.agree_to_tos(regr)
return regr
else:
if "terms_of_service" in self.client.directory.meta:
_assess_tos(self.client.directory.meta.terms_of_service)
client_v2 = cast(ClientV2, self.client)
if "terms_of_service" in client_v2.directory.meta:
_assess_tos(client_v2.directory.meta.terms_of_service)
regr = regr.update(terms_of_service_agreed=True)
return self.client.new_account(regr)
return client_v2.new_account(regr)
def new_order(self, csr_pem):
"""Request a new Order object from the server.
@@ -853,28 +869,32 @@ class BackwardsCompatibleClientV2(object):
"""
if self.acme_version == 1:
client_v1 = cast(Client, self.client)
csr = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# pylint: disable=protected-access
dnsNames = crypto_util._pyopenssl_cert_or_req_all_names(csr)
authorizations = []
for domain in dnsNames:
authorizations.append(self.client.request_domain_challenges(domain))
authorizations.append(client_v1.request_domain_challenges(domain))
return messages.OrderResource(authorizations=authorizations, csr_pem=csr_pem)
return self.client.new_order(csr_pem)
return cast(ClientV2, self.client).new_order(csr_pem)
def finalize_order(self, orderr, deadline):
def finalize_order(self, orderr, deadline, fetch_alternative_chains=False):
"""Finalize an order and obtain a certificate.
:param messages.OrderResource orderr: order to finalize
:param datetime.datetime deadline: when to stop polling and timeout
:param bool fetch_alternative_chains: whether to also fetch alternative
certificate chains
:returns: finalized order
:rtype: messages.OrderResource
"""
if self.acme_version == 1:
client_v1 = cast(Client, self.client)
csr_pem = orderr.csr_pem
certr = self.client.request_issuance(
certr = client_v1.request_issuance(
jose.ComparableX509(
OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr_pem)),
orderr.authorizations)
@@ -882,7 +902,7 @@ class BackwardsCompatibleClientV2(object):
chain = None
while datetime.datetime.now() < deadline:
try:
chain = self.client.fetch_chain(certr)
chain = client_v1.fetch_chain(certr)
break
except errors.Error:
time.sleep(1)
@@ -897,7 +917,8 @@ class BackwardsCompatibleClientV2(object):
chain = crypto_util.dump_pyopenssl_chain(chain).decode()
return orderr.update(fullchain_pem=(cert + chain))
return self.client.finalize_order(orderr, deadline)
return cast(ClientV2, self.client).finalize_order(
orderr, deadline, fetch_alternative_chains)
def revoke(self, cert, rsn):
"""Revoke certificate.
@@ -923,10 +944,10 @@ class BackwardsCompatibleClientV2(object):
Always return False for ACMEv1 servers, as it doesn't use External Account Binding."""
if self.acme_version == 1:
return False
return self.client.external_account_required()
return cast(ClientV2, self.client).external_account_required()
class ClientNetwork(object):
class ClientNetwork:
"""Wrapper around requests that signs POSTs for authentication.
Also adds user agent, and handles Content-Type.
@@ -956,7 +977,7 @@ class ClientNetwork(object):
self.account = account
self.alg = alg
self.verify_ssl = verify_ssl
self._nonces = set() # type: Set[Text]
self._nonces: Set[Text] = set()
self.user_agent = user_agent
self.session = requests.Session()
self._default_timeout = timeout
@@ -987,6 +1008,8 @@ class ClientNetwork(object):
:rtype: `josepy.JWS`
"""
if isinstance(obj, VersionedLEACMEMixin):
obj.le_acme_version = acme_version
jobj = obj.json_dumps(indent=2).encode() if obj else b''
logger.debug('JWS payload:\n%s', jobj)
kwargs = {
@@ -1114,14 +1137,15 @@ class ClientNetwork(object):
# If content is DER, log the base64 of it instead of raw bytes, to keep
# binary data out of the logs.
debug_content: Union[bytes, str]
if response.headers.get("Content-Type") == DER_CONTENT_TYPE:
debug_content = base64.b64encode(response.content)
else:
debug_content = response.content.decode("utf-8")
logger.debug('Received response:\nHTTP %d\n%s\n\n%s',
response.status_code,
"\n".join(["{0}: {1}".format(k, v)
for k, v in response.headers.items()]),
"\n".join("{0}: {1}".format(k, v)
for k, v in response.headers.items()),
debug_content)
return response

View File

@@ -5,15 +5,15 @@ import logging
import os
import re
import socket
from typing import Callable
from typing import Tuple
from typing import Union
import josepy as jose
from OpenSSL import crypto
from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
from acme import errors
from acme.magic_typing import Callable
from acme.magic_typing import Tuple
from acme.magic_typing import Union
logger = logging.getLogger(__name__)
@@ -27,19 +27,41 @@ logger = logging.getLogger(__name__)
_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
class SSLSocket(object):
class _DefaultCertSelection:
def __init__(self, certs):
self.certs = certs
def __call__(self, connection):
server_name = connection.get_servername()
return self.certs.get(server_name, None)
class SSLSocket: # pylint: disable=too-few-public-methods
"""SSL wrapper for sockets.
:ivar socket sock: Original wrapped socket.
:ivar dict certs: Mapping from domain names (`bytes`) to
`OpenSSL.crypto.X509`.
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
:ivar alpn_selection: Hook to select negotiated ALPN protocol for
connection.
:ivar cert_selection: Hook to select certificate for connection. If given,
`certs` parameter would be ignored, and therefore must be empty.
"""
def __init__(self, sock, certs, method=_DEFAULT_SSL_METHOD):
def __init__(self, sock, certs=None,
method=_DEFAULT_SSL_METHOD, alpn_selection=None,
cert_selection=None):
self.sock = sock
self.certs = certs
self.alpn_selection = alpn_selection
self.method = method
if not cert_selection and not certs:
raise ValueError("Neither cert_selection or certs specified.")
if cert_selection and certs:
raise ValueError("Both cert_selection and certs specified.")
if cert_selection is None:
cert_selection = _DefaultCertSelection(certs)
self.cert_selection = cert_selection
def __getattr__(self, name):
return getattr(self.sock, name)
@@ -56,21 +78,22 @@ class SSLSocket(object):
:type connection: :class:`OpenSSL.Connection`
"""
server_name = connection.get_servername()
try:
key, cert = self.certs[server_name]
except KeyError:
logger.debug("Server name (%s) not recognized, dropping SSL",
server_name)
pair = self.cert_selection(connection)
if pair is None:
logger.debug("Certificate selection for server name %s failed, dropping SSL",
connection.get_servername())
return
key, cert = pair
new_context = SSL.Context(self.method)
new_context.set_options(SSL.OP_NO_SSLv2)
new_context.set_options(SSL.OP_NO_SSLv3)
new_context.use_privatekey(key)
new_context.use_certificate(cert)
if self.alpn_selection is not None:
new_context.set_alpn_select_callback(self.alpn_selection)
connection.set_context(new_context)
class FakeConnection(object):
class FakeConnection:
"""Fake OpenSSL.SSL.Connection."""
# pylint: disable=missing-function-docstring
@@ -92,6 +115,8 @@ class SSLSocket(object):
context.set_options(SSL.OP_NO_SSLv2)
context.set_options(SSL.OP_NO_SSLv3)
context.set_tlsext_servername_callback(self._pick_certificate_cb)
if self.alpn_selection is not None:
context.set_alpn_select_callback(self.alpn_selection)
ssl_sock = self.FakeConnection(SSL.Connection(context, sock))
ssl_sock.set_accept_state()
@@ -107,8 +132,9 @@ class SSLSocket(object):
return ssl_sock, addr
def probe_sni(name, host, port=443, timeout=300,
method=_DEFAULT_SSL_METHOD, source_address=('', 0)):
def probe_sni(name, host, port=443, timeout=300, # pylint: disable=too-many-arguments
method=_DEFAULT_SSL_METHOD, source_address=('', 0),
alpn_protocols=None):
"""Probe SNI server for SSL certificate.
:param bytes name: Byte string to send as the server name in the
@@ -120,6 +146,8 @@ def probe_sni(name, host, port=443, timeout=300,
:param tuple source_address: Enables multi-path probing (selection
of source interface). See `socket.creation_connection` for more
info. Available only in Python 2.7+.
:param alpn_protocols: Protocols to request using ALPN.
:type alpn_protocols: `list` of `bytes`
:raises acme.errors.Error: In case of any problems.
@@ -138,9 +166,9 @@ def probe_sni(name, host, port=443, timeout=300,
" from {0}:{1}".format(
source_address[0],
source_address[1]
) if socket_kwargs else ""
) if any(source_address) else ""
)
socket_tuple = (host, port) # type: Tuple[str, int]
socket_tuple: Tuple[str, int] = (host, port)
sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore
except socket.error as error:
raise errors.Error(error)
@@ -149,6 +177,8 @@ def probe_sni(name, host, port=443, timeout=300,
client_ssl = SSL.Connection(context, client)
client_ssl.set_connect_state()
client_ssl.set_tlsext_host_name(name) # pyOpenSSL>=0.13
if alpn_protocols is not None:
client_ssl.set_alpn_protos(alpn_protocols)
try:
client_ssl.do_handshake()
client_ssl.shutdown()
@@ -156,6 +186,7 @@ def probe_sni(name, host, port=443, timeout=300,
raise errors.Error(error)
return client_ssl.get_peer_certificate()
def make_csr(private_key_pem, domains, must_staple=False):
"""Generate a CSR containing a list of domains as subjectAltNames.
@@ -187,6 +218,7 @@ def make_csr(private_key_pem, domains, must_staple=False):
return crypto.dump_certificate_request(
crypto.FILETYPE_PEM, csr)
def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req):
common_name = loaded_cert_or_req.get_subject().CN
sans = _pyopenssl_cert_or_req_san(loaded_cert_or_req)
@@ -195,6 +227,7 @@ def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req):
return sans
return [common_name] + [d for d in sans if d != common_name]
def _pyopenssl_cert_or_req_san(cert_or_req):
"""Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
@@ -223,7 +256,7 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
if isinstance(cert_or_req, crypto.X509):
# pylint: disable=line-too-long
func = crypto.dump_certificate # type: Union[Callable[[int, crypto.X509Req], bytes], Callable[[int, crypto.X509], bytes]]
func: Union[Callable[[int, crypto.X509Req], bytes], Callable[[int, crypto.X509], bytes]] = crypto.dump_certificate
else:
func = crypto.dump_certificate_request
text = func(crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
@@ -239,12 +272,14 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
def gen_ss_cert(key, domains, not_before=None,
validity=(7 * 24 * 60 * 60), force_san=True):
validity=(7 * 24 * 60 * 60), force_san=True, extensions=None):
"""Generate new self-signed certificate.
:type domains: `list` of `unicode`
:param OpenSSL.crypto.PKey key:
:param bool force_san:
:param extensions: List of additional extensions to include in the cert.
:type extensions: `list` of `OpenSSL.crypto.X509Extension`
If more than one domain is provided, all of the domains are put into
``subjectAltName`` X.509 extension and first domain is set as the
@@ -257,10 +292,13 @@ def gen_ss_cert(key, domains, not_before=None,
cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16))
cert.set_version(2)
extensions = [
if extensions is None:
extensions = []
extensions.append(
crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
]
)
cert.get_subject().CN = domains[0]
# TODO: what to put into cert.get_subject()?
@@ -282,6 +320,7 @@ def gen_ss_cert(key, domains, not_before=None,
cert.sign(key, "sha256")
return cert
def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle.

View File

@@ -28,13 +28,8 @@ class NonceError(ClientError):
class BadNonce(NonceError):
"""Bad nonce error."""
def __init__(self, nonce, error, *args, **kwargs):
# MyPy complains here that there is too many arguments for BaseException constructor.
# This is an error fixed in typeshed, see https://github.com/python/mypy/issues/4183
# The fix is included in MyPy>=0.740, but upgrading it would bring dozen of errors due to
# new types definitions. So we ignore the error until the code base is fixed to match
# with MyPy>=0.740 referential.
super(BadNonce, self).__init__(*args, **kwargs) # type: ignore
def __init__(self, nonce, error, *args):
super().__init__(*args)
self.nonce = nonce
self.error = error
@@ -49,12 +44,11 @@ class MissingNonce(NonceError):
Replay-Nonce header field in each successful response to a POST it
provides to a client (...)".
:ivar requests.Response response: HTTP Response
:ivar requests.Response ~.response: HTTP Response
"""
def __init__(self, response, *args, **kwargs):
# See comment in BadNonce constructor above for an explanation of type: ignore here.
super(MissingNonce, self).__init__(*args, **kwargs) # type: ignore
def __init__(self, response, *args):
super().__init__(*args)
self.response = response
def __str__(self):
@@ -78,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):
@@ -96,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
@@ -112,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):
@@ -125,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

@@ -14,7 +14,9 @@ class Header(jose.Header):
kid = jose.Field('kid', omitempty=True)
url = jose.Field('url', omitempty=True)
@nonce.decoder
# Mypy does not understand the josepy magic happening here, and falsely claims
# that nonce is redefined. Let's ignore the type check here.
@nonce.decoder # type: ignore
def nonce(value): # pylint: disable=no-self-argument,missing-function-docstring
try:
return jose.decode_b64jose(value)
@@ -48,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

@@ -1,16 +1,17 @@
"""Shim class to not have to depend on typing module in prod."""
import sys
"""Simple shim around the typing module.
This was useful when this code supported Python 2 and typing wasn't always
available. This code is being kept for now for backwards compatibility.
class TypingClass(object):
"""
import warnings
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
from typing import Collection, IO # type: ignore
warnings.warn("acme.magic_typing is deprecated and will be removed in a future release.",
DeprecationWarning)
class TypingClass:
"""Ignore import errors by getting anything"""
def __getattr__(self, name):
return None
try:
# mypy doesn't respect modifying sys.modules
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
from typing import Collection, IO # type: ignore
# pylint: enable=unused-import
except ImportError:
sys.modules[__name__] = TypingClass()
return None # pragma: no cover

View File

@@ -1,21 +1,18 @@
"""ACME protocol messages."""
from collections.abc import Hashable
import json
from typing import Any
from typing import Dict
from typing import Type
import josepy as jose
import six
from acme import challenges
from acme import errors
from acme import fields
from acme import jws
from acme import util
try:
from collections.abc import Hashable
except ImportError: # pragma: no cover
from collections import Hashable
from acme.mixins import ResourceMixin
OLD_ERROR_PREFIX = "urn:acme:error:"
ERROR_PREFIX = "urn:ietf:params:acme:error:"
@@ -67,7 +64,6 @@ def is_acme_error(err):
return False
@six.python_2_unicode_compatible
class Error(jose.JSONObjectWithFields, errors.Error):
"""ACME error.
@@ -94,7 +90,9 @@ class Error(jose.JSONObjectWithFields, errors.Error):
raise ValueError("The supplied code: %s is not a known ACME error"
" code" % code)
typ = ERROR_PREFIX + code
return cls(typ=typ, **kwargs)
# Mypy will not understand that the Error constructor accepts a named argument
# "typ" because of josepy magic. Let's ignore the type check here.
return cls(typ=typ, **kwargs) # type: ignore
@property
def description(self):
@@ -131,10 +129,10 @@ class Error(jose.JSONObjectWithFields, errors.Error):
class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore
"""ACME constant."""
__slots__ = ('name',)
POSSIBLE_NAMES = NotImplemented
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
@@ -157,13 +155,10 @@ class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore
def __hash__(self):
return hash((self.__class__, self.name))
def __ne__(self, other):
return not self == other
class Status(_Constant):
"""ACME "status" field."""
POSSIBLE_NAMES = {} # type: dict
POSSIBLE_NAMES: dict = {}
STATUS_UNKNOWN = Status('unknown')
STATUS_PENDING = Status('pending')
STATUS_PROCESSING = Status('processing')
@@ -176,7 +171,7 @@ STATUS_DEACTIVATED = Status('deactivated')
class IdentifierType(_Constant):
"""ACME identifier type."""
POSSIBLE_NAMES = {} # type: dict
POSSIBLE_NAMES: Dict[str, 'IdentifierType'] = {}
IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder
@@ -194,7 +189,7 @@ class Identifier(jose.JSONObjectWithFields):
class Directory(jose.JSONDeSerializable):
"""Directory."""
_REGISTERED_TYPES = {} # type: dict
_REGISTERED_TYPES: Dict[str, Type[Any]] = {}
class Meta(jose.JSONObjectWithFields):
"""Directory Meta."""
@@ -205,8 +200,8 @@ class Directory(jose.JSONDeSerializable):
external_account_required = jose.Field('externalAccountRequired', omitempty=True)
def __init__(self, **kwargs):
kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
super(Directory.Meta, self).__init__(**kwargs)
kwargs = {self._internal_name(k): v for k, v in kwargs.items()}
super().__init__(**kwargs)
@property
def terms_of_service(self):
@@ -216,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):
@@ -228,7 +223,7 @@ class Directory(jose.JSONDeSerializable):
return getattr(key, 'resource_type', key)
@classmethod
def register(cls, resource_body_cls):
def register(cls, resource_body_cls: Type[Any]) -> Type[Any]:
"""Register resource."""
resource_type = resource_body_cls.resource_type
assert resource_type not in cls._REGISTERED_TYPES
@@ -274,7 +269,7 @@ class Resource(jose.JSONObjectWithFields):
class ResourceWithURI(Resource):
"""ACME Resource with URI.
:ivar unicode uri: Location of the resource.
:ivar unicode ~.uri: Location of the resource.
"""
uri = jose.Field('uri') # no ChallengeResource.uri
@@ -284,7 +279,7 @@ class ResourceBody(jose.JSONObjectWithFields):
"""ACME Resource Body."""
class ExternalAccountBinding(object):
class ExternalAccountBinding:
"""ACME External Account Binding"""
@classmethod
@@ -314,6 +309,9 @@ class Registration(ResourceBody):
# on new-reg key server ignores 'key' and populates it based on
# JWS.signature.combined.jwk
key = jose.Field('key', omitempty=True, decoder=jose.JWK.from_json)
# Contact field implements special behavior to allow messages that clear existing
# contacts while not expecting the `contact` field when loading from json.
# This is implemented in the constructor and *_json methods.
contact = jose.Field('contact', omitempty=True, default=())
agreement = jose.Field('agreement', omitempty=True)
status = jose.Field('status', omitempty=True)
@@ -326,24 +324,73 @@ class Registration(ResourceBody):
@classmethod
def from_data(cls, phone=None, email=None, external_account_binding=None, **kwargs):
"""Create registration resource from contact details."""
"""
Create registration resource from contact details.
The `contact` keyword being passed to a Registration object is meaningful, so
this function represents empty iterables in its kwargs by passing on an empty
`tuple`.
"""
# Note if `contact` was in kwargs.
contact_provided = 'contact' in kwargs
# Pop `contact` from kwargs and add formatted email or phone numbers
details = list(kwargs.pop('contact', ()))
if phone is not None:
details.append(cls.phone_prefix + phone)
if email is not None:
details.extend([cls.email_prefix + mail for mail in email.split(',')])
kwargs['contact'] = tuple(details)
# Insert formatted contact information back into kwargs
# or insert an empty tuple if `contact` provided.
if details or contact_provided:
kwargs['contact'] = tuple(details)
if external_account_binding:
kwargs['external_account_binding'] = external_account_binding
return cls(**kwargs)
def __init__(self, **kwargs):
"""Note if the user provides a value for the `contact` member."""
if 'contact' in kwargs:
# Avoid the __setattr__ used by jose.TypedJSONObjectWithFields
object.__setattr__(self, '_add_contact', True)
super().__init__(**kwargs)
def _filter_contact(self, prefix):
return tuple(
detail[len(prefix):] for detail in self.contact # pylint: disable=not-an-iterable
if detail.startswith(prefix))
def _add_contact_if_appropriate(self, jobj):
"""
The `contact` member of Registration objects should not be required when
de-serializing (as it would be if the Fields' `omitempty` flag were `False`), but
it should be included in serializations if it was provided.
:param jobj: Dictionary containing this Registrations' data
:type jobj: dict
:returns: Dictionary containing Registrations data to transmit to the server
:rtype: dict
"""
if getattr(self, '_add_contact', False):
jobj['contact'] = self.encode('contact')
return jobj
def to_partial_json(self):
"""Modify josepy.JSONDeserializable.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().fields_to_partial_json()
return self._add_contact_if_appropriate(jobj)
@property
def phones(self):
"""All phones found in the ``contact`` field."""
@@ -356,13 +403,13 @@ class Registration(ResourceBody):
@Directory.register
class NewRegistration(Registration):
class NewRegistration(ResourceMixin, Registration):
"""New registration."""
resource_type = 'new-reg'
resource = fields.Resource(resource_type)
class UpdateRegistration(Registration):
class UpdateRegistration(ResourceMixin, Registration):
"""Update registration."""
resource_type = 'reg'
resource = fields.Resource(resource_type)
@@ -412,20 +459,20 @@ class ChallengeBody(ResourceBody):
omitempty=True, default=None)
def __init__(self, **kwargs):
kwargs = dict((self._internal_name(k), v) for k, v in kwargs.items())
super(ChallengeBody, self).__init__(**kwargs)
kwargs = {self._internal_name(k): v for k, v in kwargs.items()}
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
@@ -440,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):
@@ -486,7 +533,9 @@ class Authorization(ResourceBody):
expires = fields.RFC3339Field('expires', omitempty=True)
wildcard = jose.Field('wildcard', omitempty=True)
@challenges.decoder
# Mypy does not understand the josepy magic happening here, and falsely claims
# that challenge is redefined. Let's ignore the type check here.
@challenges.decoder # type: ignore
def challenges(value): # pylint: disable=no-self-argument,missing-function-docstring
return tuple(ChallengeBody.from_json(chall) for chall in value)
@@ -498,13 +547,13 @@ class Authorization(ResourceBody):
@Directory.register
class NewAuthorization(Authorization):
class NewAuthorization(ResourceMixin, Authorization):
"""New authorization."""
resource_type = 'new-authz'
resource = fields.Resource(resource_type)
class UpdateAuthorization(Authorization):
class UpdateAuthorization(ResourceMixin, Authorization):
"""Update authorization."""
resource_type = 'authz'
resource = fields.Resource(resource_type)
@@ -522,7 +571,7 @@ class AuthorizationResource(ResourceWithURI):
@Directory.register
class CertificateRequest(jose.JSONObjectWithFields):
class CertificateRequest(ResourceMixin, jose.JSONObjectWithFields):
"""ACME new-cert request.
:ivar josepy.util.ComparableX509 csr:
@@ -548,7 +597,7 @@ class CertificateResource(ResourceWithURI):
@Directory.register
class Revocation(jose.JSONObjectWithFields):
class Revocation(ResourceMixin, jose.JSONObjectWithFields):
"""Revocation message.
:ivar .ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
@@ -565,14 +614,16 @@ class Revocation(jose.JSONObjectWithFields):
class Order(ResourceBody):
"""Order Resource Body.
:ivar list of .Identifier: List of identifiers for the certificate.
:ivar identifiers: List of identifiers for the certificate.
:vartype identifiers: `list` of `.Identifier`
:ivar acme.messages.Status status:
:ivar list of str authorizations: URLs of authorizations.
:ivar authorizations: URLs of authorizations.
:vartype authorizations: `list` of `str`
:ivar str certificate: URL to download certificate as a fullchain PEM.
:ivar str finalize: URL to POST to to request issuance once all
authorizations have "valid" status.
:ivar datetime.datetime expires: When the order expires.
:ivar .Error error: Any error that occurred during finalization, if applicable.
:ivar ~.Error error: Any error that occurred during finalization, if applicable.
"""
identifiers = jose.Field('identifiers', omitempty=True)
status = jose.Field('status', decoder=Status.from_json,
@@ -583,7 +634,9 @@ class Order(ResourceBody):
expires = fields.RFC3339Field('expires', omitempty=True)
error = jose.Field('error', omitempty=True, decoder=Error.from_json)
@identifiers.decoder
# Mypy does not understand the josepy magic happening here, and falsely claims
# that identifiers is redefined. Let's ignore the type check here.
@identifiers.decoder # type: ignore
def identifiers(value): # pylint: disable=no-self-argument,missing-function-docstring
return tuple(Identifier.from_json(identifier) for identifier in value)
@@ -592,15 +645,20 @@ class OrderResource(ResourceWithURI):
:ivar acme.messages.Order body:
:ivar str csr_pem: The CSR this Order will be finalized with.
:ivar list of acme.messages.AuthorizationResource authorizations:
Fully-fetched AuthorizationResource objects.
:ivar authorizations: Fully-fetched AuthorizationResource objects.
:vartype authorizations: `list` of `acme.messages.AuthorizationResource`
:ivar str fullchain_pem: The fetched contents of the certificate URL
produced once the order was finalized, if it's present.
:ivar alternative_fullchains_pem: The fetched contents of alternative certificate
chain URLs produced once the order was finalized, if present and requested during
finalization.
:vartype alternative_fullchains_pem: `list` of `str`
"""
body = jose.Field('body', decoder=Order.from_json)
csr_pem = jose.Field('csr_pem', omitempty=True)
authorizations = jose.Field('authorizations')
fullchain_pem = jose.Field('fullchain_pem', omitempty=True)
alternative_fullchains_pem = jose.Field('alternative_fullchains_pem', omitempty=True)
@Directory.register
class NewOrder(Order):

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

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

View File

@@ -1,17 +1,16 @@
"""Support for standalone client challenge solvers. """
import collections
import functools
import http.client as http_client
import http.server as BaseHTTPServer
import logging
import socket
import socketserver
import threading
from six.moves import BaseHTTPServer # type: ignore
from six.moves import http_client
from six.moves import socketserver # type: ignore
from typing import List
from acme import challenges
from acme import crypto_util
from acme.magic_typing import List
logger = logging.getLogger(__name__)
@@ -33,7 +32,14 @@ class TLSServer(socketserver.TCPServer):
def _wrap_sock(self):
self.socket = crypto_util.SSLSocket(
self.socket, certs=self.certs, method=self.method)
self.socket, cert_selection=self._cert_selection,
alpn_selection=getattr(self, '_alpn_selection', None),
method=self.method)
def _cert_selection(self, connection): # pragma: no cover
"""Callback selecting certificate for connection."""
server_name = connection.get_servername()
return self.certs.get(server_name, None)
def server_bind(self):
self._wrap_sock()
@@ -47,7 +53,7 @@ class ACMEServerMixin:
allow_reuse_address = True
class BaseDualNetworkedServers(object):
class BaseDualNetworkedServers:
"""Base class for a pair of IPv6 and IPv4 servers that tries to do everything
it's asked for both servers, but where failures in one server don't
affect the other.
@@ -57,8 +63,8 @@ class BaseDualNetworkedServers(object):
def __init__(self, ServerClass, server_address, *remaining_args, **kwargs):
port = server_address[1]
self.threads = [] # type: List[threading.Thread]
self.servers = [] # type: List[ACMEServerMixin]
self.threads: List[threading.Thread] = []
self.servers: List[socketserver.BaseServer] = []
# Must try True first.
# Ubuntu, for example, will fail to bind to IPv4 if we've already bound
@@ -120,6 +126,40 @@ class BaseDualNetworkedServers(object):
self.threads = []
class TLSALPN01Server(TLSServer, ACMEServerMixin):
"""TLSALPN01 Server."""
ACME_TLS_1_PROTOCOL = b"acme-tls/1"
def __init__(self, server_address, certs, challenge_certs, ipv6=False):
TLSServer.__init__(
self, server_address, _BaseRequestHandlerWithLogging, certs=certs,
ipv6=ipv6)
self.challenge_certs = challenge_certs
def _cert_selection(self, connection):
# TODO: We would like to serve challenge cert only if asked for it via
# ALPN. To do this, we need to retrieve the list of protos from client
# hello, but this is currently impossible with openssl [0], and ALPN
# negotiation is done after cert selection.
# Therefore, currently we always return challenge cert, and terminate
# handshake in alpn_selection() if ALPN protos are not what we expect.
# [0] https://github.com/openssl/openssl/issues/4952
server_name = connection.get_servername()
logger.debug("Serving challenge cert for server name %s", server_name)
return self.challenge_certs.get(server_name, None)
def _alpn_selection(self, _connection, alpn_protos):
"""Callback to select alpn protocol."""
if len(alpn_protos) == 1 and alpn_protos[0] == self.ACME_TLS_1_PROTOCOL:
logger.debug("Agreed on %s ALPN", self.ACME_TLS_1_PROTOCOL)
return self.ACME_TLS_1_PROTOCOL
logger.debug("Cannot agree on ALPN proto. Got: %s", str(alpn_protos))
# Explicitly close the connection now, by returning an empty string.
# See https://www.pyopenssl.org/en/stable/api/ssl.html#OpenSSL.SSL.Context.set_alpn_select_callback # pylint: disable=line-too-long
return b""
class HTTPServer(BaseHTTPServer.HTTPServer):
"""Generic HTTP Server."""
@@ -135,10 +175,10 @@ class HTTPServer(BaseHTTPServer.HTTPServer):
class HTTP01Server(HTTPServer, ACMEServerMixin):
"""HTTP01 Server."""
def __init__(self, server_address, resources, ipv6=False):
def __init__(self, server_address, resources, ipv6=False, timeout=30):
HTTPServer.__init__(
self, server_address, HTTP01RequestHandler.partial_init(
simple_http_resources=resources), ipv6=ipv6)
simple_http_resources=resources, timeout=timeout), ipv6=ipv6)
class HTTP01DualNetworkedServers(BaseDualNetworkedServers):
@@ -163,7 +203,24 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.simple_http_resources = kwargs.pop("simple_http_resources", set())
self._timeout = kwargs.pop('timeout', 30)
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
self.server: HTTP01Server
# In parent class BaseHTTPRequestHandler, 'timeout' is a class-level property but we
# need to define its value during the initialization phase in HTTP01RequestHandler.
# However MyPy does not appreciate that we dynamically shadow a class-level property
# with an instance-level property (eg. self.timeout = ... in __init__()). So to make
# everyone happy, we statically redefine 'timeout' as a method property, and set the
# timeout value in a new internal instance-level property _timeout.
@property
def timeout(self):
"""
The default timeout this server should apply to requests.
:return: timeout to apply
:rtype: int
"""
return self._timeout
def log_message(self, format, *args): # pylint: disable=redefined-builtin
"""Log arbitrary message."""
@@ -212,7 +269,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
self.path)
@classmethod
def partial_init(cls, simple_http_resources):
def partial_init(cls, simple_http_resources, timeout):
"""Partially initialize this handler.
This is useful because `socketserver.BaseServer` takes
@@ -221,4 +278,18 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
"""
return functools.partial(
cls, simple_http_resources=simple_http_resources)
cls, simple_http_resources=simple_http_resources,
timeout=timeout)
class _BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler):
"""BaseRequestHandler with logging."""
def log_message(self, format, *args): # pylint: disable=redefined-builtin
"""Log arbitrary message."""
logger.debug("%s - - %s", self.client_address[0], format % args)
def handle(self):
"""Handle request."""
self.log_message("Incoming request")
socketserver.BaseRequestHandler.handle(self)

View File

@@ -1,7 +1,6 @@
"""ACME utilities."""
import six
def map_keys(dikt, func):
"""Map dictionary keys."""
return dict((func(key), value) for key, value in six.iteritems(dikt))
return {func(key): value for key, value in dikt.items()}

View File

@@ -9,7 +9,7 @@ BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from https://www.sphinx-doc.org/)
endif
# Internal variables.

View File

@@ -13,7 +13,6 @@
# serve to show the default.
import os
import shlex
import sys
here = os.path.abspath(os.path.dirname(__file__))
@@ -86,7 +85,9 @@ language = 'en'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
exclude_patterns = [
'_build',
]
# The reST default role (used for this markup: `text`) to use for all
# documents.
@@ -121,7 +122,7 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs
# https://docs.readthedocs.io/en/stable/faq.html#i-want-to-use-the-read-the-docs-theme-locally
# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally

View File

@@ -65,7 +65,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
echo.https://www.sphinx-doc.org/
exit /b 1
)

View File

@@ -1 +1,3 @@
:orphan:
.. literalinclude:: ../jws-help.txt

View File

@@ -2,28 +2,22 @@ import sys
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.16.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
# load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8)
'cryptography>=1.2.3',
'cryptography>=2.1.4',
# formerly known as acme.jose:
# 1.1.0+ is required to avoid the warnings described at
# https://github.com/certbot/josepy/issues/13.
'josepy>=1.1.0',
'mock',
# Connection.set_tlsext_host_name (>=0.13)
'PyOpenSSL>=0.13.1',
'PyOpenSSL>=17.3.0',
'pyrfc3339',
'pytz',
'requests[security]>=2.6.0', # security extras added in 2.4.1
'requests>=2.6.0',
'requests-toolbelt>=0.3.0',
'setuptools',
'six>=1.9.0', # needed for python_2_unicode_compatible
'setuptools>=39.0.1',
]
dev_extras = [
@@ -37,43 +31,25 @@ docs_extras = [
'sphinx_rtd_theme',
]
class PyTest(TestCommand):
user_options = []
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ''
def run_tests(self):
import shlex
# import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(shlex.split(self.pytest_args))
sys.exit(errno)
setup(
name='acme',
version=version,
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='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
python_requires='>=3.6',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],
@@ -85,7 +61,4 @@ setup(
'dev': dev_extras,
'docs': docs_extras,
},
test_suite='acme',
tests_require=["pytest"],
cmdclass={"test": PyTest},
)

View File

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

View File

@@ -2,20 +2,21 @@
# pylint: disable=too-many-lines
import copy
import datetime
import http.client as http_client
import json
import unittest
from typing import Dict
from unittest import mock
import josepy as jose
import mock
import OpenSSL
import requests
from six.moves import http_client # pylint: disable=import-error
from acme import challenges
from acme import errors
from acme import jws as acme_jws
from acme import messages
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
from acme.mixins import VersionedLEACMEMixin
import messages_test
import test_util
@@ -61,7 +62,7 @@ class ClientTestBase(unittest.TestCase):
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
reg = messages.Registration(
contact=self.contact, key=KEY.public_key())
the_arg = dict(reg) # type: Dict
the_arg: Dict = dict(reg)
self.new_reg = messages.NewRegistration(**the_arg)
self.regr = messages.RegistrationResource(
body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1')
@@ -89,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)
@@ -260,7 +261,7 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
with mock.patch('acme.client.ClientV2') as mock_client:
client = self._init()
client.finalize_order(mock_orderr, mock_deadline)
mock_client().finalize_order.assert_called_once_with(mock_orderr, mock_deadline)
mock_client().finalize_order.assert_called_once_with(mock_orderr, mock_deadline, False)
def test_revoke(self):
self.response.json.return_value = DIRECTORY_V1.to_json()
@@ -318,7 +319,7 @@ class ClientTest(ClientTestBase):
"""Tests for acme.client.Client."""
def setUp(self):
super(ClientTest, self).setUp()
super().setUp()
self.directory = DIRECTORY_V1
@@ -603,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, [
@@ -640,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
@@ -699,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):
@@ -715,7 +716,7 @@ class ClientV2Test(ClientTestBase):
"""Tests for acme.client.ClientV2."""
def setUp(self):
super(ClientV2Test, self).setUp()
super().setUp()
self.directory = DIRECTORY_V2
@@ -839,6 +840,32 @@ class ClientV2Test(ClientTestBase):
deadline = datetime.datetime.now() - datetime.timedelta(seconds=60)
self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline)
def test_finalize_order_alt_chains(self):
updated_order = self.order.update(
certificate='https://www.letsencrypt-demo.org/acme/cert/',
)
updated_orderr = self.orderr.update(body=updated_order,
fullchain_pem=CERT_SAN_PEM,
alternative_fullchains_pem=[CERT_SAN_PEM,
CERT_SAN_PEM])
self.response.json.return_value = updated_order.to_json()
self.response.text = CERT_SAN_PEM
self.response.headers['Link'] ='<https://example.com/acme/cert/1>;rel="alternate", ' + \
'<https://example.com/dir>;rel="index", ' + \
'<https://example.com/acme/cert/2>;title="foo";rel="alternate"'
deadline = datetime.datetime(9999, 9, 9)
resp = self.client.finalize_order(self.orderr, deadline, fetch_alternative_chains=True)
self.net.post.assert_any_call('https://example.com/acme/cert/1',
mock.ANY, acme_version=2, new_nonce_url=mock.ANY)
self.net.post.assert_any_call('https://example.com/acme/cert/2',
mock.ANY, acme_version=2, new_nonce_url=mock.ANY)
self.assertEqual(resp, updated_orderr)
del self.response.headers['Link']
resp = self.client.finalize_order(self.orderr, deadline, fetch_alternative_chains=True)
self.assertEqual(resp, updated_orderr.update(alternative_fullchains_pem=[]))
def test_revoke(self):
self.client.revoke(messages_test.CERT, self.rsn)
self.net.post.assert_called_once_with(
@@ -850,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()
@@ -886,7 +913,7 @@ class ClientV2Test(ClientTestBase):
self.client.net.get.assert_not_called()
class MockJSONDeSerializable(jose.JSONDeSerializable):
class MockJSONDeSerializable(VersionedLEACMEMixin, jose.JSONDeSerializable):
# pylint: disable=missing-docstring
def __init__(self, value):
self.value = value
@@ -916,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
@@ -1158,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":
@@ -1303,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
@@ -1313,7 +1340,7 @@ class ClientNetworkSourceAddressBindingTest(unittest.TestCase):
# test should fail if the default adapter type is changed by requests
net = ClientNetwork(key=None, alg=None)
session = requests.Session()
for scheme in session.adapters.keys():
for scheme in session.adapters:
client_network_adapter = net.session.adapters.get(scheme)
default_adapter = session.adapters.get(scheme)
self.assertEqual(client_network_adapter.__class__, default_adapter.__class__)

View File

@@ -1,24 +1,22 @@
"""Tests for acme.crypto_util."""
import itertools
import socket
import socketserver
import threading
import time
import unittest
from typing import List
import josepy as jose
import OpenSSL
import six
from six.moves import socketserver # type: ignore # pylint: disable=import-error
from acme import errors
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
import test_util
class SSLSocketAndProbeSNITest(unittest.TestCase):
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
def setUp(self):
self.cert = test_util.load_comparable_cert('rsa2048_cert.pem')
key = test_util.load_pyopenssl_private_key('rsa2048_key.pem')
@@ -29,10 +27,9 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
class _TestServer(socketserver.TCPServer):
# six.moves.* | pylint: disable=attribute-defined-outside-init,no-init
def server_bind(self): # pylint: disable=missing-docstring
self.socket = SSLSocket(socket.socket(), certs=certs)
self.socket = SSLSocket(socket.socket(),
certs)
socketserver.TCPServer.server_bind(self)
self.server = _TestServer(('', 0), socketserver.BaseRequestHandler)
@@ -63,7 +60,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
self.assertRaises(errors.Error, self._probe, b'bar')
def test_probe_connection_error(self):
# pylint has a hard time with six
self.server.server_close()
original_timeout = socket.getdefaulttimeout()
try:
@@ -73,6 +69,18 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
socket.setdefaulttimeout(original_timeout)
class SSLSocketTest(unittest.TestCase):
"""Tests for acme.crypto_util.SSLSocket."""
def test_ssl_socket_invalid_arguments(self):
from acme.crypto_util import SSLSocket
with self.assertRaises(ValueError):
_ = SSLSocket(None, {'sni': ('key', 'cert')},
cert_selection=lambda _: None)
with self.assertRaises(ValueError):
_ = SSLSocket(None)
class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
"""Test for acme.crypto_util._pyopenssl_cert_or_req_all_names."""
@@ -110,9 +118,9 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
@classmethod
def _get_idn_names(cls):
"""Returns expected names from '{cert,csr}-idnsans.pem'."""
chars = [six.unichr(i) for i in itertools.chain(range(0x3c3, 0x400),
range(0x641, 0x6fc),
range(0x1820, 0x1877))]
chars = [chr(i) for i in itertools.chain(range(0x3c3, 0x400),
range(0x641, 0x6fc),
range(0x1820, 0x1877))]
return [''.join(chars[i: i + 45]) + '.invalid'
for i in range(0, len(chars), 45)]
@@ -173,7 +181,7 @@ class RandomSnTest(unittest.TestCase):
def setUp(self):
self.cert_count = 5
self.serial_num = [] # type: List[int]
self.serial_num: List[int] = []
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
@@ -183,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."""
@@ -198,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

@@ -1,7 +1,6 @@
"""Tests for acme.errors."""
import unittest
import mock
from unittest import mock
class BadNonceTest(unittest.TestCase):
@@ -25,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):
@@ -35,7 +34,7 @@ class PollErrorTest(unittest.TestCase):
def setUp(self):
from acme.errors import PollError
self.timeout = PollError(
exhausted=set([mock.sentinel.AR]),
exhausted={mock.sentinel.AR},
updated={})
self.invalid = PollError(exhausted=set(), updated={
mock.sentinel.AR: mock.sentinel.AR2})

View File

@@ -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

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

View File

@@ -1,11 +1,11 @@
"""Tests for acme.messages."""
from typing import Dict
import unittest
from unittest import mock
import josepy as jose
import mock
from acme import challenges
from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module
import test_util
CERT = test_util.load_comparable_cert('cert.der')
@@ -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
@@ -82,7 +82,7 @@ class ConstantTest(unittest.TestCase):
from acme.messages import _Constant
class MockConstant(_Constant): # pylint: disable=missing-docstring
POSSIBLE_NAMES = {} # type: Dict
POSSIBLE_NAMES: Dict = {}
self.MockConstant = MockConstant # pylint: disable=invalid-name
self.const_a = MockConstant('a')
@@ -106,11 +106,11 @@ class ConstantTest(unittest.TestCase):
def test_equality(self):
const_a_prime = self.MockConstant('a')
self.assertFalse(self.const_a == self.const_b)
self.assertTrue(self.const_a == const_a_prime)
self.assertNotEqual(self.const_a, self.const_b)
self.assertEqual(self.const_a, const_a_prime)
self.assertTrue(self.const_a != self.const_b)
self.assertFalse(self.const_a != const_a_prime)
self.assertNotEqual(self.const_a, self.const_b)
self.assertEqual(self.const_a, const_a_prime)
class DirectoryTest(unittest.TestCase):
@@ -252,6 +252,19 @@ class RegistrationTest(unittest.TestCase):
from acme.messages import Registration
hash(Registration.from_json(self.jobj_from))
def test_default_not_transmitted(self):
from acme.messages import NewRegistration
empty_new_reg = NewRegistration()
new_reg_with_contact = NewRegistration(contact=())
self.assertEqual(empty_new_reg.contact, ())
self.assertEqual(new_reg_with_contact.contact, ())
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):
"""Tests for acme.messages.UpdateRegistration."""
@@ -393,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):
@@ -404,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()))
@@ -420,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()))
@@ -453,6 +466,7 @@ class OrderResourceTest(unittest.TestCase):
'authorizations': None,
})
class NewOrderTest(unittest.TestCase):
"""Tests for acme.messages.NewOrder."""
@@ -467,5 +481,18 @@ class NewOrderTest(unittest.TestCase):
})
class JWSPayloadRFC8555Compliant(unittest.TestCase):
"""Test for RFC8555 compliance of JWS generated from resources/challenges"""
def test_message_payload(self):
from acme.messages import NewAuthorization
new_order = NewAuthorization()
new_order.le_acme_version = 2
jobj = new_order.json_dumps(indent=2).encode()
# RFC8555 states that JWS bodies must not have a resource field.
self.assertEqual(jobj, b'{}')
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

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

View File

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

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

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

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

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

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

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

View File

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

View File

@@ -5,9 +5,10 @@ import logging
import re
import subprocess
import pkg_resources
from certbot import errors
from certbot import util
from certbot.compat import os
logger = logging.getLogger(__name__)
@@ -128,7 +129,7 @@ def included_in_paths(filepath, paths):
:rtype: bool
"""
return any([fnmatch.fnmatch(filepath, path) for path in paths])
return any(fnmatch.fnmatch(filepath, path) for path in paths)
def parse_defines(apachectl):
@@ -142,7 +143,7 @@ def parse_defines(apachectl):
:rtype: dict
"""
variables = dict()
variables = {}
define_cmd = [apachectl, "-t", "-D",
"DUMP_RUN_CFG"]
matches = parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
@@ -223,7 +224,8 @@ def _get_runtime_cfg(command):
command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
universal_newlines=True)
universal_newlines=True,
env=util.env_no_snap_for_external_calls())
stdout, stderr = proc.communicate()
except (OSError, ValueError):
@@ -241,3 +243,14 @@ def _get_runtime_cfg(command):
"loaded because Apache is misconfigured.")
return stdout
def find_ssl_apache_conf(prefix):
"""
Find a TLS Apache config file in the dedicated storage.
:param str prefix: prefix of the TLS Apache config file to find
:return: the path the TLS Apache config file
:rtype: str
"""
return pkg_resources.resource_filename(
"certbot_apache",
os.path.join("_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix)))

View File

@@ -1,4 +1,5 @@
""" apacheconfig implementation of the ParserNode interfaces """
from typing import Tuple
from certbot_apache._internal import assertions
from certbot_apache._internal import interfaces
@@ -14,14 +15,14 @@ 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
self.metadata = metadata
self._raw = self.metadata["ac_ast"]
def save(self, msg): # pragma: no cover
def save(self, msg): # pragma: no cover
pass
def find_ancestors(self, name): # pylint: disable=unused-variable
@@ -38,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
@@ -56,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
@@ -73,7 +74,7 @@ class ApacheDirectiveNode(ApacheParserNode):
self.metadata == other.metadata)
return False
def set_parameters(self, _parameters):
def set_parameters(self, _parameters): # pragma: no cover
"""Sets the parameters for DirectiveNode"""
return
@@ -82,8 +83,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
""" apacheconfig implementation of BlockNode interface """
def __init__(self, **kwargs):
super(ApacheBlockNode, self).__init__(**kwargs)
self.children = ()
super().__init__(**kwargs)
self.children: Tuple[ApacheParserNode, ...] = ()
def __eq__(self, other): # pragma: no cover
if isinstance(other, self.__class__):
@@ -97,7 +98,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
self.metadata == other.metadata)
return False
def add_child_block(self, name, parameters=None, position=None): # pylint: disable=unused-argument
# pylint: disable=unused-argument
def add_child_block(self, name, parameters=None, position=None): # pragma: no cover
"""Adds a new BlockNode to the sequence of children"""
new_block = ApacheBlockNode(name=assertions.PASS,
parameters=assertions.PASS,
@@ -107,7 +109,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
self.children += (new_block,)
return new_block
def add_child_directive(self, name, parameters=None, position=None): # pylint: disable=unused-argument
# pylint: disable=unused-argument
def add_child_directive(self, name, parameters=None, position=None): # pragma: no cover
"""Adds a new DirectiveNode to the sequence of children"""
new_dir = ApacheDirectiveNode(name=assertions.PASS,
parameters=assertions.PASS,
@@ -144,7 +147,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
filepath=assertions.PASS,
metadata=self.metadata)]
def find_comments(self, comment, exact=False): # pylint: disable=unused-argument
# pylint: disable=unused-argument
def find_comments(self, comment, exact=False): # pragma: no cover
"""Recursive search of DirectiveNodes from the sequence of children"""
return [ApacheCommentNode(comment=assertions.PASS,
ancestor=self,

View File

@@ -3,7 +3,6 @@ import fnmatch
from certbot_apache._internal import interfaces
PASS = "CERTBOT_PASS_ASSERT"
@@ -132,9 +131,9 @@ def assertEqualPathsList(first, second): # pragma: no cover
Checks that the two lists of file paths match. This assertion allows for wildcard
paths.
"""
if any([isPass(path) for path in first]):
if any(isPass(path) for path in first):
return
if any([isPass(path) for path in second]):
if any(isPass(path) for path in second):
return
for fpath in first:
assert any([fnmatch.fnmatch(fpath, spath) for spath in second])

View File

@@ -6,7 +6,7 @@ Authors:
Raphael Pinson <raphink@gmail.com>
About: Reference
Online Apache configuration manual: http://httpd.apache.org/docs/trunk/
Online Apache configuration manual: https://httpd.apache.org/docs/trunk/
About: License
This file is licensed under the LGPL v2+.

View File

@@ -64,10 +64,10 @@ Translates over to:
"/files/etc/apache2/apache2.conf/bLoCk[1]",
]
"""
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from typing import Set
from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import assertions
from certbot_apache._internal import interfaces
@@ -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):
@@ -339,7 +339,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
def find_blocks(self, name, exclude=True):
"""Recursive search of BlockNodes from the sequence of children"""
nodes = list()
nodes = []
paths = self._aug_find_blocks(name)
if exclude:
paths = self.parser.exclude_dirs(paths)
@@ -351,11 +351,11 @@ class AugeasBlockNode(AugeasDirectiveNode):
def find_directives(self, name, exclude=True):
"""Recursive search of DirectiveNodes from the sequence of children"""
nodes = list()
nodes = []
ownpath = self.metadata.get("augeaspath")
directives = self.parser.find_dir(name, start=ownpath, exclude=exclude)
already_parsed = set() # type: Set[str]
already_parsed: Set[str] = set()
for directive in directives:
# Remove the /arg part from the Augeas path
directive = directive.partition("/arg")[0]
@@ -374,7 +374,7 @@ class AugeasBlockNode(AugeasDirectiveNode):
:param str comment: Comment content to search for.
"""
nodes = list()
nodes = []
ownpath = self.metadata.get("augeaspath")
comments = self.parser.find_comments(comment, start=ownpath)

View File

@@ -2,23 +2,23 @@
# pylint: disable=too-many-lines
from collections import defaultdict
import copy
from distutils.version import LooseVersion
import fnmatch
import logging
import re
import socket
import time
from typing import DefaultDict
from typing import Dict
from typing import List
from typing import Optional
from typing import Set
from typing import Union
import pkg_resources
import six
import zope.component
import zope.interface
from acme import challenges
from acme.magic_typing import DefaultDict
from acme.magic_typing import Dict
from acme.magic_typing import List
from acme.magic_typing import Set
from acme.magic_typing import Union
from certbot import errors
from certbot import interfaces
from certbot import util
@@ -36,10 +36,61 @@ 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
HAS_APACHECONFIG = True
except ImportError: # pragma: no cover
HAS_APACHECONFIG = False
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
@@ -95,28 +146,24 @@ 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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)
OS_DEFAULTS = OsOptions()
def option(self, key):
"""Get a value from options"""
return self.options.get(key)
def pick_apache_config(self, warn_on_no_mod_ssl=True):
"""
Pick the appropriate TLS Apache configuration file for current version of Apache and OS.
:param bool warn_on_no_mod_ssl: True if we should warn if mod_ssl is not found.
:return: the path to the TLS Apache configuration file to use
:rtype: str
"""
# Disabling TLS session tickets is supported by Apache 2.4.11+ and OpenSSL 1.0.2l+.
# So for old versions of Apache we pick a configuration without this option.
openssl_version = self.openssl_version(warn_on_no_mod_ssl)
if self.version < (2, 4, 11) or not openssl_version or\
LooseVersion(openssl_version) < LooseVersion('1.0.2l'):
return apache_util.find_ssl_apache_conf("old")
return apache_util.find_ssl_apache_conf("current")
def _prepare_options(self):
"""
@@ -125,18 +172,18 @@ class ApacheConfigurator(common.Installer):
"""
opts = ["enmod", "dismod", "le_vhost_ext", "server_root", "vhost_root",
"logs_root", "challenge_location", "handle_modules", "handle_sites",
"ctl"]
"ctl", "bin"]
for o in opts:
# Config options use dashes instead of underscores
if self.conf(o.replace("_", "-")) is not None:
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
self.options["version_cmd"][0] = self.option("ctl")
self.options["restart_cmd"][0] = self.option("ctl")
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):
@@ -151,29 +198,31 @@ 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,
help="Full path to apache2/httpd binary")
def __init__(self, *args, **kwargs):
"""Initialize an Apache Configurator.
@@ -184,32 +233,34 @@ class ApacheConfigurator(common.Installer):
"""
version = kwargs.pop("version", None)
use_parsernode = kwargs.pop("use_parsernode", False)
super(ApacheConfigurator, self).__init__(*args, **kwargs)
openssl_version = kwargs.pop("openssl_version", None)
super().__init__(*args, **kwargs)
# Add name_server association dict
self.assoc = dict() # type: Dict[str, obj.VirtualHost]
self.assoc: Dict[str, obj.VirtualHost] = {}
# Outstanding challenges
self._chall_out = set() # type: Set[KeyAuthorizationAnnotatedChallenge]
self._chall_out: Set[KeyAuthorizationAnnotatedChallenge] = set()
# List of vhosts configured per wildcard domain on this run.
# used by deploy_cert() and enhance()
self._wildcard_vhosts = dict() # type: Dict[str, List[obj.VirtualHost]]
self._wildcard_vhosts: Dict[str, List[obj.VirtualHost]] = {}
# Maps enhancements to vhosts we've enabled the enhancement for
self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]]
self._enhanced_vhosts: DefaultDict[str, Set[obj.VirtualHost]] = defaultdict(set)
# Temporary state for AutoHSTS enhancement
self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]]
self._autohsts: Dict[str, Dict[str, Union[int, float]]] = {}
# Reverter save notes
self.save_notes = ""
# Should we use ParserNode implementation instead of the old behavior
self.USE_PARSERNODE = use_parsernode
# Saves the list of file paths that were parsed initially, and
# not added to parser tree by self.conf("vhost-root") for example.
self.parsed_paths = [] # type: List[str]
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.vhosts = None
self._openssl_version = openssl_version
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,
@@ -225,6 +276,59 @@ class ApacheConfigurator(common.Installer):
"""Full absolute path to digest of updated SSL configuration file."""
return os.path.join(self.config.config_dir, constants.UPDATED_MOD_SSL_CONF_DIGEST)
def _open_module_file(self, ssl_module_location):
"""Extract the open lines of openssl_version for testing purposes"""
try:
with open(ssl_module_location, mode="rb") as f:
contents = f.read()
except IOError as error:
logger.debug(str(error), exc_info=True)
return None
return contents
def openssl_version(self, warn_on_no_mod_ssl=True):
"""Lazily retrieve openssl version
:param bool warn_on_no_mod_ssl: `True` if we should warn if mod_ssl is not found. Set to
`False` when we know we'll try to enable mod_ssl later. This is currently debian/ubuntu,
when called from `prepare`.
:return: the OpenSSL version as a string, or None.
:rtype: str or None
"""
if self._openssl_version:
return self._openssl_version
# Step 1. Determine the location of ssl_module
try:
ssl_module_location = self.parser.modules['ssl_module']
except KeyError:
if warn_on_no_mod_ssl:
logger.warning("Could not find ssl_module; not disabling session tickets.")
return None
if ssl_module_location:
# Possibility A: ssl_module is a DSO
ssl_module_location = self.parser.standard_path_from_server_root(ssl_module_location)
else:
# Possibility B: ssl_module is statically linked into Apache
if self.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.")
return None
# Step 2. Grep in the binary for openssl version
contents = self._open_module_file(ssl_module_location)
if not contents:
logger.warning("Unable to read ssl_module file; not disabling session tickets.")
return None
# looks like: OpenSSL 1.0.2s 28 May 2019
matches = re.findall(br"OpenSSL ([0-9]\.[^ ]+) ", contents)
if not matches:
logger.warning("Could not find OpenSSL version; not disabling session tickets.")
return None
self._openssl_version = matches[0].decode('UTF-8')
return self._openssl_version
def prepare(self):
"""Prepare the authenticator/installer.
@@ -237,7 +341,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()
@@ -250,6 +354,9 @@ class ApacheConfigurator(common.Installer):
if self.version < (2, 2):
raise errors.NotSupportedError(
"Apache Version {0} not supported.".format(str(self.version)))
elif self.version < (2, 4):
logger.warning('Support for Apache 2.2 is deprecated and will be removed in a '
'future release.')
# Recover from previous crash before Augeas initialization to have the
# correct parse tree from the get go.
@@ -262,8 +369,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")
@@ -271,18 +379,22 @@ class ApacheConfigurator(common.Installer):
# Get all of the available vhosts
self.vhosts = self.get_virtual_hosts()
# We may try to enable mod_ssl later. If so, we shouldn't warn if we can't find it now.
# This is currently only true for debian/ubuntu.
warn_on_no_mod_ssl = not self.options.handle_modules
self.install_ssl_options_conf(self.mod_ssl_conf,
self.updated_mod_ssl_conf_digest)
self.updated_mod_ssl_conf_digest,
warn_on_no_mod_ssl)
# Prevent two Apache plugins from modifying a config at once
try:
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):
@@ -318,10 +430,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()
@@ -343,7 +455,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):
@@ -357,17 +469,23 @@ 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):
"""Initializes the ParserNode parser root instance."""
apache_vars = dict()
apache_vars["defines"] = apache_util.parse_defines(self.option("ctl"))
apache_vars["includes"] = apache_util.parse_includes(self.option("ctl"))
apache_vars["modules"] = apache_util.parse_modules(self.option("ctl"))
metadata["apache_vars"] = apache_vars
if HAS_APACHECONFIG:
apache_vars = {}
apache_vars["defines"] = apache_util.parse_defines(self.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:
with apacheconfig.make_loader(writable=True,
**apacheconfig.flavors.NATIVE_APACHE) as loader:
metadata["ac_ast"] = loader.loads(f.read())
return dualparser.DualBlockNode(
name=assertions.PASS,
@@ -376,21 +494,6 @@ class ApacheConfigurator(common.Installer):
metadata=metadata
)
def _wildcard_domain(self, domain):
"""
Checks if domain is a wildcard domain
:param str domain: Domain to check
:returns: If the domain is wildcard domain
:rtype: bool
"""
if isinstance(domain, six.text_type):
wildcard_marker = u"*."
else:
wildcard_marker = b"*."
return domain.startswith(wildcard_marker)
def deploy_cert(self, domain, cert_path, key_path,
chain_path=None, fullchain_path=None):
"""Deploys certificate to specified virtual host.
@@ -425,7 +528,7 @@ class ApacheConfigurator(common.Installer):
:rtype: `list` of :class:`~certbot_apache._internal.obj.VirtualHost`
"""
if self._wildcard_domain(domain):
if util.is_wildcard_domain(domain):
if domain in self._wildcard_vhosts:
# Vhosts for a wildcard domain were already selected
return self._wildcard_vhosts[domain]
@@ -470,7 +573,7 @@ class ApacheConfigurator(common.Installer):
# Go through the vhosts, making sure that we cover all the names
# present, but preferring the SSL vhosts
filtered_vhosts = dict()
filtered_vhosts = {}
for vhost in vhosts:
for name in vhost.get_names():
if vhost.ssl:
@@ -496,7 +599,7 @@ class ApacheConfigurator(common.Installer):
# Make sure we create SSL vhosts for the ones that are HTTP only
# if requested.
return_vhosts = list()
return_vhosts = []
for vhost in dialog_output:
if not vhost.ssl:
return_vhosts.append(self.make_vhost_ssl(vhost))
@@ -517,6 +620,11 @@ class ApacheConfigurator(common.Installer):
# cert_key... can all be parsed appropriately
self.prepare_server_https("443")
# If we haven't managed to enable mod_ssl by this point, error out
if "ssl_module" not in self.parser.modules:
raise errors.MisconfigurationError("Could not find ssl_module; "
"not installing certificate.")
# Add directives and remove duplicates
self._add_dummy_ssl_directives(vhost.path)
self._clean_vhost(vhost)
@@ -531,21 +639,6 @@ class ApacheConfigurator(common.Installer):
path["chain_path"] = self.parser.find_dir(
"SSLCertificateChainFile", None, vhost.path)
# Handle errors when certificate/key directives cannot be found
if not path["cert_path"]:
logger.warning(
"Cannot find an SSLCertificateFile directive in %s. "
"VirtualHost was not modified", vhost.path)
raise errors.PluginError(
"Unable to find an SSLCertificateFile directive")
elif not path["cert_key"]:
logger.warning(
"Cannot find an SSLCertificateKeyFile directive for "
"certificate in %s. VirtualHost was not modified", vhost.path)
raise errors.PluginError(
"Unable to find an SSLCertificateKeyFile directive for "
"certificate")
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
if self.version < (2, 4, 8) or (chain_path and not fullchain_path):
@@ -767,7 +860,7 @@ class ApacheConfigurator(common.Installer):
:rtype: set
"""
all_names = set() # type: Set[str]
all_names: Set[str] = set()
vhost_macro = []
@@ -907,7 +1000,7 @@ class ApacheConfigurator(common.Installer):
"""
v1_vhosts = self.get_virtual_hosts_v1()
if self.USE_PARSERNODE:
if self.USE_PARSERNODE and HAS_APACHECONFIG:
v2_vhosts = self.get_virtual_hosts_v2()
for v1_vh in v1_vhosts:
@@ -931,8 +1024,8 @@ class ApacheConfigurator(common.Installer):
"""
# Search base config, and all included paths for VirtualHosts
file_paths = {} # type: Dict[str, str]
internal_paths = defaultdict(set) # type: DefaultDict[str, Set[str]]
file_paths: Dict[str, str] = {}
internal_paths: DefaultDict[str, Set[str]] = defaultdict(set)
vhs = []
# Make a list of parser paths because the parser_paths
# dictionary may be modified during the loop.
@@ -983,6 +1076,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:
@@ -1235,19 +1331,27 @@ 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)
if "ssl_module" not in self.parser.modules:
self.enable_mod("ssl", temp=temp)
# Make sure we're not throwing away any unwritten changes to the config
self.parser.ensure_augeas_state()
self.parser.aug.load()
self.parser.reset_modules() # Reset to load the new ssl_module path
# Call again because now we can gate on openssl version
self.install_ssl_options_conf(self.mod_ssl_conf,
self.updated_mod_ssl_conf_digest,
warn_on_no_mod_ssl=True)
def make_vhost_ssl(self, nonssl_vhost):
"""Makes an ssl_vhost version of a nonssl_vhost.
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
@@ -1346,15 +1450,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.
@@ -1376,7 +1480,7 @@ class ApacheConfigurator(common.Installer):
if not line.lower().lstrip().startswith("rewriterule"):
return False
# According to: http://httpd.apache.org/docs/2.4/rewrite/flags.html
# According to: https://httpd.apache.org/docs/2.4/rewrite/flags.html
# The syntax of a RewriteRule is:
# RewriteRule pattern target [Flag1,Flag2,Flag3]
# i.e. target is required, so it must exist.
@@ -1493,7 +1597,7 @@ class ApacheConfigurator(common.Installer):
result.append(comment)
sift = True
result.append('\n'.join(['# ' + l for l in chunk]))
result.append('\n'.join('# ' + l for l in chunk))
else:
result.append('\n'.join(chunk))
return result, sift
@@ -1633,7 +1737,7 @@ class ApacheConfigurator(common.Installer):
for addr in vhost.addrs:
# In Apache 2.2, when a NameVirtualHost directive is not
# set, "*" and "_default_" will conflict when sharing a port
addrs = set((addr,))
addrs = {addr,}
if addr.get_addr() in ("*", "_default_"):
addrs.update(obj.Addr((a, addr.get_port(),))
for a in ("*", "_default_"))
@@ -1824,7 +1928,7 @@ class ApacheConfigurator(common.Installer):
try:
self._autohsts = self.storage.fetch("autohsts")
except KeyError:
self._autohsts = dict()
self._autohsts = {}
def _autohsts_save_state(self):
"""
@@ -2083,7 +2187,7 @@ class ApacheConfigurator(common.Installer):
# There can be other RewriteRule directive lines in vhost config.
# rewrite_args_dict keys are directive ids and the corresponding value
# for each is a list of arguments to that directive.
rewrite_args_dict = defaultdict(list) # type: DefaultDict[str, List[str]]
rewrite_args_dict: DefaultDict[str, List[str]] = defaultdict(list)
pat = r'(.*directive\[\d+\]).*'
for match in rewrite_path:
m = re.match(pat, match)
@@ -2177,7 +2281,7 @@ class ApacheConfigurator(common.Installer):
if ssl_vhost.aliases:
serveralias = "ServerAlias " + " ".join(ssl_vhost.aliases)
rewrite_rule_args = [] # type: List[str]
rewrite_rule_args: List[str] = []
if self.get_version() >= (2, 3, 9):
rewrite_rule_args = constants.REWRITE_HTTPS_ARGS_WITH_END
else:
@@ -2198,7 +2302,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
@@ -2210,7 +2314,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
@@ -2330,19 +2434,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")
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)
@@ -2357,7 +2460,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))
@@ -2373,11 +2476,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)
@@ -2385,7 +2488,7 @@ class ApacheConfigurator(common.Installer):
if len(matches) != 1:
raise errors.PluginError("Unable to find Apache version")
return tuple([int(i) for i in matches[0].split(".")])
return tuple(int(i) for i in matches[0].split("."))
def more_info(self):
"""Human-readable string to help understand the module"""
@@ -2454,14 +2557,19 @@ class ApacheConfigurator(common.Installer):
self.restart()
self.parser.reset_modules()
def install_ssl_options_conf(self, options_ssl, options_ssl_digest):
"""Copy Certbot's SSL options file into the system's config dir if required."""
def install_ssl_options_conf(self, options_ssl, options_ssl_digest, warn_on_no_mod_ssl=True):
"""Copy Certbot's SSL options file into the system's config dir if required.
:param bool warn_on_no_mod_ssl: True if we should warn if mod_ssl is not found.
"""
# XXX if we ever try to enforce a local privilege boundary (eg, running
# certbot for unprivileged users via setuid), this function will need
# to be modified.
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
self.option("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
apache_config_path = self.pick_apache_config(warn_on_no_mod_ssl)
return common.install_version_controlled_file(
options_ssl, options_ssl_digest, apache_config_path, constants.ALL_SSL_OPTIONS_HASHES)
def enable_autohsts(self, _unused_lineage, domains):
"""

View File

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

View File

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

View File

@@ -1,10 +1,10 @@
""" Dual ParserNode implementation """
from certbot_apache._internal import apacheparser
from certbot_apache._internal import assertions
from certbot_apache._internal import augeasparser
from certbot_apache._internal import apacheparser
class DualNodeBase(object):
class DualNodeBase:
""" Dual parser interface for in development testing. This is used as the
base class for dual parser interface classes. This class handles runtime
attribute value assertions."""
@@ -49,7 +49,7 @@ class DualNodeBase(object):
pass_primary = assertions.isPassNodeList(primary_res)
pass_secondary = assertions.isPassNodeList(secondary_res)
new_nodes = list()
new_nodes = []
if pass_primary and pass_secondary:
# Both unimplemented
@@ -221,7 +221,7 @@ class DualBlockNode(DualNodeBase):
implementations to a list of tuples.
"""
matched = list()
matched = []
for p in primary_list:
match = None
for s in secondary_list:

View File

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

View File

@@ -1,8 +1,9 @@
"""A class that performs HTTP-01 challenges for Apache"""
import errno
import logging
from typing import List
from typing import Set
from acme.magic_typing import List
from acme.magic_typing import Set
from certbot import errors
from certbot.compat import filesystem
from certbot.compat import os
@@ -46,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")
@@ -56,7 +57,7 @@ class ApacheHttp01(common.ChallengePerformer):
self.challenge_dir = os.path.join(
self.configurator.config.work_dir,
"http_challenges")
self.moded_vhosts = set() # type: Set[VirtualHost]
self.moded_vhosts: Set[VirtualHost] = set()
def perform(self):
"""Perform all HTTP-01 challenges."""
@@ -92,7 +93,7 @@ class ApacheHttp01(common.ChallengePerformer):
self.configurator.enable_mod(mod, temp=True)
def _mod_config(self):
selected_vhosts = [] # type: List[VirtualHost]
selected_vhosts: List[VirtualHost] = []
http_port = str(self.configurator.config.http01_port)
for chall in self.achalls:
# Search for matching VirtualHosts
@@ -168,7 +169,15 @@ class ApacheHttp01(common.ChallengePerformer):
def _set_up_challenges(self):
if not os.path.isdir(self.challenge_dir):
filesystem.makedirs(self.challenge_dir, 0o755)
old_umask = filesystem.umask(0o022)
try:
filesystem.makedirs(self.challenge_dir, 0o755)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for http-01 challenge")
finally:
filesystem.umask(old_umask)
responses = []
for achall in self.achalls:

View File

@@ -100,13 +100,9 @@ For this reason the internal representation of data should not ignore the case.
"""
import abc
import six
from acme.magic_typing import Any, Dict, Optional, Tuple # pylint: disable=unused-import, no-name-in-module
@six.add_metaclass(abc.ABCMeta)
class ParserNode(object):
class ParserNode(object, metaclass=abc.ABCMeta):
"""
ParserNode is the basic building block of the tree of such nodes,
representing the structure of the configuration. It is largely meant to keep
@@ -205,9 +201,7 @@ class ParserNode(object):
"""
# Linter rule exclusion done because of https://github.com/PyCQA/pylint/issues/179
@six.add_metaclass(abc.ABCMeta) # pylint: disable=abstract-method
class CommentNode(ParserNode):
class CommentNode(ParserNode, metaclass=abc.ABCMeta):
"""
CommentNode class is used for representation of comments within the parsed
configuration structure. Because of the nature of comments, it is not able
@@ -244,14 +238,13 @@ class CommentNode(ParserNode):
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
@six.add_metaclass(abc.ABCMeta)
class DirectiveNode(ParserNode):
class DirectiveNode(ParserNode, metaclass=abc.ABCMeta):
"""
DirectiveNode class represents a configuration directive within the configuration.
It can have zero or more parameters attached to it. Because of the nature of
@@ -309,7 +302,7 @@ class DirectiveNode(ParserNode):
: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
@@ -326,8 +319,7 @@ class DirectiveNode(ParserNode):
"""
@six.add_metaclass(abc.ABCMeta)
class BlockNode(DirectiveNode):
class BlockNode(DirectiveNode, metaclass=abc.ABCMeta):
"""
BlockNode class represents a block of nested configuration directives, comments
and other blocks as its children. A BlockNode can have zero or more parameters

View File

@@ -1,7 +1,7 @@
"""Module contains classes used by the Apache Configurator."""
import re
from typing import Set
from acme.magic_typing import Set
from certbot.plugins import common
@@ -20,16 +20,13 @@ class Addr(common.Addr):
self.is_wildcard() and other.is_wildcard()))
return False
def __ne__(self, other):
return not self.__eq__(other)
def __repr__(self):
return "certbot_apache._internal.obj.Addr(" + repr(self.tup) + ")"
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()."""
@@ -98,7 +95,7 @@ class Addr(common.Addr):
return self.get_addr_obj(port)
class VirtualHost(object):
class VirtualHost:
"""Represents an Apache Virtualhost.
:ivar str filep: file path of VH
@@ -140,7 +137,7 @@ class VirtualHost(object):
def get_names(self):
"""Return a set of all names."""
all_names = set() # type: Set[str]
all_names: Set[str] = set()
all_names.update(self.aliases)
# Strip out any scheme:// and <port> field from servername
if self.name is not None:
@@ -191,9 +188,6 @@ class VirtualHost(object):
return False
def __ne__(self, other):
return not self.__eq__(other)
def __hash__(self):
return hash((self.filep, self.path,
tuple(self.addrs), tuple(self.get_names()),
@@ -251,7 +245,7 @@ class VirtualHost(object):
# already_found acts to keep everything very conservative.
# Don't allow multiple ip:ports in same set.
already_found = set() # type: Set[str]
already_found: Set[str] = set()
for addr in vhost.addrs:
for local_addr in self.addrs:

View File

@@ -1,17 +1,16 @@
""" Distribution specific override class for Arch Linux """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import configurator
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",
@@ -20,12 +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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)

View File

@@ -1,18 +1,18 @@
""" Distribution specific override class for CentOS family (RHEL, Fedora) """
import logging
from typing import cast
from typing import List
import pkg_resources
import zope.interface
from acme.magic_typing import List
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import os
from certbot.errors import MisconfigurationError
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
from certbot_apache._internal.configurator import OsOptions
logger = logging.getLogger(__name__)
@@ -21,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",
@@ -31,14 +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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)
def config_test(self):
@@ -53,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()
@@ -71,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()
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
@@ -93,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()
@@ -105,9 +100,9 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
loadmods = self.parser.find_dir("LoadModule", "ssl_module", exclude=False)
correct_ifmods = [] # type: List[str]
loadmod_args = [] # type: List[str]
loadmod_paths = [] # type: List[str]
correct_ifmods: List[str] = []
loadmod_args: List[str] = []
loadmod_paths: List[str] = []
for m in loadmods:
noarg_path = m.rpartition("/")[0]
path_args = self.parser.get_all_args(noarg_path)
@@ -121,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()):
@@ -170,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

@@ -1,31 +1,21 @@
""" Distribution specific override class for macOS """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import configurator
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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)

View File

@@ -1,7 +1,6 @@
""" Distribution specific override class for Debian family (Ubuntu/Debian) """
import logging
import pkg_resources
import zope.interface
from certbot import errors
@@ -11,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__)
@@ -19,23 +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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)
def enable_site(self, vhost):
@@ -60,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)
@@ -134,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,21 +1,20 @@
""" Distribution specific override class for Fedora 29+ """
import pkg_resources
import zope.interface
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
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",
@@ -25,15 +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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
# TODO: eventually newest version of Fedora will need their own config
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)
def config_test(self):
@@ -44,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):
@@ -64,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):
"""
@@ -72,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()
self.options["restart_cmd"][0] = 'apachectl'
self.options["restart_cmd_alt"][0] = 'apachectl'
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):
@@ -83,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 """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)
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()
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

@@ -1,31 +1,23 @@
""" Distribution specific override class for OpenSUSE """
import pkg_resources
import zope.interface
from certbot import interfaces
from certbot.compat import os
from certbot_apache._internal import configurator
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="apache2ctl",
version_cmd=['apache2ctl', '-v'],
restart_cmd=['apache2ctl', 'graceful'],
conftest_cmd=['apache2ctl', 'configtest'],
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",
MOD_SSL_CONF_SRC=pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "options-ssl-apache.conf"))
)

View File

@@ -3,22 +3,24 @@ import copy
import fnmatch
import logging
import re
import sys
from typing import Dict
from typing import List
from typing import Optional
import six
from acme.magic_typing import Dict
from acme.magic_typing import List
from acme.magic_typing import Set
from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import constants
try:
from augeas import Augeas
except ImportError: # pragma: no cover
Augeas = None # type: ignore
logger = logging.getLogger(__name__)
class ApacheParser(object):
class ApacheParser:
"""Class handles the fine details of parsing the Apache Configuration.
.. todo:: Make parsing general... remove sites-available etc...
@@ -31,7 +33,7 @@ class ApacheParser(object):
"""
arg_var_interpreter = re.compile(r"\$\{[^ \}]*}")
fnmatch_chars = set(["*", "?", "\\", "[", "]"])
fnmatch_chars = {"*", "?", "\\", "[", "]"}
def __init__(self, root, vhostroot=None, version=(2, 4),
configurator=None):
@@ -43,8 +45,7 @@ class ApacheParser(object):
self.configurator = configurator
# Initialize augeas
self.aug = None
self.init_augeas()
self.aug = init_augeas()
if not self.check_aug_version():
raise errors.NotSupportedError(
@@ -52,9 +53,9 @@ class ApacheParser(object):
"version 1.2.0 or higher, please make sure you have you have "
"those installed.")
self.modules = set() # type: Set[str]
self.parser_paths = {} # type: Dict[str, List[str]]
self.variables = {} # type: Dict[str, str]
self.modules: Dict[str, Optional[str]] = {}
self.parser_paths: Dict[str, List[str]] = {}
self.variables: Dict[str, str] = {}
# Find configuration root and make sure augeas can parse it.
self.root = os.path.abspath(root)
@@ -80,30 +81,13 @@ class ApacheParser(object):
# 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.
@@ -249,14 +233,14 @@ class ApacheParser(object):
def add_mod(self, mod_name):
"""Shortcut for updating parser modules."""
if mod_name + "_module" not in self.modules:
self.modules.add(mod_name + "_module")
self.modules[mod_name + "_module"] = None
if "mod_" + mod_name + ".c" not in self.modules:
self.modules.add("mod_" + mod_name + ".c")
self.modules["mod_" + mod_name + ".c"] = None
def reset_modules(self):
"""Reset the loaded modules list. This is called from cleanup to clear
temporarily loaded modules."""
self.modules = set()
self.modules = {}
self.update_modules()
self.parse_modules()
@@ -267,7 +251,7 @@ class ApacheParser(object):
the iteration issue. Else... parse and enable mods at same time.
"""
mods = set() # type: Set[str]
mods: Dict[str, str] = {}
matches = self.find_dir("LoadModule")
iterator = iter(matches)
# Make sure prev_size != cur_size for do: while: iteration
@@ -276,13 +260,13 @@ class ApacheParser(object):
while len(mods) != prev_size:
prev_size = len(mods)
for match_name, match_filename in six.moves.zip(
for match_name, match_filename in zip(
iterator, iterator):
mod_name = self.get_arg(match_name)
mod_filename = self.get_arg(match_filename)
if mod_name and mod_filename:
mods.add(mod_name)
mods.add(os.path.basename(mod_filename)[:-2] + "c")
mods[mod_name] = mod_filename
mods[os.path.basename(mod_filename)[:-2] + "c"] = mod_filename
else:
logger.debug("Could not read LoadModule directive from Augeas path: %s",
match_name[6:])
@@ -298,7 +282,7 @@ class ApacheParser(object):
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"""
@@ -308,7 +292,7 @@ class ApacheParser(object):
# 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):
@@ -317,7 +301,7 @@ class ApacheParser(object):
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())
@@ -554,7 +538,7 @@ class ApacheParser(object):
else:
arg_suffix = "/*[self::arg=~regexp('%s')]" % case_i(arg)
ordered_matches = [] # type: List[str]
ordered_matches: List[str] = []
# TODO: Wildcards should be included in alphabetical order
# https://httpd.apache.org/docs/2.4/mod/core.html#include
@@ -621,7 +605,7 @@ class ApacheParser(object):
def exclude_dirs(self, matches):
"""Exclude directives that are not loaded into the configuration."""
filters = [("ifmodule", self.modules), ("ifdefine", self.variables)]
filters = [("ifmodule", self.modules.keys()), ("ifdefine", self.variables)]
valid_matches = []
@@ -662,6 +646,25 @@ class ApacheParser(object):
return True
def standard_path_from_server_root(self, arg):
"""Ensure paths are consistent and absolute
:param str arg: Argument of directive
:returns: Standardized argument path
:rtype: str
"""
# Remove beginning and ending quotes
arg = arg.strip("'\"")
# Standardize the include argument based on server root
if not arg.startswith("/"):
# Normpath will condense ../
arg = os.path.normpath(os.path.join(self.root, arg))
else:
arg = os.path.normpath(arg)
return arg
def _get_include_path(self, arg):
"""Converts an Apache Include directive into Augeas path.
@@ -682,16 +685,7 @@ class ApacheParser(object):
# if matchObj.group() != arg:
# logger.error("Error: Invalid regexp characters in %s", arg)
# return []
# Remove beginning and ending quotes
arg = arg.strip("'\"")
# Standardize the include argument based on server root
if not arg.startswith("/"):
# Normpath will condense ../
arg = os.path.normpath(os.path.join(self.root, arg))
else:
arg = os.path.normpath(arg)
arg = self.standard_path_from_server_root(arg)
# Attempts to add a transform to the file if one does not already exist
if os.path.isdir(arg):
@@ -722,7 +716,6 @@ class ApacheParser(object):
privileged users.
https://apr.apache.org/docs/apr/2.0/apr__fnmatch_8h_source.html
http://apache2.sourcearchive.com/documentation/2.2.16-6/apr__fnmatch_8h_source.html
:param str clean_fn_match: Apache style filename match, like globs
@@ -730,9 +723,6 @@ class ApacheParser(object):
:rtype: str
"""
if sys.version_info < (3, 6):
# This strips off final /Z(?ms)
return fnmatch.translate(clean_fn_match)[:-7]
# Since Python 3.6, it returns a different pattern like (?s:.*\.load)\Z
return fnmatch.translate(clean_fn_match)[4:-3] # pragma: no cover
@@ -790,7 +780,7 @@ class ApacheParser(object):
def _parsed_by_parser_paths(self, filep, paths):
"""Helper function that searches through provided paths and returns
True if file path is found in the set"""
for directory in paths.keys():
for directory in paths:
for filename in paths[directory]:
if fnmatch.fnmatch(filep, os.path.join(directory, filename)):
return True
@@ -936,8 +926,8 @@ def case_i(string):
:param str string: string to make case i regex
"""
return "".join(["[" + c.upper() + c.lower() + "]"
if c.isalpha() else c for c in re.escape(string)])
return "".join("[" + c.upper() + c.lower() + "]"
if c.isalpha() else c for c in re.escape(string))
def get_aug_path(file_path):
@@ -947,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

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

View File

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

View File

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

View File

@@ -1,51 +1,32 @@
import sys
from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.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.1.0',
'mock',
'certbot>=1.6.0',
'python-augeas',
'setuptools',
'setuptools>=39.0.1',
'zope.component',
'zope.interface',
]
dev_extras = [
'apacheconfig>=0.3.1',
'apacheconfig>=0.3.2',
]
class PyTest(TestCommand):
user_options = []
def initialize_options(self):
TestCommand.initialize_options(self)
self.pytest_args = ''
def run_tests(self):
import shlex
# import here, cause outside the eggs aren't loaded
import pytest
errno = pytest.main(shlex.split(self.pytest_args))
sys.exit(errno)
setup(
name='certbot-apache',
version=version,
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='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
python_requires='>=3.6',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -53,13 +34,11 @@ setup(
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',
@@ -79,7 +58,4 @@ setup(
'apache = certbot_apache._internal.entrypoint:ENTRYPOINT',
],
},
test_suite='certbot_apache',
tests_require=["pytest"],
cmdclass={"test": PyTest},
)

View File

@@ -52,7 +52,7 @@ function Cleanup() {
# if our environment asks us to enable modules, do our best!
if [ "$1" = --debian-modules ] ; then
sudo apt-get install -y apache2
sudo apt-get install -y libapache2-mod-wsgi
sudo apt-get install -y libapache2-mod-wsgi-py3
sudo apt-get install -y libapache2-mod-macro
for mod in ssl rewrite macro wsgi deflate userdir version mime setenvif ; do

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,10 @@
"""Test for certbot_apache._internal.configurator for Centos overrides"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import filesystem
@@ -21,12 +24,12 @@ def get_vh_truth(temp_dir, config_name):
obj.VirtualHost(
os.path.join(prefix, "centos.example.com.conf"),
os.path.join(aug_pre, "centos.example.com.conf/VirtualHost"),
set([obj.Addr.fromstring("*:80")]),
{obj.Addr.fromstring("*:80")},
False, True, "centos.example.com"),
obj.VirtualHost(
os.path.join(prefix, "ssl.conf"),
os.path.join(aug_pre, "ssl.conf/VirtualHost"),
set([obj.Addr.fromstring("_default_:443")]),
{obj.Addr.fromstring("_default_:443")},
True, True, None)
]
return vh_truth
@@ -38,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")
@@ -93,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,
@@ -126,7 +129,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
return mod_val
return ""
mock_get.side_effect = mock_get_cfg
self.config.parser.modules = set()
self.config.parser.modules = {}
self.config.parser.variables = {}
with mock.patch("certbot.util.get_os_info") as mock_osi:
@@ -137,7 +140,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
self.assertEqual(mock_get.call_count, 3)
self.assertEqual(len(self.config.parser.modules), 4)
self.assertEqual(len(self.config.parser.variables), 2)
self.assertTrue("TEST2" in self.config.parser.variables.keys())
self.assertTrue("TEST2" in self.config.parser.variables)
self.assertTrue("mod_another.c" in self.config.parser.modules)
def test_get_virtual_hosts(self):
@@ -169,11 +172,11 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
mock_osi.return_value = ("centos", "7")
self.config.parser.update_runtime_variables()
self.assertTrue("mock_define" in self.config.parser.variables.keys())
self.assertTrue("mock_define_too" in self.config.parser.variables.keys())
self.assertTrue("mock_value" in self.config.parser.variables.keys())
self.assertTrue("mock_define" in self.config.parser.variables)
self.assertTrue("mock_define_too" in self.config.parser.variables)
self.assertTrue("mock_value" in self.config.parser.variables)
self.assertEqual("TRUE", self.config.parser.variables["mock_value"])
self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys())
self.assertTrue("MOCK_NOSEP" in self.config.parser.variables)
self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"])
@mock.patch("certbot_apache._internal.configurator.util.run_script")

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

@@ -2,7 +2,10 @@
import shutil
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
import util
@@ -13,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

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

View File

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

View File

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

View File

@@ -1,7 +1,10 @@
"""Tests for DualParserNode implementation"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot_apache._internal import assertions
from certbot_apache._internal import augeasparser
@@ -409,9 +412,9 @@ class DualParserNodeTest(unittest.TestCase): # pylint: disable=too-many-public-
ancestor=self.block,
filepath="/path/to/whatever",
metadata=self.metadata)
self.assertFalse(self.block == ne_block)
self.assertFalse(self.directive == ne_directive)
self.assertFalse(self.comment == ne_comment)
self.assertNotEqual(self.block, ne_block)
self.assertNotEqual(self.directive, ne_directive)
self.assertNotEqual(self.comment, ne_comment)
def test_parsed_paths(self):
mock_p = mock.MagicMock(return_value=['/path/file.conf',

View File

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

View File

@@ -1,7 +1,10 @@
"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides"""
import unittest
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from certbot import errors
from certbot.compat import filesystem
@@ -43,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")
@@ -87,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,
@@ -120,7 +123,7 @@ class MultipleVhostsTestFedora(util.ApacheTest):
return mod_val
return ""
mock_get.side_effect = mock_get_cfg
self.config.parser.modules = set()
self.config.parser.modules = {}
self.config.parser.variables = {}
with mock.patch("certbot.util.get_os_info") as mock_osi:
@@ -131,7 +134,7 @@ class MultipleVhostsTestFedora(util.ApacheTest):
self.assertEqual(mock_get.call_count, 3)
self.assertEqual(len(self.config.parser.modules), 4)
self.assertEqual(len(self.config.parser.variables), 2)
self.assertTrue("TEST2" in self.config.parser.variables.keys())
self.assertTrue("TEST2" in self.config.parser.variables)
self.assertTrue("mod_another.c" in self.config.parser.modules)
@mock.patch("certbot_apache._internal.configurator.util.run_script")
@@ -169,11 +172,11 @@ class MultipleVhostsTestFedora(util.ApacheTest):
mock_osi.return_value = ("fedora", "29")
self.config.parser.update_runtime_variables()
self.assertTrue("mock_define" in self.config.parser.variables.keys())
self.assertTrue("mock_define_too" in self.config.parser.variables.keys())
self.assertTrue("mock_value" in self.config.parser.variables.keys())
self.assertTrue("mock_define" in self.config.parser.variables)
self.assertTrue("mock_define_too" in self.config.parser.variables)
self.assertTrue("mock_value" in self.config.parser.variables)
self.assertEqual("TRUE", self.config.parser.variables["mock_value"])
self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys())
self.assertTrue("MOCK_NOSEP" in self.config.parser.variables)
self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"])
@mock.patch("certbot_apache._internal.configurator.util.run_script")

View File

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

View File

@@ -1,10 +1,14 @@
"""Test for certbot_apache._internal.http_01."""
import unittest
import errno
from typing import List
import mock
try:
import mock
except ImportError: # pragma: no cover
from unittest import mock # type: ignore
from acme import challenges
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
from certbot import achallenges
from certbot import errors
from certbot.compat import filesystem
@@ -20,10 +24,10 @@ 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 = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge]
self.achalls: List[achallenges.KeyAuthorizationAnnotatedChallenge] = []
vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
# Takes the vhosts for encryption-example.demo, certbot.demo
@@ -40,8 +44,8 @@ class ApacheHttp01Test(util.ApacheTest):
modules = ["ssl", "rewrite", "authz_core", "authz_host"]
for mod in modules:
self.config.parser.modules.add("mod_{0}.c".format(mod))
self.config.parser.modules.add(mod + "_module")
self.config.parser.modules["mod_{0}.c".format(mod)] = None
self.config.parser.modules[mod + "_module"] = None
from certbot_apache._internal.http_01 import ApacheHttp01
self.http = ApacheHttp01(self.config)
@@ -52,24 +56,24 @@ class ApacheHttp01Test(util.ApacheTest):
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
def test_enable_modules_apache_2_2(self, mock_enmod):
self.config.version = (2, 2)
self.config.parser.modules.remove("authz_host_module")
self.config.parser.modules.remove("mod_authz_host.c")
del self.config.parser.modules["authz_host_module"]
del self.config.parser.modules["mod_authz_host.c"]
enmod_calls = self.common_enable_modules_test(mock_enmod)
self.assertEqual(enmod_calls[0][0][0], "authz_host")
@mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod")
def test_enable_modules_apache_2_4(self, mock_enmod):
self.config.parser.modules.remove("authz_core_module")
self.config.parser.modules.remove("mod_authz_core.c")
del self.config.parser.modules["authz_core_module"]
del self.config.parser.modules["mod_authz_host.c"]
enmod_calls = self.common_enable_modules_test(mock_enmod)
self.assertEqual(enmod_calls[0][0][0], "authz_core")
def common_enable_modules_test(self, mock_enmod):
"""Tests enabling mod_rewrite and other modules."""
self.config.parser.modules.remove("rewrite_module")
self.config.parser.modules.remove("mod_rewrite.c")
del self.config.parser.modules["rewrite_module"]
del self.config.parser.modules["mod_rewrite.c"]
self.http.prepare_http01_modules()
@@ -197,6 +201,12 @@ class ApacheHttp01Test(util.ApacheTest):
self.assertTrue(os.path.exists(challenge_dir))
@mock.patch("certbot_apache._internal.http_01.filesystem.makedirs")
def test_failed_makedirs(self, mock_makedirs):
mock_makedirs.side_effect = OSError(errno.EACCES, "msg")
self.http.add_chall(self.achalls[0])
self.assertRaises(errors.PluginError, self.http.perform)
def _test_challenge_conf(self):
with open(self.http.challenge_conf_pre) as f:
pre_conf_contents = f.read()

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