Compare commits

...

162 Commits

Author SHA1 Message Date
Adrien Ferrand
9ae12c010c Merge branch 'master' into drop-python37 2023-10-10 16:05:44 +02:00
Mattias Ellert
01d129dfca Adapt to Python 3.12.0rc2 (#9764)
The warning message changed from "datetime.utcfromtimestamp() is deprecated"
to "datetime.datetime.utcfromtimestamp() is deprecated"
2023-10-10 16:02:24 +02:00
Adrien Ferrand
8691d3a657 Update oldest constaints and compatibility tests runtime 2023-10-10 12:32:55 +02:00
Adrien Ferrand
82813f3f36 Update requirements 2023-10-10 10:36:05 +02:00
Adrien Ferrand
10f58b212b Check for venv generation 2023-10-10 10:02:26 +02:00
Adrien Ferrand
8347ddb436 Fix lint and test 2023-10-09 16:32:22 +02:00
Adrien Ferrand
35513f4d0a Drop Python 3.7 support 2023-10-09 15:38:57 +02:00
Brad Warren
61773be971 update changelog for configargparse (#9789)
I'd like to do a bug fix release for https://github.com/certbot/certbot/issues/9786. If we're doing one, I figure we may as well flag this change from https://github.com/certbot/certbot/pull/9784 too.
2023-10-06 11:39:19 -07:00
Brad Warren
5849ff73fb update pinned dependencies (#9788)
This fixes the security alerts those with access can see at https://github.com/certbot/certbot/security/dependabot.
2023-10-06 11:39:08 -07:00
Brad Warren
4e60a0d03a Fix dnsimple typo (#9787)
Fixes https://github.com/certbot/certbot/issues/9786.
2023-10-05 13:15:30 -07:00
Alexis
44046c70c3 Merge pull request #9762 from certbot/docs/yaml-config
Add YAML files for Readthedocs requirements
2023-10-05 09:24:02 -07:00
Will Greenberg
02efc8c5ca Bump setup.py's ConfigArgParse version (#9784)
I neglected to do this during #9678, so looks like some pip installs
are failing to get the minimum required version.
2023-10-04 16:22:13 -07:00
Brad Warren
0862e05754 Merge pull request #9780 from certbot/candidate-2.7.0
Candidate 2.7.0
2023-10-03 12:46:06 -07:00
Will Greenberg
08d1979bcb Bump version to 2.8.0 2023-10-03 11:22:04 -07:00
Will Greenberg
6c66764f25 Add contents to certbot/CHANGELOG.md for next version 2023-10-03 11:22:04 -07:00
Will Greenberg
c4642c2dfe Release 2.7.0 2023-10-03 11:22:02 -07:00
Will Greenberg
bcb7f371e3 Update changelog for 2.7.0 release 2023-10-03 11:21:15 -07:00
Adrien Ferrand
732a3ac962 Refactor Lexicon-based DNS plugins (#9746)
* Refactor Lexicon-based DNS plugins and upgrade minimal version of Lexicon

* Relax filterwarning to comply with envs where boto3 is not installed

* Update pinned dependencies

* Use our previous method to deprecate part of modules

* Safe import internally

* Add changelog

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2023-09-25 15:15:04 -07:00
Alexis
694c758db7 Swap out with updated AMI image IDs (#9770)
- Add comments for other OS
2023-09-20 13:03:53 -07:00
zoracon
f5cb0a156b Remove duplicate file
- was in the incorrect directory
2023-09-20 12:58:36 -07:00
zoracon
4178e8ffc4 Merge branch 'master' of https://github.com/certbot/certbot into docs/yaml-config 2023-09-20 12:55:59 -07:00
zoracon
a3353b5c42 Revert "Swap out with updated AMI image IDs"
This reverts commit 24c8825d22.
2023-09-20 12:55:48 -07:00
zoracon
24c8825d22 Swap out with updated AMI image IDs
- Add comments for other OS
2023-09-20 12:46:33 -07:00
Adrien Ferrand
23f9dfc655 Migrate pkg_resources usages to importlib.metadata (#9749)
* Migrate entrypoint logic from pkg_resources to importlib.metadata

* Usage of importlib_metadata up to Python 3.9 to align API behavior to Python 3.10

---------

Co-authored-by: Adrien Ferrand <adrien.ferrand@amadeus.com>
Co-authored-by: Adrien Ferrand <adrien.ferrand@arteris.com>
2023-09-12 08:18:57 -07:00
Adrien Ferrand
cc359dab46 Migrate pkg_resources usages to importlib.resources (#9748)
* Migrate pkg_resources API related to resources to importlib_resources

* Fix lint and mypy + pin lexicon

* Update filterwarnings

* Update oldest tests requirements

* Update pinned dependencies

* Fix for modern versions of python

* Fix assets load in nginx integration tests

* Fix a warning

* Isolate static generation from importlib.resource into a private function

---------

Co-authored-by: Adrien Ferrand <adrien.ferrand@amadeus.com>
2023-09-07 11:38:44 -07:00
zoracon
89902e26bf Add YAML files for Readthedocs requirements 2023-08-31 16:06:47 -07:00
Paulo Dias
b1978ff188 dns-google: fix condition to don't use private dns zones (#9744)
* dns-google: fix condition to don't use private dns zones

* update MD

* Fix condition

* fix condition

* update testdata

* fix identation

* update tests

* update changelog

* Update dns_google.py

* add test for split horizon dns google

* add dnsName to managed zones
2023-08-27 01:19:38 +02:00
Brad Warren
579b39dce1 Fix docs (#9755)
* update quickstart and remove os import

* simplify theme use

* list sphinx_rtd_theme as extension

Our docs builds failed last night, presumably because #9754 updated `sphinx_rtd_theme` which changed some unknown thing.

Looking into it, our usage of this project was very unconventional. Following the code comment I deleted in this PR to https://docs.readthedocs.io/en/stable/faq.html#i-want-to-use-the-read-the-docs-theme-locally, simple instructions are given to put the following in your `conf.py` file:
```
extensions = [
    ...
    'sphinx_rtd_theme',
]

html_theme = "sphinx_rtd_theme"
```
I did this instead of the more complicated logic we were using and all builds passed locally. I also triggered a build on readthedocs with these changes which also passed.
2023-08-25 12:22:14 -07:00
Brad Warren
9b4b99f3e8 Update dependencies (#9754)
This takes care of the dependabot alerts those with access can see at https://github.com/certbot/certbot/security/dependabot.

Pinning back `cython` is needed because without it, our full test suite will fail when trying to build `pyyaml` on ARM systems.
2023-08-24 17:05:54 -07:00
Alexis
3e84b94308 Merge pull request #9739 from certbot/CI/workflow-patch-forks
Skip Mattermost Job for Forked Repos
2023-07-24 13:12:28 -07:00
Alexis
2cb2cb0575 Update merged.yaml 2023-07-24 12:11:40 -07:00
Mattias Ellert
ddd4b31b1c Mock in Python 3.12 finds more errors in mock syntax. (#9734) 2023-07-21 16:44:48 -07:00
Will Greenberg
68d812e6dd Add pytz as a dependency for integration tests (#9737) 2023-07-19 13:10:35 -07:00
Mattias Ellert
6effedc2f4 Do not call deprecated datetime.utcnow() and datetime.utcfromtimestamp() (#9735)
* Do not call deprecated datetime.utcnow() and datetime.utcfromtimestamp()

* Ignore DeprecationWarnings from importing dependencies

$ python3 -Wdefault
Python 3.12.0b4 (main, Jul 12 2023, 00:00:00) [GCC 13.1.1 20230614 (Red Hat 13.1.1-4)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pkg_resources
/usr/lib/python3.12/site-packages/pkg_resources/__init__.py:121: DeprecationWarning: pkg_resources is deprecated as an API
  warnings.warn("pkg_resources is deprecated as an API", DeprecationWarning)
>>> import pytz
/usr/lib/python3.12/site-packages/pytz/tzinfo.py:27: DeprecationWarning: datetime.utcfromtimestamp() is deprecated and scheduled for removal in a future version. Use timezone-aware objects to represent datetimes in UTC: datetime.fromtimestamp(timestamp, datetime.UTC).
  _epoch = datetime.utcfromtimestamp(0)

* Used pytz.UTC consistently for clarity
2023-07-18 15:44:25 -07:00
Zachary Ware
c31d3a2cfd Update PyPI links (#9733)
Switch from the legacy pypi.python.org/pypi/ to the canonical pypi.org/project/
2023-07-15 15:58:00 -07:00
Brad Warren
e6572e695b deprecate python 3.7 support (#9730) 2023-07-10 08:06:04 +10:00
Brad Warren
a7674548ab Fix snap builds (#9729)
* release script change

* fix setup.py

* match setup.py logic
2023-07-07 13:14:05 +10:00
Michael Cassaniti
436b7fbe28 post renewal hook: Add RENEWED_DOMAINS and FAILED_DOMAINS as environment variables (#9724)
* renewal hook: Add RENEWED_DOMAINS and FAILED_DOMAINS as environment variables

* renewal hook: Updated documentation

* renewal hook: Updated CHANGELOG

* renew post hook: Add limit on variable sizes
2023-07-06 06:56:31 -07:00
alexzorin
d0e11c81b1 Repin dependencies to fix security alerts (#9717)
* repin current

* repin oldest

* csr must have version set to zero

* only set PIP_USE_PEP517 for macOS

* experiment with brew update git failure workaround
2023-07-05 06:40:02 -07:00
Leon G
4fc4d536c1 Add LooseVersion class with risky comparison, deprecate parse_loose_version (#9646)
* Replace parse_loose_version with LooseVersion

* Fix LooseVersion docstring

* Strengthen LooseVersion comparison

* Update changelog
2023-06-21 07:57:50 -07:00
alexzorin
b1e5efac3c disco: print the name of the plugin if it fails to load (#9719) 2023-06-16 08:26:15 -07:00
alexzorin
539d48d438 letstest: replace buster with bullseye (#9718) 2023-06-12 06:56:53 -07:00
Alex Gaynor
ae6268ea3c Remove workaround that's not relevant since py2 isn't supported (#9716) 2023-06-11 06:44:58 +10:00
Charles Hong
2d8a274eb5 Update using.rst (#9714)
Add a link to the third-party DNS authentication plugin using SOLIDserver
2023-06-08 18:40:58 +10:00
Remi Rampin
ff8afe827b Update GitHub repo location letsencrypt -> certbot (#9713)
* Update GitHub repo location letsencrypt -> certbot

* Revert changes to CHANGELOG
2023-06-08 10:27:28 +10:00
Will Greenberg
468f4749b8 Revert change to NamespaceConfig's constructor (#9709)
* Revert change to NamespaceConfig's constructor

NamespaceConfig's argument sources dict is now set with a method,
and raises a runtime error if one isn't set when set_by_user() is
called.

* Actually update CHANGELOG to reflect the set_by_user changes

* linter appeasement

* configuration: update docs, add test

This test ensures that calling `set_by_user` without an initialized
sources dict raises a RuntimeError.
2023-06-07 15:16:14 -07:00
Will Greenberg
a5d223d1e5 Replace (most) global state in cli/__init__.py (#9678)
* Rewrite helpful_test to appease the linter

* Use public interface to access argparse sources dict

* HelpfulParser builds ArgumentSources dict, stores it in NamespaceConfig

After arguments/config files/user prompted input have been parsed, we
build a mapping of Namespace options to an ArgumentSource value. These
generally come from argparse's builtin "source_to_settings" dict, but
we also add a source value representing dynamic values set at runtime.

This dict is then passed to NamespaceConfig, which can then be queried
directly or via the "set_by_user" method, which replaces the global
"set_by_cli" and "option_was_set" functions.

* Use NamespaceConfig.set_by_user instead of set_by_cli/option_was_set

This involves passing the NamespaceConfig around to more functions
than before, removes the need for most of the global state shenanigans
needed by set_by_cli and friends.

* Set runtime config values on the NamespaceConfig object

This'll correctly mark them as being "runtime" values in the
ArgumentSources dict

* Bump oldest configargparse version

We need a version that has get_source_to_settings_dict()

* Add more cli unit tests, use ArgumentSource.DEFAULT by default

One of the tests revealed that ConfigArgParse's source dict excludes
arguments it considers unimportant/irrelevant. We now mark all arguments
as having a DEFAULT source by default, and update them otherwise.

* Mark more argument sources as RUNTIME

* Removes some redundant helpful_test.py, moves one to cli_test.py

We were already testing most of these cases in cli_test.py, only
with a more complete HelpfulArgumentParser setup. And since the hsts/no-hsts
test was manually performing the kind of argument adding that cli
already does out of the box, I figured the cli tests were a more natural
place for it.

* appease the linter

* Various fixups from review

* Add windows compatability fix

* Add test ensuring relevant_values behaves properly

* Build sources dict in a more predictable manner

The dict is now built in a defined order: first defaults, then config
files, then env vars, then command line args. This way we eliminate the
possibility of undefined behavior if configargparse puts an arg's entry
in multiple source dicts.

* remove superfluous update to sources dict

* remove duplicate constant defines, resolve circular import situation
2023-05-30 17:12:51 -07:00
Alexis
b5661e84e8 Update README.rst (#9693)
* Update README.rst

Updating with newer info about keys and server support and removing redundant wording

* Adjust from feedback
2023-05-23 10:58:40 +10:00
alexzorin
aa270b37a2 docs: add "Choosing dependency versions" to contributing.rst (#9681)
* docs: add "Choosing dependency versions" to contributing.rst

* change a word
2023-05-12 07:52:02 +10:00
Brad Warren
35209d921d bump stale limit (#9691) 2023-05-09 17:06:47 -07:00
Brad Warren
0ac8e10c85 Merge pull request #9692 from certbot/candidate-2.6.0
Release Certbot 2.6.0
2023-05-09 15:52:33 -07:00
Erica Portnoy
36bfddbf4e Bump version to 2.7.0 2023-05-09 12:45:29 -07:00
Erica Portnoy
721c4665e6 Add contents to certbot/CHANGELOG.md for next version 2023-05-09 12:45:29 -07:00
Erica Portnoy
013621d04e Release 2.6.0 2023-05-09 12:45:28 -07:00
Erica Portnoy
e0e2bfe13a Update changelog for 2.6.0 release 2023-05-09 12:44:36 -07:00
alexzorin
d2e2a92cdd update farm tests (#9687)
* letstest: -ubuntu18.04 +centos9stream +debian11

* letstest: username for centos 9 stream is ec2-user

This is mentioned on https://centos.org/download/aws-images/

* ensure mod_ssl is installed

in centos 9 stream, apache has to be restarted after mod_ssl is
installed, or the snakeoil certificates will not be present and
apache won't start.

this also removes nghttp2 being installed as the relevant bug
is long fixed.
2023-05-08 14:37:14 -07:00
alexzorin
6e52695faa dns-rfc2136: add test coverage for PR #9672 (#9684)
* dns-rfc2136: add test coverage for PR #9672

* fix compatibility with oldest dnspython

* rename test to be more descriptive

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

---------

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2023-05-08 14:34:40 -07:00
Brad Warren
5b5a2efdc9 squelch warnings (#9689) 2023-05-04 10:42:49 -07:00
✨ Q (it/its) ✨
8a0b0f63de Support unknown ACME challenge types (#9680)
This is, to my knowledge, an entirely inconsequential PR to add support for entirely novel challenge types.

Presently in the [`challb_to_achall` function](399b932a86/certbot/certbot/_internal/auth_handler.py (L367)) if the challenge type is not of a type known to certbot an error is thrown. This check is mostly pointless as an authenticator would not request a challenge unknown to it. This check does however forbid any plugins from supporting entirely novel challenges not of the key authorisation form.

* support unknown ACME challenge types

* add to changelog

* update tests

---------

Co-authored-by: Brad Warren <bmw@eff.org>
2023-04-26 08:23:11 -07:00
alexzorin
10fba2ee3f docs: clarify --dry-run documentation (#9683)
* remove pointless paragraph about --server and wildcards

* docs: update help text for --dry-run and --staging

* docs: update "Changing the ACME Server" for --dry-run

* add note about webserver reloads
2023-04-25 16:43:18 -07:00
alexzorin
67f14f177b ignore invalid plugin selection choices (#9665)
* plugins: ensure --installer/--authenticator is properly filtered

* fix windows failure in test
2023-04-25 11:27:32 +10:00
Phil Martin
f378ec4a0f Optionally sign initial SOA query (#9672)
* Optionally sign initial SOA query

Added configuration file option to enable signing of the initial SOA query when determining the authoritative nameserver for the zone. Default is disabled.

* Better handling of sign_query configuration and fix lint issues

* Update str casting to match 5503d12395

* Update certbot/CHANGELOG.md

Co-authored-by: alexzorin <alex@zorin.au>

* Update certbot/CHANGELOG.md

Co-authored-by: alexzorin <alex@zorin.au>

* Update dns_rfc2136.py

Updated with feedback from certbot/certbot#9672

---------

Co-authored-by: alexzorin <alex@zorin.au>
2023-04-25 11:25:57 +10:00
Jawshua
b0d0a83277 google: use Application Default Credentials where available (#9670)
* google: use Application Default Credentials where available

* Updated custom role documentation
2023-04-22 07:58:18 +10:00
Will Greenberg
399b932a86 Merge pull request #9673 from certbot/types-dns-common-get
types: CredentialsConfiguration.conf can return None
2023-04-17 17:45:00 -07:00
Alex Zorin
b9ec3155f7 amend rtype 2023-04-18 08:14:11 +10:00
Alex Zorin
ef5f4cae04 fix cast formatting 2023-04-18 08:13:28 +10:00
Brad Warren
31094bc547 rewrite coverage tests (#9669)
In addition to the speed improvements in CI, the speed improvements locally with both this https://github.com/certbot/certbot/pull/9666 which this builds on is even more significant. After it's been run once so it's had a chance to set up the different virtual environments, `tox` locally now takes 39 seconds on my laptop when it used to take 137 seconds.
2023-04-17 13:01:00 -07:00
Niek Peeters
f41673982d validate lineage name (#9644)
Fixes #6127.

* Added lineage name validity check

* Verify lineage name validity before obtaining certificate

* Added linage name limitation to cli help

* Update documentation on certificate name

* Added lineage name validation to changelog

* Use filepath seperators to determine lineagename validity

* Add unittest for private choose_lineagename method

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2023-04-17 12:55:20 -07:00
Brad Warren
996cc20cd7 remove unused envrc (#9677) 2023-04-17 02:17:55 +00:00
Brad Warren
20ccf8c9c9 remove development dockerfile (#9676) 2023-04-17 12:14:25 +10:00
Alex Zorin
5503d12395 types: CredentialsConfiguration.conf can return None 2023-04-16 10:43:00 +10:00
Brad Warren
4740e20725 Rewrite tox config (#9666)
* rewrite tox config

* fix apacheconftest-with-pebble deps

* more fixes

* more fixes

* move comment up

* fix mock location

* bump cffi

* update oldest constraints

* Revert "fix mock location"

This reverts commit 561037bfad.

* fix apache test

* fix server cleanup

* fix some leaky sockets

* stop leaking sockets

* change less

* Update tox.ini

Co-authored-by: alexzorin <alex@zorin.id.au>

* Update tox.ini

Co-authored-by: alexzorin <alex@zorin.id.au>

* tweak contributing doc

---------

Co-authored-by: alexzorin <alex@zorin.id.au>
2023-04-16 10:30:59 +10:00
Brad Warren
dc05b4da7a Increase stale operations per run (#9668)
* increase operations per run

* update comment
2023-04-13 09:18:24 +10:00
Brad Warren
5149dfd96e Add some missing type libraries for mypy (#9657)
* add some missing types

* install pkg-config

* install pkg-config for docker too

* add pkg-config to plugins

* pkg-config when cryptography may need to be built

* deps cleanup

* more comments

* more tweaks
2023-04-09 11:49:08 +10:00
humanoid2050
9ee1eee219 Build with buildkit (#9628)
* generate multiarch images for non-architecture tags

* Update documentation related to multiarch Docker

* Remove qemu and switch to build via buildkit

* Move to multistage Dockerfile

* refactor docker script arg parsing and fix merge bugs

* removed unnecessary testing script and fixed function name

* improved quoting in shell scripts

---------

Co-authored-by: humanoid2050 <humanoid2050@monolith>
Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
Co-authored-by: humanoid2050 <humanoid2050@katana>
Co-authored-by: Brad Warren <bmw@eff.org>
2023-04-08 12:22:16 -07:00
Brad Warren
7a68b29140 update min cryptography (#9663) 2023-04-07 10:28:17 +10:00
Brad Warren
a78073812c Always "pipstrap" when running pip_install.py (#9658)
Fixes https://github.com/certbot/certbot/issues/7921.

In all cases when we run `pip_install.py`, we first run `pipstrap.py`. This PR combines these two steps for convenience and to make always doing that less error prone. This will also help me with some of the `tox.ini` refactoring I'm planning to do.

I ran the full test suite on everything and tested the release script changes locally.

This change shouldn't have any effect on cryptography's setup because they install `certbot[test]` which depends on pip, setuptools, and wheel.

* always pipstrap

* use pip_install.py during releases
2023-04-05 16:43:26 -07:00
alexzorin
45327d00c4 Merge pull request #9624 from certbot/more-stale
Increase stale frequency
2023-04-06 09:24:25 +10:00
alexzorin
163bb9e945 Merge pull request #9656 from certbot/candidate-2.5.0
Update files from 2.5.0 release
2023-04-05 08:12:54 +10:00
Brad Warren
1fe201e320 Bump version to 2.6.0 2023-04-04 08:07:50 -07:00
Brad Warren
86c51acb91 Add contents to certbot/CHANGELOG.md for next version 2023-04-04 08:07:50 -07:00
Brad Warren
3c667e8fff Release 2.5.0 2023-04-04 08:07:49 -07:00
Brad Warren
10ba4ea349 Update changelog for 2.5.0 release 2023-04-04 08:06:41 -07:00
alexzorin
df85c25da8 add dns_route53_propagation_seconds to DEPRECATED_OPTIONS (#9652)
Fixes #9651.
2023-04-03 10:20:22 -07:00
Bishop Clark
1bd6bef42f Update __init__.py (#9653)
Removed two en_US meta-commas to cure the spliced sentences.
2023-04-02 11:15:08 +10:00
Brad Warren
097af18417 remove readlink (#9649) 2023-03-31 10:02:12 +11:00
Brad Warren
608d731e2b Make mypy pass on our tests (#9648)
* make mypy pass on our tests

* fix grammar
2023-03-31 09:20:44 +11:00
Brad Warren
63fb97d8de add changelog entry (#9641) 2023-03-28 22:29:08 +00:00
Brad Warren
c987c3f3aa remove boulder-integration.conf.sh (#9640) 2023-03-28 22:23:16 +00:00
Brad Warren
ba3dde9384 make dns tests internal (#9639) 2023-03-29 09:10:34 +11:00
Brad Warren
9e30e8afa9 make tests internal (#9638)
This is the certbot-nginx version of #9625.
2023-03-28 15:01:31 -07:00
Brad Warren
ed6bbde38f Make apache tests internal (#9637)
This is the certbot-apache version of #9625.
2023-03-28 14:55:44 -07:00
Brad Warren
16cc1a74be make certbot tests internal (#9627)
This is the Certbot version of https://github.com/certbot/certbot/pull/9625.
2023-03-28 14:44:55 -07:00
Brad Warren
6832521272 Make acme tests internal (#9625)
This is a first step towards implementing the plan I described at https://github.com/certbot/certbot/issues/7909#issuecomment-1448675456 which got a +1 from both Erica and Will. Similar changes for our other packages will be made in followup PRs to try and make this easier to review.

It may be helpful to look at https://github.com/certbot/certbot/pull/7600 when reviewing this PR where we did something similar in the past.

The value of `ignore-paths` in `.pylintrc` should work on Windows based on https://pylint.readthedocs.io/en/latest/user_guide/configuration/all-options.html#ignore-paths and the fact that on macOS/linux, changing path delimiters to `\` still causes these directories to be ignored.

I started testing this for mypy as well, but mypy doesn't current pass for us on Windows so I didn't bother and took this opportunity to remove it from the default environments in `tox.ini`. I'll update https://github.com/certbot/certbot/issues/7803 to mention that the value of `exclude` in `mypy.ini` may need to be tweaked if anyone works on that issue.

* make acme tests internal

* no mypy-win
2023-03-28 14:02:33 -07:00
alexzorin
e10e549a95 renewal: fix key_type not being preserved on <v1.25.0 renewal configs (#9636)
Fixes #9635.
2023-03-28 08:44:19 -07:00
Brad Warren
208ef4eb94 remove CERTBOT_NO_PIN (#9634)
Adrien and I added this is in https://github.com/certbot/certbot/pull/6590 in response to https://github.com/certbot/certbot/issues/6582 which I wrote. I now personally think these tests are way more trouble than they're worth.

In almost all cases, the versions pinned in `tools/requirements.txt` are used. The two exceptions to this that come to mind are users using OS packages and pip. In the former, the version of our dependencies is picked by the OS and do not change much on most systems. As for pip, [we only "support it on a best effort basis"](https://eff-certbot.readthedocs.io/en/stable/install.html#alternative-2-pip).

Even for pip users, I'm not convinced this buys us much other than frequent test failures. We have our tests configured to error on all Python warnings and [we regularly update `tools/requirements.txt`](https://github.com/certbot/certbot/commits/master/tools/requirements.txt). Due to that, assuming our dependencies follow normal conventions, we should have a chance to fix things in response to planned API changes long before they make their way to our users. I do not think it is necessary for our tests to break immediately after an API is deprecated.

I think almost all other failures due to these tests are caused by upstream bugs. In my experience, almost all of them will sort themselves out pretty quickly. I think that responding to those that are not or planned API changes we somehow missed can be addressed when `tools/requirements.txt` is updated or when someone opens an issue. I personally don't think blocking releases or causing our nightly tests to fail is at all worth it here. I think removing this frequent cause of test failures makes things just a little bit easier for Certbot devs without costing us much of anything.
2023-03-27 17:01:27 -07:00
alexzorin
f004383582 avoid pyOpenSSL 23.1.0 (#9631)
Our `NO_PIN` test [fails](https://dev.azure.com/certbot/certbot/_build/results?buildId=6542&view=logs&j=ce03f7c1-1e3f-5d55-28be-f084e7c62a50&t=597fea95-d44e-53a2-5b71-76ed20bd4dde) due to https://github.com/pyca/pyopenssl/issues/1199.

This PR might strictly not be necessary once a new release of `PyOpenSSL` is available? I suppose it depends whether they yank the release.
2023-03-27 11:27:48 -07:00
alexzorin
fbf7f1f4d1 logging: use logger.warning for DeprecatedArgumentAction (#9630) 2023-03-27 11:13:16 -07:00
alexzorin
a16f316b8f logging: increase pre-argparse logging level to WARNING (#9629) 2023-03-27 11:12:18 -07:00
alexzorin
8037321ad7 dns-route53: deprecate --dns-route53-propagation-seconds (#9619) 2023-03-24 07:28:13 +11:00
Brad Warren
6a666b0323 increase stale frequency 2023-03-23 10:11:20 -07:00
Christoph Anton Mitterer
7ce1f1789e improve documentation about shell commands in hooks (#9612)
Fixes #9611.

Signed-off-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
2023-03-23 07:28:26 -07:00
Anna Glasgall
8e28e36178 Add async interface for finalization to acme.client.ClientV2 (#9622)
* Add async interface for finalization to acme.client.ClientV2

Add `begin_order_finalization()`/`poll_finalization()` to
`acme.client.ClientV2`, which are directly analogous to
`answer_challenge()`/`poll_authorizations()`. This allows us to
finalize an order and then later poll for its completion as separate
steps.

* Address code review feedback

Rename `begin_order_finalization` -> `begin_finalization` and tweak
wording of changelog entry
2023-03-23 11:09:14 +11:00
Anna Glasgall
5d5dc429c4 acme.messages.OrderResource: Make roundtrippable through JSON (#9617)
Right now if you to_json() an `OrderResource` and later deserialize
it, the `AuthorizationResource` objects don't come back through the
round-trip (they just get de-jsonified as frozendicts and worse, they
can't even be passed to `AuthorizationResource.from_json` because
frozendicts aren't dicts). In addition, the `csr_pem` field gets
encoded as an array of integers, which definitely does not get
de-jsonified into what we want.

Fix these by adding an encoder to `authorizations` and encoder and
decoder to `csr_pem`.
2023-03-21 10:49:39 -07:00
Brad Warren
c07b5efb7f Rewrite lock_test.py (#9614)
`lock_test.py` is a weird, heavily customized, standalone testing relic that's giving me trouble because the name currently conflicts with `certbot/tests/lock_test.py`. Moving `certbot/tests` inside the Certbot package as discussed at https://github.com/certbot/certbot/issues/7909#issuecomment-1448675456 would avoid this, however, this is at least somewhat blocked on getting that test code passing lint and mypy checks again because we run those checks on the entirety of the Certbot package 🙃 Since `lock_test.py` could probably stand to be rewritten/refactored anyway, I took this approach.

What I did is I rewrote something largely equivalent to `lock_test.py` inside Certbot's unit tests. I chose not to do this in `certbot-ci` because its not necessary to have an ACME server available. We're no longer explicitly testing things with the nginx plugin here like we were in `lock_test.py`, however, we are checking that `prepare` is called on the plugin at the right time and I added comments about the importance of checking that we lock the directory during the call to `prepare` in the Apache and nginx test code.

As a bonus, this fixes https://github.com/certbot/certbot/issues/8121.
2023-03-15 12:54:20 -07:00
Will Greenberg
7a6752a68e Merge pull request #9601 from certbot/yaml/merge-notifications
Create Workflow for Merge Notifications
2023-03-08 14:07:53 -08:00
Alexis
40486f3ab4 Fix indentation error 2023-03-08 09:22:17 -08:00
Brad Warren
e3880b8912 Merge pull request #9608 from certbot/candidate-2.4.0
Candidate 2.4.0
2023-03-07 14:59:14 -08:00
Will Greenberg
242c96527b Bump version to 2.5.0 2023-03-07 13:18:07 -08:00
Will Greenberg
336ca91c26 Add contents to certbot/CHANGELOG.md for next version 2023-03-07 13:18:07 -08:00
Will Greenberg
eeb88c0855 Release 2.4.0 2023-03-07 13:18:06 -08:00
Will Greenberg
b586672f78 Update changelog for 2.4.0 release 2023-03-07 13:17:25 -08:00
Alexis
6c22e29875 Update to include sanitization for JSON file 2023-03-07 12:42:39 -08:00
alexzorin
397f6bc20a docs: link for certbot-standalone-nfq plugin (#9607) 2023-03-07 05:40:39 -08:00
Will Greenberg
48b499a38f Merge pull request #9600 from certbot/yaml/github-notify-weekly
Create Weekly Notification Message for Certbot Team
2023-03-06 12:18:15 -08:00
Alexis
9f5e666702 Add a space for link selection 2023-03-03 11:09:56 -08:00
Alexis
077cfb7861 Update .github/workflows/merged.yaml
Escape any double quotes in the Title that may come in
2023-03-03 11:06:27 -08:00
Brad Warren
da01846d34 Remove unnecessary unittest (#9596)
Now that we're using pytest more aggressively, I think we should start transitioning our tests to that style rather than continuing to use unittest. This PR removes some unnecessary uses of unittest I found.

I kept the test classes (while removing the inheritance from unittest.TestCase) where I felt like it added structure or logical grouping of tests.

I verified that pytest still finds all the tests in both this branch and master by running commands like:
```
pytest $(git diff --name-only master | grep -v windows_installer_integration_tests)
```
2023-03-02 06:48:40 -08:00
Brad Warren
cd467f2ce1 remove nose cruft (#9603) 2023-03-02 10:28:20 +11:00
alexzorin
bdd81a5961 google: ignore declare_namespace deprecation warnings (#9604)
Fixes the nopin build on master.

---------

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2023-03-01 22:41:00 +00:00
Alexis
87f8eca033 Update .github/workflows/notify_weekly.yaml
Remove trailing char
2023-02-28 17:39:17 -08:00
Alexis
10b0fb6da0 Update .github/workflows/notify_weekly.yaml 2023-02-28 17:38:43 -08:00
Alexis
44be66eed9 Update .github/workflows/notify_weekly.yaml 2023-02-28 17:38:23 -08:00
Alexis
bd3e3d1af1 Update .github/workflows/merged.yaml 2023-02-28 17:35:56 -08:00
Alexis
97dd95329d Update merged.yaml 2023-02-28 13:31:06 -08:00
Alexis
676863760a Create Workflow for Merge Notifications
Sent to Mattermost Channel for Certbot Team to check and be generally aware of more granular merge events.
2023-02-28 13:15:41 -08:00
Alexis
173b832a8f Create Weekly Notification Message for Certbot Team
Composes Mattermost message to team channel
2023-02-28 12:38:53 -08:00
Mads Jensen
34e6b1e74d Remove redundant OCSPTestOpenSSL.tearDown (#9599) 2023-02-27 15:45:19 -08:00
Will Greenberg
72be7999ed Merge pull request #9597 from certbot/revert-nginx-lua-detection
nginx: fix performance regression caused by #9475
2023-02-27 11:32:57 -08:00
Alex Zorin
1cb8c389b7 note the issue in the CHANGELOG 2023-02-25 09:32:38 +11:00
Alex Zorin
7c840a7dfd Revert "nginx: on encountering lua directives, produce a better warning (#9475)"
This reverts commit c178fa8c0b.
2023-02-25 08:45:19 +11:00
humanoid2050
a42cffc351 generate multiarch images for non-architecture tags (#9586)
* generate multiarch images for non-architecture tags

* lock docker build to legacy docker buider, and bugfix

* rename deploy.sh to deploy_by_arch.sh

* Update documentation related to multiarch Docker

* Consistent IFS value with respect to other scripts

Co-authored-by: humanoid2050 <humanoid2050@monolith>
Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2023-02-22 17:53:45 -08:00
ohemorange
1cb48eca58 Remove update symlinks (#9592)
* Add deprecation warning when update_symlinks is run

* Remove information about update_symlinks from help

* ignore our own warning and remove unused imports

* Update changelog
2023-02-22 12:16:28 -08:00
Brad Warren
2a7eeef176 Polish snap_build.py (#9584)
I wanted to try to make our tooling's messaging about it a little clearer.

While fixing my typo/bad English, we happened to hit a "Chroot problem" failure! See the logs for the CI first attempt at https://dev.azure.com/certbot/certbot/_build/results?buildId=6416&view=results.

Looking at these logs, I noticed three things:

1. This message I added is sometimes printed many times because we're still processing output from snapcraft. See https://dev.azure.com/certbot/certbot/_build/results?buildId=6416&view=logs&j=f44d40a4-7318-5ffe-762c-ae4557889284&s=1dfbc15b-7d0f-52a9-b1da-b17592bf94f8&t=07786725-57f8-5198-4d13-ea77f640bd5c&l=565.
2. snapcraft is complaining that we should be using --build-for now instead of --build-on. See https://dev.azure.com/certbot/certbot/_build/results?buildId=6416&view=logs&j=f44d40a4-7318-5ffe-762c-ae4557889284&s=1dfbc15b-7d0f-52a9-b1da-b17592bf94f8&t=07786725-57f8-5198-4d13-ea77f640bd5c&l=472.
3. Us canceling the Certbot build due to a "Chroot problem" happened 3 times in 3 seconds which seems very unlikely. See https://dev.azure.com/certbot/certbot/_build/results?buildId=6416&view=logs&j=f44d40a4-7318-5ffe-762c-ae4557889284&s=1dfbc15b-7d0f-52a9-b1da-b17592bf94f8&t=07786725-57f8-5198-4d13-ea77f640bd5c&l=587. I looked at the builds on launchpad and I only saw one Certbot build. I think what's happening is this code is causing the old build state to be reported so we error immediately.
I fixed all of these things in my follow up commits.

* polish chroot problem messaging

* only execute branch once
2023-02-16 11:17:47 -08:00
Brad Warren
a3c9371dc5 Use pytest assertions (#9585)
* run unittest2pytest

The command used here was `unittest2pytest -nw acme/tests certbot*/tests`.

* fix with pytest.raises

* add parens to fix refactoring

* <= not <
2023-02-16 16:02:02 +11:00
Brad Warren
fedb0b5f9d Merge pull request #9581 from certbot/candidate-2.3.0
Release candidate 2.3.0
2023-02-14 15:13:44 -08:00
Will Greenberg
1b904b62c9 Enable stale issue tracker (#9580) 2023-02-14 15:13:13 -08:00
Will Greenberg
941119f05b Bump version to 2.4.0 2023-02-14 12:44:32 -08:00
Will Greenberg
5d34a4d982 Add contents to certbot/CHANGELOG.md for next version 2023-02-14 12:44:32 -08:00
Will Greenberg
d4b2d3202b Release 2.3.0 2023-02-14 12:44:31 -08:00
Will Greenberg
1fe2d671cb Update changelog for 2.3.0 release 2023-02-14 12:42:21 -08:00
Will Greenberg
9960c1907b Merge pull request #9577 from certbot/update-stale
Disable stale for PRs
2023-02-14 12:28:09 -08:00
Brad Warren
1da113d7d6 tweak comment 2023-02-14 08:55:07 -08:00
Brad Warren
64800c2b1f disable stale for PRs 2023-02-14 08:51:17 -08:00
Brad Warren
dc07dfd07b Automatically run test files with pytest (#9576)
* Switch to pytest

git grep -l unittest.main | xargs sed -i 's/unittest.main()/sys.exit(pytest.main([__file__]))/g'
git ls-files -m | xargs -I {} sh -c "echo 'import sys\nimport pytest' >> '{}'"
isort --float-to-top .

* add pytest dep

* use sys.argv
2023-02-14 06:44:42 +11:00
alexzorin
057524aa52 certbot-ci: fix crash in and simplify manual_http_hooks (#9570)
There is a typo (`request` instead of `requests`) in the `auth.py` generated by this function:

d792d39813/certbot-ci/certbot_integration_tests/utils/misc.py (L184-L191)

that has [never ever succeeded](https://gist.github.com/alexzorin/ff2686b7123cea49f1e4107d1e7d95f5#file-master-log-L203-L208).

Moreover, this polling code is not necessary because `create_http_server` already polls until the HTTP server to come up, and the file we wrote to disk is guaranteed is immediately visible by the web server anyway.

* certbot-ci: fix crash in and simplify manual_http_hooks

* remove superfluous format argument

* remove unused argument
2023-02-10 11:15:42 -08:00
Brad Warren
1bb09da270 Update and run isort (#9573)
I want to use isort as part of https://github.com/certbot/certbot/issues/9572 because I want to do it programmatically, however, I felt like the config needed to be tweaked a bit due to it not understanding what is and is not our own code.

This PR updates the isort config so it recognizes our own modules and runs `isort .` from the root of the repo to update everything.

* update isort config

* run "isort ."
2023-02-10 10:51:20 -08:00
Will Greenberg
d8d177ce72 Merge pull request #9575 from certbot/no-more-1.x
Simplify release process and push everyone to 2.0
2023-02-10 10:10:13 -08:00
Brad Warren
7d4535a836 tweak cloudxns condition 2023-02-09 19:35:37 -08:00
Brad Warren
c32da71e8a fail faster if we try to use candidate 2023-02-09 18:56:43 -08:00
Brad Warren
ca5f13d0e3 update snapcraft credentials 2023-02-09 18:55:29 -08:00
Brad Warren
91005a0422 always push to beta 2023-02-09 18:45:06 -08:00
Brad Warren
f91d3ca828 remove 1.32.x deps 2023-02-09 18:42:41 -08:00
Brad Warren
3512d15dff Remove most progressive release tooling 2023-02-09 18:41:16 -08:00
Will Greenberg
caad4d93d0 Merge pull request #9574 from certbot/remove-test-test-code
Remove code testing testing code
2023-02-09 12:03:06 -08:00
Brad Warren
aac02bef35 Remove code testing testing code 2023-02-08 20:55:59 -08:00
alexzorin
cbb4c871c2 docs: document reconfigure verb (#9563)
* docs: document reconfigure verb

* expand on the flags relevant to reconfigure

* Update phrasing

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

---------

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2023-02-09 13:14:32 +11:00
ohemorange
99956ecab9 Fix typo direcory --> directory in --run-deploy-hooks help (#9568) 2023-02-08 16:16:28 -08:00
Brad Warren
d792d39813 reset set_by_cli between each test (#9567) 2023-02-09 09:24:07 +11:00
Brad Warren
f5ea5d453e fix requests-toolbelt warning (#9569) 2023-02-09 09:21:07 +11:00
Alexis
cd9ee996a8 Create SECURITY.md (#9566) 2023-02-09 07:12:15 +11:00
alexzorin
99184daff6 repin cryptography for openssl security update (#9565)
* repin cryptography for openssl security update

https://www.openssl.org/news/secadv/20230207.txt
https://cryptography.io/en/latest/changelog/#v39-0-1

* fix type hints

* remove outdated comments
2023-02-08 11:17:44 -08:00
584 changed files with 10231 additions and 9741 deletions

View File

@@ -15,11 +15,5 @@ stages:
- template: templates/stages/changelog-stage.yml
- template: templates/stages/deploy-stage.yml
parameters:
${{ if startsWith(variables['Build.SourceBranchName'], 'v2') }}:
snapReleaseChannel: beta
${{ elseif startsWith(variables['Build.SourceBranchName'], 'v1') }}:
snapReleaseChannel: candidate
${{ else }}:
# This should never happen
snapReleaseChannel: somethingInvalid
snapReleaseChannel: beta
- template: templates/stages/notify-failure-stage.yml

View File

@@ -8,31 +8,22 @@ jobs:
- group: certbot-common
strategy:
matrix:
linux-py38:
PYTHON_VERSION: 3.8
TOXENV: py38
linux-py39:
PYTHON_VERSION: 3.9
TOXENV: py39
linux-py310:
PYTHON_VERSION: 3.10
TOXENV: py310
linux-py37-nopin:
PYTHON_VERSION: 3.7
TOXENV: py37
CERTBOT_NO_PIN: 1
linux-isolated:
TOXENV: 'isolated-{acme,certbot,apache,cloudflare,digitalocean,dnsimple,dnsmadeeasy,gehirn,google,linode,luadns,nsone,ovh,rfc2136,route53,sakuracloud,nginx}'
linux-boulder-v2-integration-certbot-oldest:
PYTHON_VERSION: 3.7
PYTHON_VERSION: 3.8
TOXENV: integration-certbot-oldest
ACME_SERVER: boulder-v2
linux-boulder-v2-integration-nginx-oldest:
PYTHON_VERSION: 3.7
PYTHON_VERSION: 3.8
TOXENV: integration-nginx-oldest
ACME_SERVER: boulder-v2
linux-boulder-v2-py37-integration:
PYTHON_VERSION: 3.7
TOXENV: integration
ACME_SERVER: boulder-v2
linux-boulder-v2-py38-integration:
PYTHON_VERSION: 3.8
TOXENV: integration
@@ -55,8 +46,6 @@ jobs:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: integration-dns-rfc2136
docker-dev:
TOXENV: docker_dev
le-modification:
IMAGE_NAME: ubuntu-22.04
TOXENV: modification

View File

@@ -4,12 +4,12 @@ jobs:
vmImage: ubuntu-22.04
strategy:
matrix:
amd64:
DOCKER_ARCH: amd64
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
# The default timeout of 60 minutes is a little low for compiling
# cryptography on ARM architectures.
timeoutInMinutes: 180
@@ -32,24 +32,28 @@ jobs:
path: $(Build.ArtifactStagingDirectory)
artifact: docker_$(DOCKER_ARCH)
displayName: Store Docker artifact
- job: docker_run
- job: docker_test
dependsOn: docker_build
pool:
vmImage: ubuntu-22.04
strategy:
matrix:
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: docker_amd64
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
- 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
set -e && tools/docker/test.sh $(dockerTag) $DOCKER_ARCH
displayName: Run integration tests for Docker images
- job: installer_build
pool:
@@ -62,7 +66,6 @@ jobs:
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: |
@@ -99,7 +102,6 @@ jobs:
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
@@ -171,7 +173,6 @@ jobs:
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
@@ -209,7 +210,6 @@ jobs:
- 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: |

View File

@@ -12,14 +12,13 @@ parameters:
values:
- edge
- beta
- candidate
jobs:
# This job relies on credentials used to publish the Certbot snaps. This
# credential file was created by running:
#
# snapcraft logout
# snapcraft export-login --channels=candidate,beta,edge snapcraft.cfg
# snapcraft export-login --channels=beta,edge snapcraft.cfg
# (provide the shared snapcraft credentials when prompted)
#
# Then the file was added as a secure file in Azure pipelines
@@ -30,7 +29,7 @@ jobs:
# https://docs.microsoft.com/en-us/azure/devops/pipelines/library/secure-files?view=azure-devops#q-how-do-i-authorize-a-secure-file-for-use-in-a-specific-pipeline.
#
# This file has a maximum lifetime of one year and the current file will
# expire on 2023-09-06. The file will need to be updated before then to
# expire on 2024-02-10. 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

View File

@@ -4,17 +4,22 @@ jobs:
PYTHON_VERSION: 3.11
strategy:
matrix:
macos-py37-cover:
macos-py38-cover:
IMAGE_NAME: macOS-12
PYTHON_VERSION: 3.7
PYTHON_VERSION: 3.8
TOXENV: cover
# As of pip 23.1.0, builds started failing on macOS unless this flag was set.
# See https://github.com/certbot/certbot/pull/9717#issuecomment-1610861794.
PIP_USE_PEP517: "true"
macos-cover:
IMAGE_NAME: macOS-12
TOXENV: cover
windows-py37:
# See explanation under macos-py38-cover.
PIP_USE_PEP517: "true"
windows-py38:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.7
TOXENV: py37-win
PYTHON_VERSION: 3.8
TOXENV: py-win
windows-py39-cover:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.9
@@ -23,18 +28,14 @@ jobs:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.9
TOXENV: integration-certbot
linux-oldest-tests-1:
linux-oldest:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.7
TOXENV: '{acme,apache,apache-v2,certbot}-oldest'
linux-oldest-tests-2:
PYTHON_VERSION: 3.8
TOXENV: oldest
linux-py38:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.7
TOXENV: '{dns,nginx}-oldest'
linux-py37:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.7
TOXENV: py37
PYTHON_VERSION: 3.8
TOXENV: py38
linux-cover:
IMAGE_NAME: ubuntu-22.04
TOXENV: cover
@@ -43,7 +44,7 @@ jobs:
TOXENV: lint-posix
linux-mypy:
IMAGE_NAME: ubuntu-22.04
TOXENV: mypy-posix
TOXENV: mypy
linux-integration:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8

View File

@@ -11,17 +11,33 @@ stages:
- template: ../jobs/snap-deploy-job.yml
parameters:
snapReleaseChannel: ${{ parameters.snapReleaseChannel }}
- job: publish_docker
# The credentials used in the following jobs 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. The authentication used when
# creating this service account was a personal access token
# rather than a password to bypass 2FA. When Brad set this up,
# Azure Pipelines failed to verify the credentials with an error
# like "access is forbidden with a JWT issued from a personal
# access token", but after saving them without verification, the
# access token worked when the pipeline actually ran. "Grant
# access to all pipelines" should also be checked on the service
# account. The access token can be deleted on Docker Hub if
# these credentials need to be revoked.
- job: publish_docker_by_arch
pool:
vmImage: ubuntu-22.04
strategy:
matrix:
amd64:
DOCKER_ARCH: amd64
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
steps:
- task: DownloadPipelineArtifact@2
inputs:
@@ -33,22 +49,19 @@ stages:
- 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. The authentication used when
# creating this service account was a personal access token
# rather than a password to bypass 2FA. When Brad set this up,
# Azure Pipelines failed to verify the credentials with an error
# like "access is forbidden with a JWT issued from a personal
# access token", but after saving them without verification, the
# access token worked when the pipeline actually ran. "Grant
# access to all pipelines" should also be checked on the service
# account. The access token can be deleted on Docker Hub if
# these credentials need to be revoked.
containerRegistry: docker-hub
displayName: Login to Docker Hub
- bash: set -e && tools/docker/deploy.sh $(dockerTag) $DOCKER_ARCH
displayName: Deploy the Docker images
- bash: set -e && tools/docker/deploy_images.sh $(dockerTag) $DOCKER_ARCH
displayName: Deploy the Docker images by architecture
- job: publish_docker_multiarch
dependsOn: publish_docker_by_arch
pool:
vmImage: ubuntu-22.04
steps:
- task: Docker@2
inputs:
command: login
containerRegistry: docker-hub
displayName: Login to Docker Hub
- bash: set -e && tools/docker/deploy_manifests.sh $(dockerTag) all
displayName: Deploy the Docker multiarch manifests

View File

@@ -1,9 +1,17 @@
# This does not include the dependencies needed to build cryptography. See
# https://cryptography.io/en/latest/installation/
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.
#
# We untap homebrew/core and homebrew/cask and unset HOMEBREW_NO_INSTALL_FROM_API (which
# is set by the CI macOS env) because GitHub has been having issues, making these jobs
# fail on git clones: https://github.com/orgs/Homebrew/discussions/4612.
- bash: |
set -e
unset HOMEBREW_NO_INSTALL_FROM_API
brew untap homebrew/core homebrew/cask
brew update
brew install augeas
condition: startswith(variables['IMAGE_NAME'], 'macOS')
@@ -12,14 +20,8 @@ steps:
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
python3-dev \
gcc \
libaugeas0 \
libssl-dev \
libffi-dev \
ca-certificates \
nginx-light \
openssl
nginx-light
sudo systemctl stop nginx
sudo sysctl net.ipv4.ip_unprivileged_port_start=0
condition: startswith(variables['IMAGE_NAME'], 'ubuntu')
@@ -28,17 +30,9 @@ steps:
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
python3 tools/pipstrap.py
python3 tools/pip_install.py -I tox virtualenv
python3 tools/pip_install.py tox
displayName: Install runtime dependencies
- task: DownloadSecureFile@1
name: testFarmPem

View File

@@ -1,5 +1,24 @@
[run]
omit = */setup.py
source =
acme
certbot
certbot-apache
certbot-dns-cloudflare
certbot-dns-digitalocean
certbot-dns-dnsimple
certbot-dns-dnsmadeeasy
certbot-dns-gehirn
certbot-dns-google
certbot-dns-linode
certbot-dns-luadns
certbot-dns-nsone
certbot-dns-ovh
certbot-dns-rfc2136
certbot-dns-route53
certbot-dns-sakuracloud
certbot-nginx
[report]
omit = */setup.py
show_missing = True

12
.envrc
View File

@@ -1,12 +0,0 @@
# 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

32
.github/workflows/merged.yaml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Merge Event
on:
pull_request:
types:
- closed
jobs:
if_merged:
# Forked repos can not access Mattermost secret.
if: github.event.pull_request.merged == true && !github.event.pull_request.head.repo.fork
runs-on: ubuntu-latest
steps:
- name: Create Mattermost Message
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#example-of-a-script-injection-attack
env:
NUMBER: ${{ github.event.number }}
PR_URL: https://github.com/${{ github.repository }}/pull/${{ github.event.number }}
REPO: ${{ github.repository }}
USER: ${{ github.actor }}
TITLE: ${{ github.event.pull_request.title }}
run: |
jq --null-input \
--arg number "$NUMBER" \
--arg pr_url "$PR_URL" \
--arg repo "$REPO" \
--arg user "$USER" \
--arg title "$TITLE" \
'{ "text": "[\($repo)] | [\($title) #\($number)](\($pr_url)) was merged into master by \($user)" }' > mattermost.json
- uses: mattermost/action-mattermost-notify@master
env:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_MERGE_WEBHOOK }}

25
.github/workflows/notify_weekly.yaml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Weekly Github Update
on:
schedule:
# Every week on Thursday @ 13:00
- cron: "0 13 * * 4"
jobs:
send-mattermost-message:
runs-on: ubuntu-latest
steps:
- name: Create Mattermost Message
run: |
DATE=$(date --date="7 days ago" +"%Y-%m-%d")
MERGED_URL="https://github.com/pulls?q=merged%3A%3E${DATE}+org%3Acertbot"
UPDATED_URL="https://github.com/pulls?q=updated%3A%3E${DATE}+org%3Acertbot"
echo "{\"text\":\"## Updates Across Certbot Repos\n\n
- Certbot team members SHOULD look at: [link]($MERGED_URL)\n\n
- Certbot team members MAY also want to look at: [link]($UPDATED_URL)\n\n
- Want to Discuss something today? Place it [here](https://docs.google.com/document/d/17YMUbtC1yg6MfiTMwT8zVm9LmO-cuGVBom0qFn8XJBM/edit?usp=sharing) and we can meet today on Zoom.\n\n
- The key words SHOULD and MAY in this message are to be interpreted as described in [RFC 8147](https://www.rfc-editor.org/rfc/rfc8174). \"
}" > mattermost.json
- uses: mattermost/action-mattermost-notify@master
env:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}

View File

@@ -1,7 +1,7 @@
name: Update Stale Issues
on:
schedule:
# Run at midnight every night
# Run 1:24AM every night
- cron: '24 1 * * *'
permissions:
issues: write
@@ -11,15 +11,18 @@ jobs:
steps:
- uses: actions/stale@v6
with:
# REMOVEME: dry run to see if this works
debug-only: true
# Idle number of days before marking issues stale
days-before-issue-stale: 365
# Never mark PRs as stale
days-before-pr-stale: -1
# Idle number of days before closing stale issues
days-before-issue-close: 30
# Never close PRs
days-before-pr-close: -1
# Ignore issues with an assignee
exempt-all-issue-assignees: true
@@ -38,5 +41,7 @@ jobs:
should be reopened, please open a new issue with a link to this one and we'll
take a look.
# Limit the number of actions per hour, from 1-30. Default is 30
operations-per-run: 30
# Limit the number of actions per run. As of writing this, GitHub's
# rate limit is 1000 requests per hour so we're still a ways off. See
# https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#rate-limits-for-requests-from-github-actions.
operations-per-run: 180

View File

@@ -4,3 +4,4 @@ force_sort_within_sections=True
force_single_line=True
order_by_type=False
line_length=400
src_paths=acme/acme,acme/tests,certbot*/certbot*,certbot*/tests

View File

@@ -48,7 +48,10 @@ ignore=CVS
# ignore-list. The regex matches against paths and can be in Posix or Windows
# format. Because '\' represents the directory delimiter on Windows systems, it
# can't be used as an escape character.
ignore-paths=
# CERTBOT COMMENT
# Changing this line back to the default of `ignore-paths=` is being tracked by
# https://github.com/certbot/certbot/issues/7908.
ignore-paths=.*/_internal/tests/
# Files or directories matching the regular expression patterns are skipped.
# The regex matches against base names, not paths. The default value ignores

View File

@@ -23,6 +23,7 @@ Authors
* [amplifi](https://github.com/amplifi)
* [Andrew Murray](https://github.com/radarhere)
* [Andrzej Górski](https://github.com/andrzej3393)
* [Anna Glasgall](https://github.com/aglasgall)
* [Anselm Levskaya](https://github.com/levskaya)
* [Antoine Jacoutot](https://github.com/ajacoutot)
* [April King](https://github.com/april)
@@ -122,6 +123,7 @@ Authors
* [James Balazs](https://github.com/jamesbalazs)
* [James Kasten](https://github.com/jdkasten)
* [Jason Grinblat](https://github.com/ptychomancer)
* [Jawshua](https://github.com/jawshua)
* [Jay Faulkner](https://github.com/jayofdoom)
* [J.C. Jones](https://github.com/jcjones)
* [Jeff Hodges](https://github.com/jmhodges)
@@ -152,6 +154,7 @@ Authors
* [LeCoyote](https://github.com/LeCoyote)
* [Lee Watson](https://github.com/TheReverend403)
* [Leo Famulari](https://github.com/lfam)
* [Leon G](https://github.com/LeonGr)
* [lf](https://github.com/lf-)
* [Liam Marshall](https://github.com/liamim)
* [Lior Sabag](https://github.com/liorsbg)
@@ -206,6 +209,7 @@ Authors
* [Patrick Heppler](https://github.com/PatrickHeppler)
* [Paul Buonopane](https://github.com/Zenexer)
* [Paul Feitzinger](https://github.com/pfeyz)
* [Paulo Dias](https://github.com/paulojmdias)
* [Pavan Gupta](https://github.com/pavgup)
* [Pavel Pavlov](https://github.com/ghost355)
* [Peter Conrad](https://github.com/pconrad-fb)
@@ -219,12 +223,14 @@ Authors
* [Piotr Kasprzyk](https://github.com/kwadrat)
* [Prayag Verma](https://github.com/pra85)
* [Preston Locke](https://github.com/Preston12321)
* [Q Misell][https://magicalcodewit.ch]
* [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)
* [Rémy Léone](https://github.com/sieben)
* [Richard Barnes](https://github.com/r-barnes)
* [Richard Harman](https://github.com/warewolf)
* [Richard Panek](https://github.com/kernelpanek)
* [Robert Buchholz](https://github.com/rbu)
* [Robert Dailey](https://github.com/pahrohfit)

View File

@@ -1,21 +0,0 @@
# This Dockerfile builds an image for development.
FROM ubuntu:focal
# Note: this only exposes the port to other docker containers.
EXPOSE 80 443
WORKDIR /opt/certbot/src
COPY . .
RUN apt-get update && \
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="../venv" python3 tools/venv.py
ENV PATH /opt/certbot/venv/bin:$PATH

5
SECURITY.md Normal file
View File

@@ -0,0 +1,5 @@
# Security Policy
## Reporting a Vulnerability
Security vulnerabilities can be reported using GitHub's [private vulnerability reporting tool](https://github.com/certbot/certbot/security/advisories/new).

33
acme/.readthedocs.yaml Normal file
View File

@@ -0,0 +1,33 @@
# Read the Docs configuration file for Sphinx projects
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# You can also specify other tool versions:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs
# builder: "dirhtml"
# Fail on all warnings to avoid broken references
fail_on_warning: true
# Optionally build your docs in additional formats such as PDF and ePub
# formats:
- pdf
- epub
# Optional but recommended, declare the Python requirements required
# to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt

View File

@@ -3,7 +3,7 @@ include README.rst
include pytest.ini
recursive-include docs *
recursive-include examples *
recursive-include tests *
recursive-include acme/_internal/tests/testdata *
include acme/py.typed
global-exclude __pycache__
global-exclude *.py[cod]

View File

@@ -0,0 +1 @@
"""acme's internal implementation"""

View File

@@ -0,0 +1 @@
"""acme tests"""

View File

@@ -1,16 +1,17 @@
"""Tests for acme.challenges."""
import urllib.parse as urllib_parse
import sys
import unittest
from unittest import mock
import urllib.parse as urllib_parse
import josepy as jose
import OpenSSL
import requests
from josepy.jwk import JWKEC
import OpenSSL
import pytest
import requests
from acme import errors
import test_util
from acme._internal.tests import test_util
CERT = test_util.load_comparable_cert('cert.pem')
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
@@ -22,7 +23,7 @@ class ChallengeTest(unittest.TestCase):
from acme.challenges import Challenge
from acme.challenges import UnrecognizedChallenge
chall = UnrecognizedChallenge({"type": "foo"})
self.assertEqual(chall, Challenge.from_json(chall.jobj))
assert chall == Challenge.from_json(chall.jobj)
class UnrecognizedChallengeTest(unittest.TestCase):
@@ -33,12 +34,11 @@ class UnrecognizedChallengeTest(unittest.TestCase):
self.chall = UnrecognizedChallenge(self.jobj)
def test_to_partial_json(self):
self.assertEqual(self.jobj, self.chall.to_partial_json())
assert self.jobj == self.chall.to_partial_json()
def test_from_json(self):
from acme.challenges import UnrecognizedChallenge
self.assertEqual(
self.chall, UnrecognizedChallenge.from_json(self.jobj))
assert self.chall == UnrecognizedChallenge.from_json(self.jobj)
class KeyAuthorizationChallengeResponseTest(unittest.TestCase):
@@ -54,26 +54,26 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY')
self.assertTrue(response.verify(self.chall, KEY.public_key()))
assert response.verify(self.chall, KEY.public_key())
def test_verify_wrong_token(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='bar.oKGqedy-b-acd5eoybm2f-NVFxvyOoET5CNy3xnv8WY')
self.assertFalse(response.verify(self.chall, KEY.public_key()))
assert not response.verify(self.chall, KEY.public_key())
def test_verify_wrong_thumbprint(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='foo.oKGqedy-b-acd5eoybm2f-NVFxv')
self.assertFalse(response.verify(self.chall, KEY.public_key()))
assert not response.verify(self.chall, KEY.public_key())
def test_verify_wrong_form(self):
from acme.challenges import KeyAuthorizationChallengeResponse
response = KeyAuthorizationChallengeResponse(
key_authorization='.foo.oKGqedy-b-acd5eoybm2f-'
'NVFxvyOoET5CNy3xnv8WY')
self.assertFalse(response.verify(self.chall, KEY.public_key()))
assert not response.verify(self.chall, KEY.public_key())
class DNS01ResponseTest(unittest.TestCase):
@@ -92,11 +92,11 @@ class DNS01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({}, self.msg.to_partial_json())
assert {} == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import DNS01Response
self.assertEqual(self.msg, DNS01Response.from_json(self.jmsg))
assert self.msg == DNS01Response.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import DNS01Response
@@ -106,12 +106,12 @@ class DNS01ResponseTest(unittest.TestCase):
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
public_key = key2.public_key()
verified = self.response.simple_verify(self.chall, "local", public_key)
self.assertFalse(verified)
assert not verified
def test_simple_verify_success(self):
public_key = KEY.public_key()
verified = self.response.simple_verify(self.chall, "local", public_key)
self.assertTrue(verified)
assert verified
class DNS01Test(unittest.TestCase):
@@ -126,20 +126,19 @@ class DNS01Test(unittest.TestCase):
}
def test_validation_domain_name(self):
self.assertEqual('_acme-challenge.www.example.com',
self.msg.validation_domain_name('www.example.com'))
assert '_acme-challenge.www.example.com' == \
self.msg.validation_domain_name('www.example.com')
def test_validation(self):
self.assertEqual(
"rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk",
self.msg.validation(KEY))
assert "rAa7iIg4K2y63fvUhCfy8dP1Xl7wEhmQq0oChTcE3Zk" == \
self.msg.validation(KEY)
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
assert self.jmsg == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import DNS01
self.assertEqual(self.msg, DNS01.from_json(self.jmsg))
assert self.msg == DNS01.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import DNS01
@@ -162,12 +161,11 @@ class HTTP01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({}, self.msg.to_partial_json())
assert {} == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import HTTP01Response
self.assertEqual(
self.msg, HTTP01Response.from_json(self.jmsg))
assert self.msg == HTTP01Response.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import HTTP01Response
@@ -181,16 +179,16 @@ class HTTP01ResponseTest(unittest.TestCase):
def test_simple_verify_good_validation(self, mock_get):
validation = self.chall.validation(KEY)
mock_get.return_value = mock.MagicMock(text=validation)
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
assert self.response.simple_verify(
self.chall, "local", KEY.public_key())
mock_get.assert_called_once_with(self.chall.uri("local"), verify=False,
timeout=mock.ANY)
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_bad_validation(self, mock_get):
mock_get.return_value = mock.MagicMock(text="!")
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
assert not self.response.simple_verify(
self.chall, "local", KEY.public_key())
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_whitespace_validation(self, mock_get):
@@ -198,24 +196,24 @@ class HTTP01ResponseTest(unittest.TestCase):
mock_get.return_value = mock.MagicMock(
text=(self.chall.validation(KEY) +
HTTP01Response.WHITESPACE_CUTSET))
self.assertTrue(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
assert self.response.simple_verify(
self.chall, "local", KEY.public_key())
mock_get.assert_called_once_with(self.chall.uri("local"), verify=False,
timeout=mock.ANY)
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_connection_error(self, mock_get):
mock_get.side_effect = requests.exceptions.RequestException
self.assertFalse(self.response.simple_verify(
self.chall, "local", KEY.public_key()))
assert not self.response.simple_verify(
self.chall, "local", KEY.public_key())
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_port(self, mock_get):
self.response.simple_verify(
self.chall, domain="local",
account_public_key=KEY.public_key(), port=8080)
self.assertEqual("local:8080", urllib_parse.urlparse(
mock_get.mock_calls[0][1][0]).netloc)
assert "local:8080" == urllib_parse.urlparse(
mock_get.mock_calls[0][1][0]).netloc
@mock.patch("acme.challenges.requests.get")
def test_simple_verify_timeout(self, mock_get):
@@ -241,30 +239,28 @@ class HTTP01Test(unittest.TestCase):
}
def test_path(self):
self.assertEqual(self.msg.path, '/.well-known/acme-challenge/'
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')
assert self.msg.path == '/.well-known/acme-challenge/' \
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'
def test_uri(self):
self.assertEqual(
'http://example.com/.well-known/acme-challenge/'
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA',
self.msg.uri('example.com'))
assert 'http://example.com/.well-known/acme-challenge/' \
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA' == \
self.msg.uri('example.com')
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
assert self.jmsg == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import HTTP01
self.assertEqual(self.msg, HTTP01.from_json(self.jmsg))
assert self.msg == HTTP01.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import HTTP01
hash(HTTP01.from_json(self.jmsg))
def test_good_token(self):
self.assertTrue(self.msg.good_token)
self.assertFalse(
self.msg.update(token=b'..').good_token)
assert self.msg.good_token
assert not self.msg.update(token=b'..').good_token
class TLSALPN01ResponseTest(unittest.TestCase):
@@ -284,11 +280,11 @@ class TLSALPN01ResponseTest(unittest.TestCase):
}
def test_to_partial_json(self):
self.assertEqual({}, self.response.to_partial_json())
assert {} == self.response.to_partial_json()
def test_from_json(self):
from acme.challenges import TLSALPN01Response
self.assertEqual(self.response, TLSALPN01Response.from_json(self.jmsg))
assert self.response == TLSALPN01Response.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import TLSALPN01Response
@@ -297,23 +293,23 @@ class TLSALPN01ResponseTest(unittest.TestCase):
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))
assert key1 == key2
assert 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))
assert isinstance(key, OpenSSL.crypto.PKey)
assert 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')))
assert not 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))
assert key1 == key2
assert not 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'))
@@ -322,10 +318,9 @@ class TLSALPN01ResponseTest(unittest.TestCase):
@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(
assert mock.sentinel.verification == self.response.simple_verify(
self.chall, self.domain, KEY.public_key(),
cert=mock.sentinel.cert))
cert=mock.sentinel.cert)
mock_verify_cert.assert_called_once_with(
self.response, self.domain, mock.sentinel.cert)
@@ -347,8 +342,8 @@ class TLSALPN01ResponseTest(unittest.TestCase):
@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()))
assert not self.response.simple_verify(
self.chall, self.domain, KEY.public_key())
class TLSALPN01Test(unittest.TestCase):
@@ -363,11 +358,11 @@ class TLSALPN01Test(unittest.TestCase):
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
assert self.jmsg == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import TLSALPN01
self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg))
assert self.msg == TLSALPN01.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import TLSALPN01
@@ -376,14 +371,14 @@ class TLSALPN01Test(unittest.TestCase):
def test_from_json_invalid_token_length(self):
from acme.challenges import TLSALPN01
self.jmsg['token'] = jose.encode_b64jose(b'abcd')
self.assertRaises(
jose.DeserializationError, TLSALPN01.from_json, self.jmsg)
with pytest.raises(jose.DeserializationError):
TLSALPN01.from_json(self.jmsg)
@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))
assert ('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)
@@ -400,11 +395,11 @@ class DNSTest(unittest.TestCase):
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg, self.msg.to_partial_json())
assert self.jmsg == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import DNS
self.assertEqual(self.msg, DNS.from_json(self.jmsg))
assert self.msg == DNS.from_json(self.jmsg)
def test_from_json_hashable(self):
from acme.challenges import DNS
@@ -414,13 +409,13 @@ class DNSTest(unittest.TestCase):
ec_key_secp384r1 = JWKEC(key=test_util.load_ecdsa_private_key('ec_secp384r1_key.pem'))
for key, alg in [(KEY, jose.RS256), (ec_key_secp384r1, jose.ES384)]:
with self.subTest(key=key, alg=alg):
self.assertTrue(self.msg.check_validation(
self.msg.gen_validation(key, alg=alg), key.public_key()))
assert self.msg.check_validation(
self.msg.gen_validation(key, alg=alg), key.public_key())
def test_gen_check_validation_wrong_key(self):
key2 = jose.JWKRSA.load(test_util.load_vector('rsa1024_key.pem'))
self.assertFalse(self.msg.check_validation(
self.msg.gen_validation(KEY), key2.public_key()))
assert not self.msg.check_validation(
self.msg.gen_validation(KEY), key2.public_key())
def test_check_validation_wrong_payload(self):
validations = tuple(
@@ -428,33 +423,32 @@ class DNSTest(unittest.TestCase):
for payload in (b'', b'{}')
)
for validation in validations:
self.assertFalse(self.msg.check_validation(
validation, KEY.public_key()))
assert not self.msg.check_validation(
validation, KEY.public_key())
def test_check_validation_wrong_fields(self):
bad_validation = jose.JWS.sign(
payload=self.msg.update(
token=b'x' * 20).json_dumps().encode('utf-8'),
alg=jose.RS256, key=KEY)
self.assertFalse(self.msg.check_validation(bad_validation, KEY.public_key()))
assert not self.msg.check_validation(bad_validation, KEY.public_key())
def test_gen_response(self):
with mock.patch('acme.challenges.DNS.gen_validation') as mock_gen:
mock_gen.return_value = mock.sentinel.validation
response = self.msg.gen_response(KEY)
from acme.challenges import DNSResponse
self.assertIsInstance(response, DNSResponse)
self.assertEqual(response.validation, mock.sentinel.validation)
assert isinstance(response, DNSResponse)
assert response.validation == mock.sentinel.validation
def test_validation_domain_name(self):
self.assertEqual('_acme-challenge.le.wtf', self.msg.validation_domain_name('le.wtf'))
assert '_acme-challenge.le.wtf' == self.msg.validation_domain_name('le.wtf')
def test_validation_domain_name_ecdsa(self):
ec_key_secp384r1 = JWKEC(key=test_util.load_ecdsa_private_key('ec_secp384r1_key.pem'))
self.assertIs(self.msg.check_validation(
assert self.msg.check_validation(
self.msg.gen_validation(ec_key_secp384r1, alg=jose.ES384),
ec_key_secp384r1.public_key()), True
)
ec_key_secp384r1.public_key()) is True
class DNSResponseTest(unittest.TestCase):
@@ -479,18 +473,18 @@ class DNSResponseTest(unittest.TestCase):
}
def test_to_partial_json(self):
self.assertEqual(self.jmsg_to, self.msg.to_partial_json())
assert self.jmsg_to == self.msg.to_partial_json()
def test_from_json(self):
from acme.challenges import DNSResponse
self.assertEqual(self.msg, DNSResponse.from_json(self.jmsg_from))
assert self.msg == DNSResponse.from_json(self.jmsg_from)
def test_from_json_hashable(self):
from acme.challenges import DNSResponse
hash(DNSResponse.from_json(self.jmsg_from))
def test_check_validation(self):
self.assertTrue(self.msg.check_validation(self.chall, KEY.public_key()))
assert self.msg.check_validation(self.chall, KEY.public_key())
class JWSPayloadRFC8555Compliant(unittest.TestCase):
@@ -502,8 +496,8 @@ class JWSPayloadRFC8555Compliant(unittest.TestCase):
jobj = challenge_body.json_dumps(indent=2).encode()
# RFC8555 states that challenge responses must have an empty payload.
self.assertEqual(jobj, b'{}')
assert jobj == b'{}'
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -4,21 +4,23 @@ import copy
import datetime
import http.client as http_client
import json
import unittest
import sys
from typing import Dict
import unittest
from unittest import mock
import josepy as jose
import pytest
import requests
from acme import challenges
from acme import errors
from acme import jws as acme_jws
from acme import messages
from acme._internal.tests import messages_test
from acme._internal.tests import test_util
from acme.client import ClientNetwork
from acme.client import ClientV2
import messages_test
import test_util
CERT_SAN_PEM = test_util.load_vector('cert-san.pem')
CSR_MIXED_PEM = test_util.load_vector('csr-mixed.pem')
@@ -101,7 +103,7 @@ class ClientV2Test(unittest.TestCase):
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = self.regr.uri
self.assertEqual(self.regr, self.client.new_account(self.new_reg))
assert self.regr == self.client.new_account(self.new_reg)
def test_new_account_tos_link(self):
self.response.status_code = http_client.CREATED
@@ -111,14 +113,15 @@ class ClientV2Test(unittest.TestCase):
'terms-of-service': {'url': 'https://www.letsencrypt-demo.org/tos'},
})
self.assertEqual(self.client.new_account(self.new_reg).terms_of_service,
'https://www.letsencrypt-demo.org/tos')
assert self.client.new_account(self.new_reg).terms_of_service == \
'https://www.letsencrypt-demo.org/tos'
def test_new_account_conflict(self):
self.response.status_code = http_client.OK
self.response.headers['Location'] = self.regr.uri
self.assertRaises(errors.ConflictError, self.client.new_account, self.new_reg)
with pytest.raises(errors.ConflictError):
self.client.new_account(self.new_reg)
def test_deactivate_account(self):
deactivated_regr = self.regr.update(
@@ -126,16 +129,16 @@ class ClientV2Test(unittest.TestCase):
self.response.json.return_value = deactivated_regr.body.to_json()
self.response.status_code = http_client.OK
self.response.headers['Location'] = self.regr.uri
self.assertEqual(self.client.deactivate_registration(self.regr), deactivated_regr)
assert self.client.deactivate_registration(self.regr) == deactivated_regr
def test_deactivate_authorization(self):
deactivated_authz = self.authzr.update(
body=self.authzr.body.update(status=messages.STATUS_DEACTIVATED))
self.response.json.return_value = deactivated_authz.body.to_json()
authzr = self.client.deactivate_authorization(self.authzr)
self.assertEqual(deactivated_authz.body, authzr.body)
self.assertEqual(self.client.net.post.call_count, 1)
self.assertIn(self.authzr.uri, self.net.post.call_args_list[0][0])
assert deactivated_authz.body == authzr.body
assert self.client.net.post.call_count == 1
assert self.authzr.uri in self.net.post.call_args_list[0][0]
def test_new_order(self):
order_response = copy.deepcopy(self.response)
@@ -153,7 +156,7 @@ class ClientV2Test(unittest.TestCase):
with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get:
mock_post_as_get.side_effect = (authz_response, authz_response2)
self.assertEqual(self.client.new_order(CSR_MIXED_PEM), self.orderr)
assert self.client.new_order(CSR_MIXED_PEM) == self.orderr
def test_answer_challege(self):
self.response.links['up'] = {'url': self.challr.authzr_uri}
@@ -161,13 +164,12 @@ class ClientV2Test(unittest.TestCase):
chall_response = challenges.DNSResponse(validation=None)
self.client.answer_challenge(self.challr.body, chall_response)
self.assertRaises(errors.UnexpectedUpdate, self.client.answer_challenge,
self.challr.body.update(uri='foo'), chall_response)
with pytest.raises(errors.UnexpectedUpdate):
self.client.answer_challenge(self.challr.body.update(uri='foo'), chall_response)
def test_answer_challenge_missing_next(self):
self.assertRaises(
errors.ClientError, self.client.answer_challenge,
self.challr.body, challenges.DNSResponse(validation=None))
with pytest.raises(errors.ClientError):
self.client.answer_challenge(self.challr.body, challenges.DNSResponse(validation=None))
@mock.patch('acme.client.datetime')
def test_poll_and_finalize(self, mock_datetime):
@@ -178,7 +180,7 @@ class ClientV2Test(unittest.TestCase):
self.client.poll_authorizations = mock.Mock(return_value=self.orderr)
self.client.finalize_order = mock.Mock(return_value=self.orderr)
self.assertEqual(self.client.poll_and_finalize(self.orderr), self.orderr)
assert self.client.poll_and_finalize(self.orderr) == self.orderr
self.client.poll_authorizations.assert_called_once_with(self.orderr, expected_deadline)
self.client.finalize_order.assert_called_once_with(self.orderr, expected_deadline)
@@ -191,8 +193,8 @@ class ClientV2Test(unittest.TestCase):
self.response.json.side_effect = [
self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()]
self.assertRaises(
errors.TimeoutError, self.client.poll_authorizations, self.orderr, now_side_effect[1])
with pytest.raises(errors.TimeoutError):
self.client.poll_authorizations(self.orderr, now_side_effect[1])
def test_poll_authorizations_failure(self):
deadline = datetime.datetime(9999, 9, 9)
@@ -201,8 +203,8 @@ class ClientV2Test(unittest.TestCase):
authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,))
self.response.json.return_value = authz.to_json()
self.assertRaises(
errors.ValidationError, self.client.poll_authorizations, self.orderr, deadline)
with pytest.raises(errors.ValidationError):
self.client.poll_authorizations(self.orderr, deadline)
def test_poll_authorizations_success(self):
deadline = datetime.datetime(9999, 9, 9)
@@ -213,12 +215,13 @@ class ClientV2Test(unittest.TestCase):
self.response.json.side_effect = (
self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json())
self.assertEqual(self.client.poll_authorizations(self.orderr, deadline), updated_orderr)
assert self.client.poll_authorizations(self.orderr, deadline) == updated_orderr
def test_poll_unexpected_update(self):
updated_authz = self.authz.update(identifier=self.identifier.update(value='foo'))
self.response.json.return_value = updated_authz.to_json()
self.assertRaises(errors.UnexpectedUpdate, self.client.poll, self.authzr)
with pytest.raises(errors.UnexpectedUpdate):
self.client.poll(self.authzr)
def test_finalize_order_success(self):
updated_order = self.order.update(
@@ -230,7 +233,7 @@ class ClientV2Test(unittest.TestCase):
self.response.text = CERT_SAN_PEM
deadline = datetime.datetime(9999, 9, 9)
self.assertEqual(self.client.finalize_order(self.orderr, deadline), updated_orderr)
assert self.client.finalize_order(self.orderr, deadline) == updated_orderr
def test_finalize_order_error(self):
updated_order = self.order.update(
@@ -239,19 +242,20 @@ class ClientV2Test(unittest.TestCase):
self.response.json.return_value = updated_order.to_json()
deadline = datetime.datetime(9999, 9, 9)
self.assertRaises(errors.IssuanceError, self.client.finalize_order, self.orderr, deadline)
with pytest.raises(errors.IssuanceError):
self.client.finalize_order(self.orderr, deadline)
def test_finalize_order_invalid_status(self):
# https://github.com/certbot/certbot/issues/9296
order = self.order.update(error=None, status=messages.STATUS_INVALID)
self.response.json.return_value = order.to_json()
with self.assertRaises(errors.Error) as error:
with pytest.raises(errors.Error, match="The certificate order failed"):
self.client.finalize_order(self.orderr, datetime.datetime(9999, 9, 9))
self.assertIn("The certificate order failed", str(error.exception))
def test_finalize_order_timeout(self):
deadline = datetime.datetime.now() - datetime.timedelta(seconds=60)
self.assertRaises(errors.TimeoutError, self.client.finalize_order, self.orderr, deadline)
with pytest.raises(errors.TimeoutError):
self.client.finalize_order(self.orderr, deadline)
def test_finalize_order_alt_chains(self):
updated_order = self.order.update(
@@ -274,11 +278,11 @@ class ClientV2Test(unittest.TestCase):
mock.ANY, new_nonce_url=mock.ANY)
self.net.post.assert_any_call('https://example.com/acme/cert/2',
mock.ANY, new_nonce_url=mock.ANY)
self.assertEqual(resp, updated_orderr)
assert 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=[]))
assert resp == updated_orderr.update(alternative_fullchains_pem=[])
def test_revoke(self):
self.client.revoke(messages_test.CERT, self.rsn)
@@ -287,20 +291,18 @@ class ClientV2Test(unittest.TestCase):
def test_revoke_bad_status_raises_error(self):
self.response.status_code = http_client.METHOD_NOT_ALLOWED
self.assertRaises(
errors.ClientError,
self.client.revoke,
messages_test.CERT,
with pytest.raises(errors.ClientError):
self.client.revoke(messages_test.CERT,
self.rsn)
def test_update_registration(self):
# "Instance of 'Field' has no to_json/update member" bug:
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.assertIsNotNone(self.client.net.account)
self.assertEqual(self.client.net.post.call_count, 2)
self.assertIn(DIRECTORY_V2.newAccount, self.net.post.call_args_list[0][0])
assert self.regr == self.client.update_registration(self.regr)
assert self.client.net.account is not None
assert self.client.net.post.call_count == 2
assert DIRECTORY_V2.newAccount in self.net.post.call_args_list[0][0]
self.response.json.return_value = self.regr.body.update(
contact=()).to_json()
@@ -310,22 +312,22 @@ class ClientV2Test(unittest.TestCase):
'meta': messages.Directory.Meta(external_account_required=True)
})
self.assertTrue(self.client.external_account_required())
assert self.client.external_account_required()
def test_external_account_required_false(self):
self.client.directory = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=False)
})
self.assertFalse(self.client.external_account_required())
assert not self.client.external_account_required()
def test_external_account_required_default(self):
self.assertFalse(self.client.external_account_required())
assert not self.client.external_account_required()
def test_query_registration_client(self):
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = 'https://www.letsencrypt-demo.org/acme/reg/1'
self.assertEqual(self.regr, self.client.query_registration(self.regr))
assert self.regr == self.client.query_registration(self.regr)
def test_post_as_get(self):
with mock.patch('acme.client.ClientV2._authzr_from_response') as mock_client:
@@ -340,9 +342,8 @@ class ClientV2Test(unittest.TestCase):
def test_retry_after_date(self):
self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT'
self.assertEqual(
datetime.datetime(1999, 12, 31, 23, 59, 59),
self.client.retry_after(response=self.response, default=10))
assert datetime.datetime(1999, 12, 31, 23, 59, 59) == \
self.client.retry_after(response=self.response, default=10)
@mock.patch('acme.client.datetime')
def test_retry_after_invalid(self, dt_mock):
@@ -350,9 +351,8 @@ class ClientV2Test(unittest.TestCase):
dt_mock.timedelta = datetime.timedelta
self.response.headers['Retry-After'] = 'foooo'
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
assert datetime.datetime(2015, 3, 27, 0, 0, 10) == \
self.client.retry_after(response=self.response, default=10)
@mock.patch('acme.client.datetime')
def test_retry_after_overflow(self, dt_mock):
@@ -361,9 +361,8 @@ class ClientV2Test(unittest.TestCase):
dt_mock.datetime.side_effect = datetime.datetime
self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST"
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
assert datetime.datetime(2015, 3, 27, 0, 0, 10) == \
self.client.retry_after(response=self.response, default=10)
@mock.patch('acme.client.datetime')
def test_retry_after_seconds(self, dt_mock):
@@ -371,24 +370,21 @@ class ClientV2Test(unittest.TestCase):
dt_mock.timedelta = datetime.timedelta
self.response.headers['Retry-After'] = '50'
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 50),
self.client.retry_after(response=self.response, default=10))
assert datetime.datetime(2015, 3, 27, 0, 0, 50) == \
self.client.retry_after(response=self.response, default=10)
@mock.patch('acme.client.datetime')
def test_retry_after_missing(self, dt_mock):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
self.assertEqual(
datetime.datetime(2015, 3, 27, 0, 0, 10),
self.client.retry_after(response=self.response, default=10))
assert datetime.datetime(2015, 3, 27, 0, 0, 10) == \
self.client.retry_after(response=self.response, default=10)
def test_get_directory(self):
self.response.json.return_value = DIRECTORY_V2.to_json()
self.assertEqual(
DIRECTORY_V2.to_partial_json(),
ClientV2.get_directory('https://example.com/dir', self.net).to_partial_json())
assert DIRECTORY_V2.to_partial_json() == \
ClientV2.get_directory('https://example.com/dir', self.net).to_partial_json()
class MockJSONDeSerializable(jose.JSONDeSerializable):
@@ -420,15 +416,15 @@ class ClientNetworkTest(unittest.TestCase):
self.response.links = {}
def test_init(self):
self.assertIs(self.net.verify_ssl, self.verify_ssl)
assert self.net.verify_ssl is self.verify_ssl
def test_wrap_in_jws(self):
# pylint: disable=protected-access
jws_dump = self.net._wrap_in_jws(
MockJSONDeSerializable('foo'), nonce=b'Tg', url="url")
jws = acme_jws.JWS.json_loads(jws_dump)
self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'})
self.assertEqual(jws.signature.combined.nonce, b'Tg')
assert json.loads(jws.payload.decode()) == {'foo': 'foo'}
assert jws.signature.combined.nonce == b'Tg'
def test_wrap_in_jws_v2(self):
self.net.account = {'uri': 'acct-uri'}
@@ -436,10 +432,10 @@ class ClientNetworkTest(unittest.TestCase):
jws_dump = self.net._wrap_in_jws(
MockJSONDeSerializable('foo'), nonce=b'Tg', url="url")
jws = acme_jws.JWS.json_loads(jws_dump)
self.assertEqual(json.loads(jws.payload.decode()), {'foo': 'foo'})
self.assertEqual(jws.signature.combined.nonce, b'Tg')
self.assertEqual(jws.signature.combined.kid, u'acct-uri')
self.assertEqual(jws.signature.combined.url, u'url')
assert json.loads(jws.payload.decode()) == {'foo': 'foo'}
assert jws.signature.combined.nonce == b'Tg'
assert jws.signature.combined.kid == u'acct-uri'
assert jws.signature.combined.url == u'url'
def test_check_response_not_ok_jobj_no_error(self):
self.response.ok = False
@@ -447,31 +443,31 @@ class ClientNetworkTest(unittest.TestCase):
with mock.patch('acme.client.messages.Error.from_json') as from_json:
from_json.side_effect = jose.DeserializationError
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
with pytest.raises(errors.ClientError):
self.net._check_response(self.response)
def test_check_response_not_ok_jobj_error(self):
self.response.ok = False
self.response.json.return_value = messages.Error.with_code(
'serverInternal', detail='foo', title='some title').to_json()
# pylint: disable=protected-access
self.assertRaises(
messages.Error, self.net._check_response, self.response)
with pytest.raises(messages.Error):
self.net._check_response(self.response)
def test_check_response_not_ok_no_jobj(self):
self.response.ok = False
self.response.json.side_effect = ValueError
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response)
with pytest.raises(errors.ClientError):
self.net._check_response(self.response)
def test_check_response_ok_no_jobj_ct_required(self):
self.response.json.side_effect = ValueError
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
# pylint: disable=protected-access
self.assertRaises(
errors.ClientError, self.net._check_response, self.response,
with pytest.raises(errors.ClientError):
self.net._check_response(self.response,
content_type=self.net.JSON_CONTENT_TYPE)
def test_check_response_ok_no_jobj_no_ct(self):
@@ -479,16 +475,15 @@ class ClientNetworkTest(unittest.TestCase):
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
# pylint: disable=protected-access
self.assertEqual(
self.response, self.net._check_response(self.response))
assert self.response == self.net._check_response(self.response)
@mock.patch('acme.client.logger')
def test_check_response_ok_ct_with_charset(self, mock_logger):
self.response.json.return_value = {}
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._check_response(
self.response, content_type='application/json'))
assert self.response == self.net._check_response(
self.response, content_type='application/json')
try:
mock_logger.debug.assert_called_with(
'Ignoring wrong Content-Type (%r) for JSON decodable response',
@@ -504,8 +499,8 @@ class ClientNetworkTest(unittest.TestCase):
self.response.json.return_value = {}
self.response.headers['Content-Type'] = 'text/plain'
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._check_response(
self.response, content_type='application/json'))
assert self.response == self.net._check_response(
self.response, content_type='application/json')
mock_logger.debug.assert_called_with(
'Ignoring wrong Content-Type (%r) for JSON decodable response',
'text/plain'
@@ -515,22 +510,22 @@ class ClientNetworkTest(unittest.TestCase):
self.response.ok = False
self.response.status_code = 409
# pylint: disable=protected-access
self.assertRaises(errors.ConflictError, self.net._check_response, self.response)
with pytest.raises(errors.ConflictError):
self.net._check_response(self.response)
def test_check_response_jobj(self):
self.response.json.return_value = {}
for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']:
self.response.headers['Content-Type'] = response_ct
# pylint: disable=protected-access
self.assertEqual(
self.response, self.net._check_response(self.response))
assert self.response == self.net._check_response(self.response)
def test_send_request(self):
self.net.session = mock.MagicMock()
self.net.session.request.return_value = self.response
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._send_request(
'HEAD', 'http://example.com/', 'foo', bar='baz'))
assert self.response == self.net._send_request(
'HEAD', 'http://example.com/', 'foo', bar='baz')
self.net.session.request.assert_called_once_with(
'HEAD', 'http://example.com/', 'foo',
headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, bar='baz')
@@ -552,8 +547,8 @@ class ClientNetworkTest(unittest.TestCase):
self.net.session = mock.MagicMock()
self.net.session.request.return_value = self.response
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._send_request(
'POST', 'http://example.com/', 'foo', data='qux', bar='baz'))
assert self.response == self.net._send_request(
'POST', 'http://example.com/', 'foo', data='qux', bar='baz')
self.net.session.request.assert_called_once_with(
'POST', 'http://example.com/', 'foo',
headers=mock.ANY, verify=mock.ANY, timeout=mock.ANY, data='qux', bar='baz')
@@ -565,9 +560,8 @@ class ClientNetworkTest(unittest.TestCase):
self.net.session.request.return_value = self.response
self.net.verify_ssl = verify
# pylint: disable=protected-access
self.assertEqual(
self.response,
self.net._send_request('GET', 'http://example.com/'))
assert self.response == \
self.net._send_request('GET', 'http://example.com/')
self.net.session.request.assert_called_once_with(
'GET', 'http://example.com/', verify=verify,
timeout=mock.ANY, headers=mock.ANY)
@@ -615,8 +609,8 @@ class ClientNetworkTest(unittest.TestCase):
mock_requests.exceptions = requests.exceptions
mock_requests.request.side_effect = requests.exceptions.RequestException
# pylint: disable=protected-access
self.assertRaises(requests.exceptions.RequestException,
self.net._send_request, 'GET', 'uri')
with pytest.raises(requests.exceptions.RequestException):
self.net._send_request('GET', 'uri')
def test_urllib_error(self):
# Using a connection error to test a properly formatted error message
@@ -626,12 +620,12 @@ class ClientNetworkTest(unittest.TestCase):
# Value Error Generated Exceptions
except ValueError as y:
self.assertEqual("Requesting localhost/nonexistent: "
"Connection refused", str(y))
assert "Requesting localhost/nonexistent: " \
"Connection refused" == str(y)
# Requests Library Exceptions
except requests.exceptions.ConnectionError as z: #pragma: no cover
self.assertTrue("'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z))
assert "'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z)
class ClientNetworkWithMockedResponseTest(unittest.TestCase):
@@ -658,7 +652,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
def send_request(*args, **kwargs):
# pylint: disable=unused-argument,missing-docstring
self.assertNotIn("new_nonce_url", kwargs)
assert "new_nonce_url" not in kwargs
method = args[0]
uri = args[1]
if method == 'HEAD' and uri != "new_nonce_uri":
@@ -682,58 +676,60 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
def check_response(self, response, content_type):
# pylint: disable=missing-docstring
self.assertEqual(self.response, response)
self.assertEqual(self.content_type, content_type)
self.assertTrue(self.response.ok)
assert self.response == response
assert self.content_type == content_type
assert self.response.ok
self.response.checked = True
return self.response
def test_head(self):
self.assertEqual(self.acmev1_nonce_response, self.net.head(
'http://example.com/', 'foo', bar='baz'))
assert self.acmev1_nonce_response == self.net.head(
'http://example.com/', 'foo', bar='baz')
self.send_request.assert_called_once_with(
'HEAD', 'http://example.com/', 'foo', bar='baz')
def test_head_v2(self):
self.assertEqual(self.response, self.net.head(
'new_nonce_uri', 'foo', bar='baz'))
assert self.response == self.net.head(
'new_nonce_uri', 'foo', bar='baz')
self.send_request.assert_called_once_with(
'HEAD', 'new_nonce_uri', 'foo', bar='baz')
def test_get(self):
self.assertEqual(self.response, self.net.get(
'http://example.com/', content_type=self.content_type, bar='baz'))
self.assertTrue(self.response.checked)
assert self.response == self.net.get(
'http://example.com/', content_type=self.content_type, bar='baz')
assert self.response.checked
self.send_request.assert_called_once_with(
'GET', 'http://example.com/', bar='baz')
def test_post_no_content_type(self):
self.content_type = self.net.JOSE_CONTENT_TYPE
self.assertEqual(self.response, self.net.post('uri', self.obj))
self.assertTrue(self.response.checked)
assert self.response == self.net.post('uri', self.obj)
assert self.response.checked
def test_post(self):
# pylint: disable=protected-access
self.assertEqual(self.response, self.net.post(
'uri', self.obj, content_type=self.content_type))
self.assertTrue(self.response.checked)
assert self.response == self.net.post(
'uri', self.obj, content_type=self.content_type)
assert self.response.checked
self.net._wrap_in_jws.assert_called_once_with(
self.obj, jose.b64decode(self.all_nonces.pop()), "uri")
self.available_nonces = []
self.assertRaises(errors.MissingNonce, self.net.post,
'uri', self.obj, content_type=self.content_type)
with pytest.raises(errors.MissingNonce):
self.net.post('uri', self.obj, content_type=self.content_type)
self.net._wrap_in_jws.assert_called_with(
self.obj, jose.b64decode(self.all_nonces.pop()), "uri")
def test_post_wrong_initial_nonce(self): # HEAD
self.available_nonces = [b'f', jose.b64encode(b'good')]
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
with pytest.raises(errors.BadNonce):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_wrong_post_response_nonce(self):
self.available_nonces = [jose.b64encode(b'good'), b'f']
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
with pytest.raises(errors.BadNonce):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_failed_retry(self):
@@ -742,7 +738,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
with pytest.raises(messages.Error):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_not_retried(self):
@@ -752,7 +749,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
# pylint: disable=protected-access
self.net._check_response = check_response
self.assertRaises(messages.Error, self.net.post, 'uri',
with pytest.raises(messages.Error):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_successful_retry(self):
@@ -761,16 +759,16 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.response]
# pylint: disable=protected-access
self.assertEqual(self.response, self.net.post(
'uri', self.obj, content_type=self.content_type))
assert self.response == self.net.post(
'uri', self.obj, content_type=self.content_type)
def test_head_get_post_error_passthrough(self):
self.send_request.side_effect = requests.exceptions.RequestException
for method in self.net.head, self.net.get:
self.assertRaises(
requests.exceptions.RequestException, method, 'GET', 'uri')
self.assertRaises(requests.exceptions.RequestException,
self.net.post, 'uri', obj=self.obj)
with pytest.raises(requests.exceptions.RequestException):
method('GET', 'uri')
with pytest.raises(requests.exceptions.RequestException):
self.net.post('uri', obj=self.obj)
def test_post_bad_nonce_head(self):
# pylint: disable=protected-access
@@ -781,10 +779,11 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.content_type = None
check_response = mock.MagicMock()
self.net._check_response = check_response
self.assertRaises(errors.ClientError, self.net.post, 'uri',
with pytest.raises(errors.ClientError):
self.net.post('uri',
self.obj, content_type=self.content_type,
new_nonce_url='new_nonce_uri')
self.assertEqual(check_response.call_count, 1)
assert check_response.call_count == 1
def test_new_nonce_uri_removed(self):
self.content_type = None
@@ -792,4 +791,4 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -1,18 +1,20 @@
"""Tests for acme.crypto_util."""
import itertools
import ipaddress
import itertools
import socket
import socketserver
import sys
import threading
import time
import unittest
from typing import List
import unittest
import josepy as jose
import OpenSSL
import pytest
from acme import errors
import test_util
from acme._internal.tests import test_util
class SSLSocketAndProbeSNITest(unittest.TestCase):
@@ -27,11 +29,9 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
from acme.crypto_util import SSLSocket
class _TestServer(socketserver.TCPServer):
def server_bind(self): # pylint: disable=missing-docstring
self.socket = SSLSocket(socket.socket(),
certs)
socketserver.TCPServer.server_bind(self)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.socket = SSLSocket(self.socket, certs)
self.server = _TestServer(('', 0), socketserver.BaseRequestHandler)
self.port = self.server.socket.getsockname()[1]
@@ -42,6 +42,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
if self.server_thread.is_alive():
# The thread may have already terminated.
self.server_thread.join() # pragma: no cover
self.server.server_close()
def _probe(self, name):
from acme.crypto_util import probe_sni
@@ -54,18 +55,20 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
def test_probe_ok(self):
self._start_server()
self.assertEqual(self.cert, self._probe(b'foo'))
assert self.cert == self._probe(b'foo')
def test_probe_not_recognized_name(self):
self._start_server()
self.assertRaises(errors.Error, self._probe, b'bar')
with pytest.raises(errors.Error):
self._probe(b'bar')
def test_probe_connection_error(self):
self.server.server_close()
original_timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(1)
self.assertRaises(errors.Error, self._probe, b'bar')
with pytest.raises(errors.Error):
self._probe(b'bar')
finally:
socket.setdefaulttimeout(original_timeout)
@@ -75,10 +78,10 @@ class SSLSocketTest(unittest.TestCase):
def test_ssl_socket_invalid_arguments(self):
from acme.crypto_util import SSLSocket
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
_ = SSLSocket(None, {'sni': ('key', 'cert')},
cert_selection=lambda _: None)
with self.assertRaises(ValueError):
with pytest.raises(ValueError):
_ = SSLSocket(None)
@@ -95,15 +98,15 @@ class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
return self._call(test_util.load_cert, name)
def test_cert_one_san_no_common(self):
self.assertEqual(self._call_cert('cert-nocn.der'),
['no-common-name.badssl.com'])
assert self._call_cert('cert-nocn.der') == \
['no-common-name.badssl.com']
def test_cert_no_sans_yes_common(self):
self.assertEqual(self._call_cert('cert.pem'), ['example.com'])
assert self._call_cert('cert.pem') == ['example.com']
def test_cert_two_sans_yes_common(self):
self.assertEqual(self._call_cert('cert-san.pem'),
['example.com', 'www.example.com'])
assert self._call_cert('cert-san.pem') == \
['example.com', 'www.example.com']
class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
@@ -131,47 +134,47 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
return self._call(test_util.load_csr, name)
def test_cert_no_sans(self):
self.assertEqual(self._call_cert('cert.pem'), [])
assert self._call_cert('cert.pem') == []
def test_cert_two_sans(self):
self.assertEqual(self._call_cert('cert-san.pem'),
['example.com', 'www.example.com'])
assert self._call_cert('cert-san.pem') == \
['example.com', 'www.example.com']
def test_cert_hundred_sans(self):
self.assertEqual(self._call_cert('cert-100sans.pem'),
['example{0}.com'.format(i) for i in range(1, 101)])
assert self._call_cert('cert-100sans.pem') == \
['example{0}.com'.format(i) for i in range(1, 101)]
def test_cert_idn_sans(self):
self.assertEqual(self._call_cert('cert-idnsans.pem'),
self._get_idn_names())
assert self._call_cert('cert-idnsans.pem') == \
self._get_idn_names()
def test_csr_no_sans(self):
self.assertEqual(self._call_csr('csr-nosans.pem'), [])
assert self._call_csr('csr-nosans.pem') == []
def test_csr_one_san(self):
self.assertEqual(self._call_csr('csr.pem'), ['example.com'])
assert self._call_csr('csr.pem') == ['example.com']
def test_csr_two_sans(self):
self.assertEqual(self._call_csr('csr-san.pem'),
['example.com', 'www.example.com'])
assert self._call_csr('csr-san.pem') == \
['example.com', 'www.example.com']
def test_csr_six_sans(self):
self.assertEqual(self._call_csr('csr-6sans.pem'),
assert self._call_csr('csr-6sans.pem') == \
['example.com', 'example.org', 'example.net',
'example.info', 'subdomain.example.com',
'other.subdomain.example.com'])
'other.subdomain.example.com']
def test_csr_hundred_sans(self):
self.assertEqual(self._call_csr('csr-100sans.pem'),
['example{0}.com'.format(i) for i in range(1, 101)])
assert self._call_csr('csr-100sans.pem') == \
['example{0}.com'.format(i) for i in range(1, 101)]
def test_csr_idn_sans(self):
self.assertEqual(self._call_csr('csr-idnsans.pem'),
self._get_idn_names())
assert self._call_csr('csr-idnsans.pem') == \
self._get_idn_names()
def test_critical_san(self):
self.assertEqual(self._call_cert('critical-san.pem'),
['chicago-cubs.venafi.example', 'cubs.venafi.example'])
assert self._call_cert('critical-san.pem') == \
['chicago-cubs.venafi.example', 'cubs.venafi.example']
class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase):
@@ -190,30 +193,30 @@ class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase):
return self._call(test_util.load_csr, name)
def test_cert_no_sans(self):
self.assertEqual(self._call_cert('cert.pem'), [])
assert self._call_cert('cert.pem') == []
def test_csr_no_sans(self):
self.assertEqual(self._call_csr('csr-nosans.pem'), [])
assert self._call_csr('csr-nosans.pem') == []
def test_cert_domain_sans(self):
self.assertEqual(self._call_cert('cert-san.pem'), [])
assert self._call_cert('cert-san.pem') == []
def test_csr_domain_sans(self):
self.assertEqual(self._call_csr('csr-san.pem'), [])
assert self._call_csr('csr-san.pem') == []
def test_cert_ip_two_sans(self):
self.assertEqual(self._call_cert('cert-ipsans.pem'), ['192.0.2.145', '203.0.113.1'])
assert self._call_cert('cert-ipsans.pem') == ['192.0.2.145', '203.0.113.1']
def test_csr_ip_two_sans(self):
self.assertEqual(self._call_csr('csr-ipsans.pem'), ['192.0.2.145', '203.0.113.1'])
assert self._call_csr('csr-ipsans.pem') == ['192.0.2.145', '203.0.113.1']
def test_csr_ipv6_sans(self):
self.assertEqual(self._call_csr('csr-ipv6sans.pem'),
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5'])
assert self._call_csr('csr-ipv6sans.pem') == \
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']
def test_cert_ipv6_sans(self):
self.assertEqual(self._call_cert('cert-ipv6sans.pem'),
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5'])
assert self._call_cert('cert-ipv6sans.pem') == \
['0:0:0:0:0:0:0:1', 'A3BE:32F3:206E:C75D:956:CEE:9858:5EC5']
class GenSsCertTest(unittest.TestCase):
@@ -232,12 +235,12 @@ class GenSsCertTest(unittest.TestCase):
cert = gen_ss_cert(self.key, ['dummy'], force_san=True,
ips=[ipaddress.ip_address("10.10.10.10")])
self.serial_num.append(cert.get_serial_number())
self.assertGreaterEqual(len(set(self.serial_num)), self.cert_count)
assert len(set(self.serial_num)) >= self.cert_count
def test_no_name(self):
from acme.crypto_util import gen_ss_cert
with self.assertRaises(AssertionError):
with pytest.raises(AssertionError):
gen_ss_cert(self.key, ips=[ipaddress.ip_address("1.1.1.1")])
gen_ss_cert(self.key)
@@ -255,41 +258,39 @@ class MakeCSRTest(unittest.TestCase):
def test_make_csr(self):
csr_pem = self._call_with_key(["a.example", "b.example"])
self.assertIn(b'--BEGIN CERTIFICATE REQUEST--', csr_pem)
self.assertIn(b'--END CERTIFICATE REQUEST--', csr_pem)
assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem
assert b'--END CERTIFICATE REQUEST--' in 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
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEqual(len(csr.get_extensions()), 1)
self.assertEqual(csr.get_extensions()[0].get_data(),
assert len(csr.get_extensions()) == 1
assert csr.get_extensions()[0].get_data() == \
OpenSSL.crypto.X509Extension(
b'subjectAltName',
critical=False,
value=b'DNS:a.example, DNS:b.example',
).get_data(),
)
).get_data()
def test_make_csr_ip(self):
csr_pem = self._call_with_key(["a.example"], False, [ipaddress.ip_address('127.0.0.1'), ipaddress.ip_address('::1')])
self.assertIn(b'--BEGIN CERTIFICATE REQUEST--' , csr_pem)
self.assertIn(b'--END CERTIFICATE REQUEST--' , csr_pem)
assert b'--BEGIN CERTIFICATE REQUEST--' in csr_pem
assert b'--END CERTIFICATE REQUEST--' in 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
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEqual(len(csr.get_extensions()), 1)
self.assertEqual(csr.get_extensions()[0].get_data(),
assert len(csr.get_extensions()) == 1
assert csr.get_extensions()[0].get_data() == \
OpenSSL.crypto.X509Extension(
b'subjectAltName',
critical=False,
value=b'DNS:a.example, IP:127.0.0.1, IP:::1',
).get_data(),
)
).get_data()
# for IP san it's actually need to be octet-string,
# but somewhere downstream thankfully handle it for us
@@ -302,25 +303,26 @@ class MakeCSRTest(unittest.TestCase):
# have a get_extensions() method, so we skip this test if the method
# isn't available.
if hasattr(csr, 'get_extensions'):
self.assertEqual(len(csr.get_extensions()), 2)
assert len(csr.get_extensions()) == 2
# NOTE: Ideally we would filter by the TLS Feature OID, but
# OpenSSL.crypto.X509Extension doesn't give us the extension's raw OID,
# and the shortname field is just "UNDEF"
must_staple_exts = [e for e in csr.get_extensions()
if e.get_data() == b"0\x03\x02\x01\x05"]
self.assertEqual(len(must_staple_exts), 1,
"Expected exactly one Must Staple extension")
assert len(must_staple_exts) == 1, \
"Expected exactly one Must Staple extension"
def test_make_csr_without_hostname(self):
self.assertRaises(ValueError, self._call_with_key)
with pytest.raises(ValueError):
self._call_with_key()
def test_make_csr_correct_version(self):
csr_pem = self._call_with_key(["a.example"])
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
self.assertEqual(csr.get_version(), 0,
"Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4")
assert csr.get_version() == 0, \
"Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4"
class DumpPyopensslChainTest(unittest.TestCase):
@@ -338,7 +340,7 @@ class DumpPyopensslChainTest(unittest.TestCase):
length = sum(
len(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
for cert in loaded)
self.assertEqual(len(self._call(loaded)), length)
assert len(self._call(loaded)) == length
def test_dump_pyopenssl_chain_wrapped(self):
names = ['cert.pem', 'cert-san.pem', 'cert-idnsans.pem']
@@ -347,8 +349,8 @@ class DumpPyopensslChainTest(unittest.TestCase):
wrapped = [wrap_func(cert) for cert in loaded]
dump_func = OpenSSL.crypto.dump_certificate
length = sum(len(dump_func(OpenSSL.crypto.FILETYPE_PEM, cert)) for cert in loaded)
self.assertEqual(len(self._call(wrapped)), length)
assert len(self._call(wrapped)) == length
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -1,7 +1,10 @@
"""Tests for acme.errors."""
import sys
import unittest
from unittest import mock
import pytest
class BadNonceTest(unittest.TestCase):
"""Tests for acme.errors.BadNonce."""
@@ -11,7 +14,7 @@ class BadNonceTest(unittest.TestCase):
self.error = BadNonce(nonce="xxx", error="error")
def test_str(self):
self.assertEqual("Invalid nonce ('xxx'): error", str(self.error))
assert "Invalid nonce ('xxx'): error" == str(self.error)
class MissingNonceTest(unittest.TestCase):
@@ -24,8 +27,8 @@ class MissingNonceTest(unittest.TestCase):
self.error = MissingNonce(self.response)
def test_str(self):
self.assertIn("FOO", str(self.error))
self.assertIn("{}", str(self.error))
assert "FOO" in str(self.error)
assert "{}" in str(self.error)
class PollErrorTest(unittest.TestCase):
@@ -40,13 +43,13 @@ class PollErrorTest(unittest.TestCase):
mock.sentinel.AR: mock.sentinel.AR2})
def test_timeout(self):
self.assertTrue(self.timeout.timeout)
self.assertFalse(self.invalid.timeout)
assert self.timeout.timeout
assert not self.invalid.timeout
def test_repr(self):
self.assertEqual('PollError(exhausted=%s, updated={sentinel.AR: '
'sentinel.AR2})' % repr(set()), repr(self.invalid))
assert 'PollError(exhausted=%s, updated={sentinel.AR: ' \
'sentinel.AR2})' % repr(set()) == repr(self.invalid)
if __name__ == "__main__":
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -1,9 +1,11 @@
"""Tests for acme.fields."""
import datetime
import sys
import unittest
import warnings
import josepy as jose
import pytest
import pytz
@@ -15,45 +17,44 @@ class FixedTest(unittest.TestCase):
self.field = fixed('name', 'x')
def test_decode(self):
self.assertEqual('x', self.field.decode('x'))
assert 'x' == self.field.decode('x')
def test_decode_bad(self):
self.assertRaises(jose.DeserializationError, self.field.decode, 'y')
with pytest.raises(jose.DeserializationError):
self.field.decode('y')
def test_encode(self):
self.assertEqual('x', self.field.encode('x'))
assert 'x' == self.field.encode('x')
def test_encode_override(self):
self.assertEqual('y', self.field.encode('y'))
assert 'y' == self.field.encode('y')
class RFC3339FieldTest(unittest.TestCase):
"""Tests for acme.fields.RFC3339Field."""
def setUp(self):
self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.utc)
self.decoded = datetime.datetime(2015, 3, 27, tzinfo=pytz.UTC)
self.encoded = '2015-03-27T00:00:00Z'
def test_default_encoder(self):
from acme.fields import RFC3339Field
self.assertEqual(
self.encoded, RFC3339Field.default_encoder(self.decoded))
assert self.encoded == RFC3339Field.default_encoder(self.decoded)
def test_default_encoder_naive_fails(self):
from acme.fields import RFC3339Field
self.assertRaises(
ValueError, RFC3339Field.default_encoder, datetime.datetime.now())
with pytest.raises(ValueError):
RFC3339Field.default_encoder(datetime.datetime.now())
def test_default_decoder(self):
from acme.fields import RFC3339Field
self.assertEqual(
self.decoded, RFC3339Field.default_decoder(self.encoded))
assert self.decoded == RFC3339Field.default_decoder(self.encoded)
def test_default_decoder_raises_deserialization_error(self):
from acme.fields import RFC3339Field
self.assertRaises(
jose.DeserializationError, RFC3339Field.default_decoder, '')
with pytest.raises(jose.DeserializationError):
RFC3339Field.default_decoder('')
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

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

View File

@@ -1,9 +1,11 @@
"""Tests for acme.jws."""
import sys
import unittest
import josepy as jose
import pytest
import test_util
from acme._internal.tests import test_util
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
@@ -25,9 +27,9 @@ class HeaderTest(unittest.TestCase):
from acme.jws import Header
nonce_field = Header._fields['nonce']
self.assertRaises(
jose.DeserializationError, nonce_field.decode, self.wrong_nonce)
self.assertEqual(b'foo', nonce_field.decode(self.good_nonce))
with pytest.raises(jose.DeserializationError):
nonce_field.decode(self.wrong_nonce)
assert b'foo' == nonce_field.decode(self.good_nonce)
class JWSTest(unittest.TestCase):
@@ -45,22 +47,22 @@ class JWSTest(unittest.TestCase):
jws = JWS.sign(payload=b'foo', key=self.privkey,
alg=jose.RS256, nonce=self.nonce,
url=self.url, kid=self.kid)
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.assertIsNone(jws.signature.combined.jwk)
assert jws.signature.combined.nonce == self.nonce
assert jws.signature.combined.url == self.url
assert jws.signature.combined.kid == self.kid
assert jws.signature.combined.jwk is None
# TODO: check that nonce is in protected header
self.assertEqual(jws, JWS.from_json(jws.to_json()))
assert jws == JWS.from_json(jws.to_json())
def test_jwk_serialize(self):
from acme.jws import JWS
jws = JWS.sign(payload=b'foo', key=self.privkey,
alg=jose.RS256, nonce=self.nonce,
url=self.url)
self.assertIsNone(jws.signature.combined.kid)
self.assertEqual(jws.signature.combined.jwk, self.pubkey)
assert jws.signature.combined.kid is None
assert jws.signature.combined.jwk == self.pubkey
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -1,14 +1,16 @@
"""Tests for acme.messages."""
import contextlib
import sys
from typing import Dict
import unittest
import contextlib
from unittest import mock
import warnings
import josepy as jose
import pytest
from acme import challenges
import test_util
from acme._internal.tests import test_util
CERT = test_util.load_comparable_cert('cert.der')
CSR = test_util.load_comparable_csr('csr.der')
@@ -19,7 +21,10 @@ class ErrorTest(unittest.TestCase):
"""Tests for acme.messages.Error."""
def setUp(self):
from acme.messages import Error, ERROR_PREFIX, Identifier, IDENTIFIER_FQDN
from acme.messages import Error
from acme.messages import ERROR_PREFIX
from acme.messages import Identifier
from acme.messages import IDENTIFIER_FQDN
self.error = Error.with_code('malformed', detail='foo', title='title')
self.jobj = {
'detail': 'foo',
@@ -34,11 +39,11 @@ class ErrorTest(unittest.TestCase):
def test_default_typ(self):
from acme.messages import Error
self.assertEqual(Error().typ, 'about:blank')
assert Error().typ == 'about:blank'
def test_from_json_empty(self):
from acme.messages import Error
self.assertEqual(Error(), Error.from_json('{}'))
assert Error() == Error.from_json('{}')
def test_from_json_hashable(self):
from acme.messages import Error
@@ -49,60 +54,62 @@ class ErrorTest(unittest.TestCase):
parsed_error = Error.from_json(self.error_with_subproblems.to_json())
self.assertEqual(1, len(parsed_error.subproblems))
self.assertEqual(self.subproblem, parsed_error.subproblems[0])
assert 1 == len(parsed_error.subproblems)
assert self.subproblem == parsed_error.subproblems[0]
def test_description(self):
self.assertEqual('The request message was malformed', self.error.description)
self.assertIsNone(self.error_custom.description)
assert 'The request message was malformed' == self.error.description
assert self.error_custom.description is None
def test_code(self):
from acme.messages import Error
self.assertEqual('malformed', self.error.code)
self.assertIsNone(self.error_custom.code)
self.assertIsNone(Error().code)
assert 'malformed' == self.error.code
assert self.error_custom.code is None
assert Error().code is None
def test_is_acme_error(self):
from acme.messages import is_acme_error, Error
self.assertTrue(is_acme_error(self.error))
self.assertFalse(is_acme_error(self.error_custom))
self.assertFalse(is_acme_error(Error()))
self.assertFalse(is_acme_error(self.empty_error))
self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}"))
from acme.messages import Error
from acme.messages import is_acme_error
assert is_acme_error(self.error)
assert not is_acme_error(self.error_custom)
assert not is_acme_error(Error())
assert not is_acme_error(self.empty_error)
assert not is_acme_error("must pet all the {dogs|rabbits}")
def test_unicode_error(self):
from acme.messages import Error, is_acme_error
from acme.messages import Error
from acme.messages import is_acme_error
arabic_error = Error.with_code(
'malformed', detail=u'\u0639\u062f\u0627\u0644\u0629', title='title')
self.assertTrue(is_acme_error(arabic_error))
assert is_acme_error(arabic_error)
def test_with_code(self):
from acme.messages import Error, is_acme_error
self.assertTrue(is_acme_error(Error.with_code('badCSR')))
self.assertRaises(ValueError, Error.with_code, 'not an ACME error code')
from acme.messages import Error
from acme.messages import is_acme_error
assert is_acme_error(Error.with_code('badCSR'))
with pytest.raises(ValueError):
Error.with_code('not an ACME error code')
def test_str(self):
self.assertEqual(
str(self.error),
u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}"
.format(self.error))
self.assertEqual(
str(self.error_with_subproblems),
assert str(self.error) == \
u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}" \
.format(self.error)
assert str(self.error_with_subproblems) == \
(u"{0.typ} :: {0.description} :: {0.detail} :: {0.title}\n"+
u"Problem for {1.identifier.value}: {1.typ} :: {1.description} :: {1.detail} :: {1.title}").format(
self.error_with_subproblems, self.subproblem))
self.error_with_subproblems, self.subproblem)
# this test is based on a minimal reproduction of a contextmanager/immutable
# exception related error: https://github.com/python/cpython/issues/99856
def test_setting_traceback(self):
self.assertIsNone(self.error_custom.__traceback__)
assert self.error_custom.__traceback__ is None
try:
1/0
except ZeroDivisionError as e:
self.error_custom.__traceback__ = e.__traceback__
self.assertIsNotNone(self.error_custom.__traceback__)
assert self.error_custom.__traceback__ is not None
class ConstantTest(unittest.TestCase):
@@ -119,28 +126,28 @@ class ConstantTest(unittest.TestCase):
self.const_b = MockConstant('b')
def test_to_partial_json(self):
self.assertEqual('a', self.const_a.to_partial_json())
self.assertEqual('b', self.const_b.to_partial_json())
assert 'a' == self.const_a.to_partial_json()
assert 'b' == self.const_b.to_partial_json()
def test_from_json(self):
self.assertEqual(self.const_a, self.MockConstant.from_json('a'))
self.assertRaises(
jose.DeserializationError, self.MockConstant.from_json, 'c')
assert self.const_a == self.MockConstant.from_json('a')
with pytest.raises(jose.DeserializationError):
self.MockConstant.from_json('c')
def test_from_json_hashable(self):
hash(self.MockConstant.from_json('a'))
def test_repr(self):
self.assertEqual('MockConstant(a)', repr(self.const_a))
self.assertEqual('MockConstant(b)', repr(self.const_b))
assert 'MockConstant(a)' == repr(self.const_a)
assert 'MockConstant(b)' == repr(self.const_b)
def test_equality(self):
const_a_prime = self.MockConstant('a')
self.assertNotEqual(self.const_a, self.const_b)
self.assertEqual(self.const_a, const_a_prime)
assert self.const_a != self.const_b
assert self.const_a == const_a_prime
self.assertNotEqual(self.const_a, self.const_b)
self.assertEqual(self.const_a, const_a_prime)
assert self.const_a != self.const_b
assert self.const_a == const_a_prime
class DirectoryTest(unittest.TestCase):
@@ -163,19 +170,21 @@ class DirectoryTest(unittest.TestCase):
Directory({'foo': 'bar'})
def test_getitem(self):
self.assertEqual('reg', self.dir['newReg'])
assert 'reg' == self.dir['newReg']
def test_getitem_fails_with_key_error(self):
self.assertRaises(KeyError, self.dir.__getitem__, 'foo')
with pytest.raises(KeyError):
self.dir.__getitem__('foo')
def test_getattr(self):
self.assertEqual('reg', self.dir.newReg)
assert 'reg' == self.dir.newReg
def test_getattr_fails_with_attribute_error(self):
self.assertRaises(AttributeError, self.dir.__getattr__, 'foo')
with pytest.raises(AttributeError):
self.dir.__getattr__('foo')
def test_to_json(self):
self.assertEqual(self.dir.to_json(), {
assert self.dir.to_json() == {
'newReg': 'reg',
'newCert': 'cert',
'meta': {
@@ -183,7 +192,7 @@ class DirectoryTest(unittest.TestCase):
'website': 'https://www.example.com/',
'caaIdentities': ['example.com'],
},
})
}
def test_from_json_deserialization_unknown_key_success(self): # pylint: disable=no-self-use
from acme.messages import Directory
@@ -194,7 +203,7 @@ class DirectoryTest(unittest.TestCase):
for k in self.dir.meta:
if k == 'terms_of_service':
result = self.dir.meta[k] == 'https://example.com/acme/terms'
self.assertTrue(result)
assert result
class ExternalAccountBindingTest(unittest.TestCase):
@@ -211,8 +220,8 @@ class ExternalAccountBindingTest(unittest.TestCase):
from acme.messages import ExternalAccountBinding
eab = ExternalAccountBinding.from_data(self.key, self.kid, self.hmac_key, self.dir)
self.assertEqual(len(eab), 3)
self.assertEqual(sorted(eab.keys()), sorted(['protected', 'payload', 'signature']))
assert len(eab) == 3
assert sorted(eab.keys()) == sorted(['protected', 'payload', 'signature'])
class RegistrationTest(unittest.TestCase):
@@ -241,13 +250,15 @@ class RegistrationTest(unittest.TestCase):
def test_from_data(self):
from acme.messages import Registration
reg = Registration.from_data(phone='1234', email='admin@foo.com')
self.assertEqual(reg.contact, (
assert reg.contact == (
'tel:1234',
'mailto:admin@foo.com',
))
)
def test_new_registration_from_data_with_eab(self):
from acme.messages import NewRegistration, ExternalAccountBinding, Directory
from acme.messages import Directory
from acme.messages import ExternalAccountBinding
from acme.messages import NewRegistration
key = jose.jwk.JWKRSA(key=KEY.public_key())
kid = "kid-for-testing"
hmac_key = "hmac-key-for-testing"
@@ -256,24 +267,24 @@ class RegistrationTest(unittest.TestCase):
})
eab = ExternalAccountBinding.from_data(key, kid, hmac_key, directory)
reg = NewRegistration.from_data(email='admin@foo.com', external_account_binding=eab)
self.assertEqual(reg.contact, (
assert reg.contact == (
'mailto:admin@foo.com',
))
self.assertEqual(sorted(reg.external_account_binding.keys()),
sorted(['protected', 'payload', 'signature']))
)
assert sorted(reg.external_account_binding.keys()) == \
sorted(['protected', 'payload', 'signature'])
def test_phones(self):
self.assertEqual(('1234',), self.reg.phones)
assert ('1234',) == self.reg.phones
def test_emails(self):
self.assertEqual(('admin@foo.com',), self.reg.emails)
assert ('admin@foo.com',) == self.reg.emails
def test_to_partial_json(self):
self.assertEqual(self.jobj_to, self.reg.to_partial_json())
assert self.jobj_to == self.reg.to_partial_json()
def test_from_json(self):
from acme.messages import Registration
self.assertEqual(self.reg, Registration.from_json(self.jobj_from))
assert self.reg == Registration.from_json(self.jobj_from)
def test_from_json_hashable(self):
from acme.messages import Registration
@@ -284,13 +295,13 @@ class RegistrationTest(unittest.TestCase):
empty_new_reg = NewRegistration()
new_reg_with_contact = NewRegistration(contact=())
self.assertEqual(empty_new_reg.contact, ())
self.assertEqual(new_reg_with_contact.contact, ())
assert empty_new_reg.contact == ()
assert 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())
assert 'contact' not in empty_new_reg.to_partial_json()
assert 'contact' not in empty_new_reg.fields_to_partial_json()
assert 'contact' in new_reg_with_contact.to_partial_json()
assert 'contact' in new_reg_with_contact.fields_to_partial_json()
class UpdateRegistrationTest(unittest.TestCase):
@@ -299,9 +310,8 @@ class UpdateRegistrationTest(unittest.TestCase):
def test_empty(self):
from acme.messages import UpdateRegistration
jstring = '{"resource": "reg"}'
self.assertEqual('{}', UpdateRegistration().json_dumps())
self.assertEqual(
UpdateRegistration(), UpdateRegistration.json_loads(jstring))
assert '{}' == UpdateRegistration().json_dumps()
assert UpdateRegistration() == UpdateRegistration.json_loads(jstring)
class RegistrationResourceTest(unittest.TestCase):
@@ -314,11 +324,11 @@ class RegistrationResourceTest(unittest.TestCase):
terms_of_service=mock.sentinel.terms_of_service)
def test_to_partial_json(self):
self.assertEqual(self.regr.to_json(), {
assert self.regr.to_json() == {
'body': mock.sentinel.body,
'uri': mock.sentinel.uri,
'terms_of_service': mock.sentinel.terms_of_service,
})
}
class ChallengeResourceTest(unittest.TestCase):
@@ -326,8 +336,8 @@ class ChallengeResourceTest(unittest.TestCase):
def test_uri(self):
from acme.messages import ChallengeResource
self.assertEqual('http://challb', ChallengeResource(body=mock.MagicMock(
uri='http://challb'), authzr_uri='http://authz').uri)
assert 'http://challb' == ChallengeResource(body=mock.MagicMock(
uri='http://challb'), authzr_uri='http://authz').uri
class ChallengeBodyTest(unittest.TestCase):
@@ -361,22 +371,22 @@ class ChallengeBodyTest(unittest.TestCase):
}
def test_encode(self):
self.assertEqual(self.challb.encode('uri'), self.challb.uri)
assert self.challb.encode('uri') == self.challb.uri
def test_to_partial_json(self):
self.assertEqual(self.jobj_to, self.challb.to_partial_json())
assert self.jobj_to == self.challb.to_partial_json()
def test_from_json(self):
from acme.messages import ChallengeBody
self.assertEqual(self.challb, ChallengeBody.from_json(self.jobj_from))
assert self.challb == ChallengeBody.from_json(self.jobj_from)
def test_from_json_hashable(self):
from acme.messages import ChallengeBody
hash(ChallengeBody.from_json(self.jobj_from))
def test_proxy(self):
self.assertEqual(jose.b64decode(
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA'), self.challb.token)
assert jose.b64decode(
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA') == self.challb.token
class AuthorizationTest(unittest.TestCase):
@@ -424,7 +434,7 @@ class AuthorizationResourceTest(unittest.TestCase):
authzr = AuthorizationResource(
uri=mock.sentinel.uri,
body=mock.sentinel.body)
self.assertIsInstance(authzr, jose.JSONDeSerializable)
assert isinstance(authzr, jose.JSONDeSerializable)
class CertificateRequestTest(unittest.TestCase):
@@ -435,10 +445,9 @@ class CertificateRequestTest(unittest.TestCase):
self.req = CertificateRequest(csr=CSR)
def test_json_de_serializable(self):
self.assertIsInstance(self.req, jose.JSONDeSerializable)
assert isinstance(self.req, jose.JSONDeSerializable)
from acme.messages import CertificateRequest
self.assertEqual(
self.req, CertificateRequest.from_json(self.req.to_json()))
assert self.req == CertificateRequest.from_json(self.req.to_json())
class CertificateResourceTest(unittest.TestCase):
@@ -451,10 +460,9 @@ class CertificateResourceTest(unittest.TestCase):
cert_chain_uri=mock.sentinel.cert_chain_uri)
def test_json_de_serializable(self):
self.assertIsInstance(self.certr, jose.JSONDeSerializable)
assert isinstance(self.certr, jose.JSONDeSerializable)
from acme.messages import CertificateResource
self.assertEqual(
self.certr, CertificateResource.from_json(self.certr.to_json()))
assert self.certr == CertificateResource.from_json(self.certr.to_json())
class RevocationTest(unittest.TestCase):
@@ -478,12 +486,42 @@ class OrderResourceTest(unittest.TestCase):
body=mock.sentinel.body, uri=mock.sentinel.uri)
def test_to_partial_json(self):
self.assertEqual(self.regr.to_json(), {
assert self.regr.to_json() == {
'body': mock.sentinel.body,
'uri': mock.sentinel.uri,
'authorizations': None,
})
}
def test_json_de_serializable(self):
from acme.messages import ChallengeBody
from acme.messages import STATUS_PENDING
challbs = (
ChallengeBody(
uri='http://challb1', status=STATUS_PENDING,
chall=challenges.HTTP01(token=b'IlirfxKKXAsHtmzK29Pj8A')),
ChallengeBody(uri='http://challb2', status=STATUS_PENDING,
chall=challenges.DNS(
token=b'DGyRejmCefe7v4NfDGDKfA')),
)
from acme.messages import Authorization
from acme.messages import AuthorizationResource
from acme.messages import Identifier
from acme.messages import IDENTIFIER_FQDN
identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com')
authz = AuthorizationResource(uri="http://authz1",
body=Authorization(
identifier=identifier,
challenges=challbs))
from acme.messages import Order
body = Order(identifiers=(identifier,), status=STATUS_PENDING,
authorizations=tuple(challb.uri for challb in challbs))
from acme.messages import OrderResource
orderr = OrderResource(uri="http://order1", body=body,
csr_pem=b'test blob',
authorizations=(authz,))
self.assertEqual(orderr,
OrderResource.from_json(orderr.to_json()))
class NewOrderTest(unittest.TestCase):
"""Tests for acme.messages.NewOrder."""
@@ -494,9 +532,9 @@ class NewOrderTest(unittest.TestCase):
identifiers=mock.sentinel.identifiers)
def test_to_partial_json(self):
self.assertEqual(self.reg.to_json(), {
assert self.reg.to_json() == {
'identifiers': mock.sentinel.identifiers,
})
}
class JWSPayloadRFC8555Compliant(unittest.TestCase):
@@ -508,8 +546,8 @@ class JWSPayloadRFC8555Compliant(unittest.TestCase):
jobj = new_order.json_dumps(indent=2).encode()
# RFC8555 states that JWS bodies must not have a resource field.
self.assertEqual(jobj, b'{}')
assert jobj == b'{}'
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -2,19 +2,20 @@
import http.client as http_client
import socket
import socketserver
import sys
import threading
import unittest
from typing import Set
import unittest
from unittest import mock
import josepy as jose
import pytest
import requests
from acme import challenges
from acme import crypto_util
from acme import errors
import test_util
from acme._internal.tests import test_util
class TLSServerTest(unittest.TestCase):
@@ -54,18 +55,18 @@ class HTTP01ServerTest(unittest.TestCase):
def tearDown(self):
self.server.shutdown()
self.thread.join()
self.server.server_close()
def test_index(self):
response = requests.get(
'http://localhost:{0}'.format(self.port), verify=False)
self.assertEqual(
response.text, 'ACME client standalone challenge solver')
self.assertTrue(response.ok)
assert response.text == 'ACME client standalone challenge solver'
assert response.ok
def test_404(self):
response = requests.get(
'http://localhost:{0}/foo'.format(self.port), verify=False)
self.assertEqual(response.status_code, http_client.NOT_FOUND)
assert response.status_code == http_client.NOT_FOUND
def _test_http01(self, add):
chall = challenges.HTTP01(token=(b'x' * 16))
@@ -81,32 +82,32 @@ class HTTP01ServerTest(unittest.TestCase):
port=self.port)
def test_http01_found(self):
self.assertTrue(self._test_http01(add=True))
assert self._test_http01(add=True)
def test_http01_not_found(self):
self.assertFalse(self._test_http01(add=False))
assert not 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()
with HTTP01Server(('', 0), resources=set(), timeout=0.05) as server:
server_thread = threading.Thread(target=server.serve_forever)
server_thread.start()
client = socket.socket()
client.connect(('localhost', server.socket.getsockname()[1]))
with socket.socket() as client:
client.connect(('localhost', server.socket.getsockname()[1]))
stop_thread = threading.Thread(target=server.shutdown)
stop_thread.start()
server_thread.join(5.)
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
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')
assert not is_hung, 'Server shutdown should not be hung'
@unittest.skipIf(not challenges.TLSALPN01.is_supported(), "pyOpenSSL too old")
@@ -133,6 +134,7 @@ class TLSALPN01ServerTest(unittest.TestCase):
def tearDown(self):
self.server.shutdown() # pylint: disable=no-member
self.thread.join()
self.server.server_close()
# TODO: This is not implemented yet, see comments in standalone.py
# def test_certs(self):
@@ -149,14 +151,12 @@ class TLSALPN01ServerTest(unittest.TestCase):
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),
assert 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):
with pytest.raises(errors.Error):
crypto_util.probe_sni(
b'localhost', host=host, port=port, timeout=1,
alpn_protocols=[b"bad-alpn"])
@@ -190,16 +190,17 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
@mock.patch("socket.socket.bind")
def test_fail_to_bind(self, mock_bind):
from errno import EADDRINUSE
from acme.standalone import BaseDualNetworkedServers
mock_bind.side_effect = socket.error(EADDRINUSE, "Fake addr in use error")
with self.assertRaises(socket.error) as em:
with pytest.raises(socket.error) as exc_info:
BaseDualNetworkedServers(
BaseDualNetworkedServersTest.SingleProtocolServer,
('', 0), socketserver.BaseRequestHandler)
self.assertEqual(em.exception.errno, EADDRINUSE)
assert exc_info.value.errno == EADDRINUSE
def test_ports_equal(self):
from acme.standalone import BaseDualNetworkedServers
@@ -213,8 +214,10 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
for sockname in socknames:
port = sockname[1]
if prev_port:
self.assertEqual(prev_port, port)
assert prev_port == port
prev_port = port
for server in servers.servers:
server.server_close()
class HTTP01DualNetworkedServersTest(unittest.TestCase):
@@ -237,14 +240,13 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase):
def test_index(self):
response = requests.get(
'http://localhost:{0}'.format(self.port), verify=False)
self.assertEqual(
response.text, 'ACME client standalone challenge solver')
self.assertTrue(response.ok)
assert response.text == 'ACME client standalone challenge solver'
assert response.ok
def test_404(self):
response = requests.get(
'http://localhost:{0}/foo'.format(self.port), verify=False)
self.assertEqual(response.status_code, http_client.NOT_FOUND)
assert response.status_code == http_client.NOT_FOUND
def _test_http01(self, add):
chall = challenges.HTTP01(token=(b'x' * 16))
@@ -260,11 +262,11 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase):
port=self.port)
def test_http01_found(self):
self.assertTrue(self._test_http01(add=True))
assert self._test_http01(add=True)
def test_http01_not_found(self):
self.assertFalse(self._test_http01(add=False))
assert not self._test_http01(add=False)
if __name__ == "__main__":
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -4,20 +4,25 @@
"""
import os
import sys
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import josepy as jose
from OpenSSL import crypto
import pkg_resources
from josepy.util import ComparableECKey
from OpenSSL import crypto
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
def load_vector(*names):
"""Load contents of a test vector."""
# luckily, resource_string opens file in binary mode
return pkg_resources.resource_string(
__name__, os.path.join('testdata', *names))
vector_ref = importlib_resources.files(__package__).joinpath('testdata', *names)
return vector_ref.read_bytes()
def _guess_loader(filename, loader_pem, loader_der):

View File

@@ -0,0 +1,16 @@
"""Tests for acme.util."""
import sys
import unittest
import pytest
def test_it():
from acme.util import map_keys
assert {'a': 'b', 'c': 'd'} == \
map_keys({'a': 'b', 'c': 'd'}, lambda key: key)
assert {2: 2, 4: 4} == map_keys({1: 2, 3: 4}, lambda x: x + 1)
if __name__ == '__main__':
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -5,8 +5,8 @@ import functools
import hashlib
import logging
import socket
from typing import cast
from typing import Any
from typing import cast
from typing import Dict
from typing import Mapping
from typing import Optional

View File

@@ -210,23 +210,35 @@ class ClientV2:
raise errors.ValidationError(failed)
return orderr.update(authorizations=responses)
def finalize_order(self, orderr: messages.OrderResource, deadline: datetime.datetime,
fetch_alternative_chains: bool = False) -> messages.OrderResource:
"""Finalize an order and obtain a certificate.
def begin_finalization(self, orderr: messages.OrderResource
) -> messages.OrderResource:
"""Start the process of finalizing an order.
: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
:returns: updated order
:rtype: messages.OrderResource
"""
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, orderr.csr_pem)
wrapped_csr = messages.CertificateRequest(csr=jose.ComparableX509(csr))
self._post(orderr.body.finalize, wrapped_csr)
res = self._post(orderr.body.finalize, wrapped_csr)
orderr = orderr.update(body=messages.Order.from_json(res.json()))
return orderr
def poll_finalization(self, orderr: messages.OrderResource,
deadline: datetime.datetime,
fetch_alternative_chains: bool = False
) -> messages.OrderResource:
"""
Poll an order that has been finalized for its status.
If it becomes valid, obtain the certificate.
:returns: finalized order (with certificate)
:rtype: messages.OrderResource
"""
while datetime.datetime.now() < deadline:
time.sleep(1)
response = self._post_as_get(orderr.uri)
@@ -247,6 +259,22 @@ class ClientV2:
return orderr
raise errors.TimeoutError()
def finalize_order(self, orderr: messages.OrderResource, deadline: datetime.datetime,
fetch_alternative_chains: bool = False) -> messages.OrderResource:
"""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
"""
self.begin_finalization(orderr)
return self.poll_finalization(orderr, deadline, fetch_alternative_chains)
def revoke(self, cert: jose.ComparableX509, rsn: int) -> None:
"""Revoke certificate.

View File

@@ -136,27 +136,33 @@ class SSLSocket: # pylint: disable=too-few-public-methods
def accept(self) -> Tuple[FakeConnection, Any]: # pylint: disable=missing-function-docstring
sock, addr = self.sock.accept()
context = SSL.Context(self.method)
context.set_options(SSL.OP_NO_SSLv2)
context.set_options(SSL.OP_NO_SSLv3)
context.set_tlsext_servername_callback(self._pick_certificate_cb)
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()
# This log line is especially desirable because without it requests to
# our standalone TLSALPN server would not be logged.
logger.debug("Performing handshake with %s", addr)
try:
ssl_sock.do_handshake()
except SSL.Error as error:
# _pick_certificate_cb might have returned without
# creating SSL context (wrong server name)
raise socket.error(error)
context = SSL.Context(self.method)
context.set_options(SSL.OP_NO_SSLv2)
context.set_options(SSL.OP_NO_SSLv3)
context.set_tlsext_servername_callback(self._pick_certificate_cb)
if self.alpn_selection is not None:
context.set_alpn_select_callback(self.alpn_selection)
return ssl_sock, addr
ssl_sock = self.FakeConnection(SSL.Connection(context, sock))
ssl_sock.set_accept_state()
# This log line is especially desirable because without it requests to
# our standalone TLSALPN server would not be logged.
logger.debug("Performing handshake with %s", addr)
try:
ssl_sock.do_handshake()
except SSL.Error as error:
# _pick_certificate_cb might have returned without
# creating SSL context (wrong server name)
raise socket.error(error)
return ssl_sock, addr
except:
# If we encounter any error, close the new socket before reraising
# the exception.
sock.close()
raise
def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, # pylint: disable=too-many-arguments

View File

@@ -1,8 +1,7 @@
"""ACME JSON fields."""
import datetime
from typing import Any
import logging
from typing import Any
import josepy as jose
import pyrfc3339
@@ -35,7 +34,7 @@ class RFC3339Field(jose.Field):
Handles decoding/encoding between RFC3339 strings and aware (not
naive) `datetime.datetime` objects
(e.g. ``datetime.datetime.now(pytz.utc)``).
(e.g. ``datetime.datetime.now(pytz.UTC)``).
"""

View File

@@ -1,6 +1,6 @@
"""ACME protocol messages."""
import datetime
from collections.abc import Hashable
import datetime
import json
from typing import Any
from typing import Dict
@@ -21,7 +21,6 @@ from acme import fields
from acme import jws
from acme import util
ERROR_PREFIX = "urn:ietf:params:acme:error:"
ERROR_CODES = {
@@ -655,12 +654,28 @@ class OrderResource(ResourceWithURI):
:vartype alternative_fullchains_pem: `list` of `str`
"""
body: Order = jose.field('body', decoder=Order.from_json)
csr_pem: bytes = jose.field('csr_pem', omitempty=True)
csr_pem: bytes = jose.field('csr_pem', omitempty=True,
# This looks backwards, but it's not -
# we want the deserialized value to be
# `bytes`, but anything we put into
# JSON needs to be `str`, so we encode
# to decode and decode to
# encode. Otherwise we end up with an
# array of ints on serialization
decoder=lambda s: s.encode("utf-8"),
encoder=lambda b: b.decode("utf-8"))
authorizations: List[AuthorizationResource] = jose.field('authorizations')
fullchain_pem: str = jose.field('fullchain_pem', omitempty=True)
alternative_fullchains_pem: List[str] = jose.field('alternative_fullchains_pem',
omitempty=True)
# Mypy does not understand the josepy magic happening here, and falsely claims
# that authorizations is redefined. Let's ignore the type check here.
@authorizations.decoder # type: ignore
def authorizations(value: List[Dict[str, Any]]) -> Tuple[AuthorizationResource, ...]: # pylint: disable=no-self-argument,missing-function-docstring
return tuple(AuthorizationResource.from_json(authz) for authz in value)
class NewOrder(Order):
"""New order."""

View File

@@ -37,6 +37,7 @@ extensions = [
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
'sphinx_rtd_theme',
]
autodoc_member_order = 'bysource'
@@ -122,14 +123,7 @@ todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# 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
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the

View File

@@ -3,6 +3,6 @@ usage: jws [-h] [--compact] {sign,verify} ...
positional arguments:
{sign,verify}
optional arguments:
options:
-h, --help show this help message and exit
--compact

View File

@@ -3,12 +3,13 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.3.0.dev0'
version = '2.8.0.dev0'
install_requires = [
'cryptography>=2.5.0',
'cryptography>=3.2.1',
'josepy>=1.13.0',
'PyOpenSSL>=17.5.0',
# pyOpenSSL 23.1.0 is a bad release: https://github.com/pyca/pyopenssl/issues/1199
'PyOpenSSL>=17.5.0,!=23.1.0',
'pyrfc3339',
'pytz>=2019.3',
'requests>=2.20.0',
@@ -21,6 +22,15 @@ docs_extras = [
]
test_extras = [
# In theory we could scope importlib_resources to env marker 'python_version<"3.9"'. But this
# makes the pinning mechanism emit warnings when running `poetry lock` because in the corner
# case of an extra dependency with env marker coming from a setup.py file, it generate the
# invalid requirement 'importlib_resource>=1.3.1;python<=3.9;extra=="test"'.
# To fix the issue, we do not pass the env marker. This is fine because:
# - importlib_resources can be applied to any Python version,
# - this is a "test" extra dependency for limited audience,
# - it does not change anything at the end for the generated requirement files.
'importlib_resources>=1.3.1',
'pytest',
'pytest-xdist',
'typing-extensions',
@@ -30,18 +40,17 @@ setup(
name='acme',
version=version,
description='ACME protocol implementation in Python',
url='https://github.com/letsencrypt/letsencrypt',
url='https://github.com/certbot/certbot',
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.7',
python_requires='>=3.8',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',

View File

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

View File

@@ -1,16 +0,0 @@
"""Tests for acme.util."""
import unittest
class MapKeysTest(unittest.TestCase):
"""Tests for acme.util.map_keys."""
def test_it(self):
from acme.util import map_keys
self.assertEqual({'a': 'b', 'c': 'd'},
map_keys({'a': 'b', 'c': 'd'}, lambda key: key))
self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1))
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

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

View File

@@ -1,21 +1,28 @@
""" Utility functions for certbot-apache plugin """
import atexit
import binascii
import fnmatch
import logging
import re
import subprocess
import sys
from contextlib import ExitStack
from typing import Dict
from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
import pkg_resources
from certbot import errors
from certbot import util
from certbot.compat import os
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
logger = logging.getLogger(__name__)
@@ -248,6 +255,8 @@ def find_ssl_apache_conf(prefix: str) -> str:
: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)))
file_manager = ExitStack()
atexit.register(file_manager.close)
ref = importlib_resources.files("certbot_apache").joinpath(
"_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix))
return str(file_manager.enter_context(importlib_resources.as_file(ref)))

View File

@@ -74,15 +74,14 @@ from typing import Set
from typing import Tuple
from typing import Union
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
from certbot_apache._internal import parser
from certbot_apache._internal import parsernode_util as util
from certbot import errors
from certbot.compat import os
class AugeasParserNode(interfaces.ParserNode):
""" Augeas implementation of ParserNode interface """

View File

@@ -21,16 +21,6 @@ from typing import Tuple
from typing import Type
from typing import Union
from certbot_apache._internal import apache_util
from certbot_apache._internal import assertions
from certbot_apache._internal import constants
from certbot_apache._internal import display_ops
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.apacheparser import ApacheBlockNode
from acme import challenges
from certbot import achallenges
from certbot import errors
@@ -42,6 +32,15 @@ from certbot.interfaces import RenewableCert
from certbot.plugins import common
from certbot.plugins.enhancements import AutoHSTSEnhancement
from certbot.plugins.util import path_surgery
from certbot_apache._internal import apache_util
from certbot_apache._internal import assertions
from certbot_apache._internal import constants
from certbot_apache._internal import display_ops
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.apacheparser import ApacheBlockNode
try:
import apacheconfig

View File

@@ -1,10 +1,14 @@
"""Apache plugin constants."""
import atexit
import sys
from contextlib import ExitStack
from typing import Dict
from typing import List
import pkg_resources
from certbot.compat import os
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
MOD_SSL_CONF_DEST = "options-ssl-apache.conf"
"""Name of the mod_ssl config file as saved
@@ -37,8 +41,15 @@ ALL_SSL_OPTIONS_HASHES: List[str] = [
]
"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC"""
AUGEAS_LENS_DIR = pkg_resources.resource_filename(
"certbot_apache", os.path.join("_internal", "augeas_lens"))
def _generate_augeas_lens_dir_static() -> str:
# This code ensures that the resource is accessible as file for the lifetime of current
# Python process, and will be automatically cleaned up on exit.
file_manager = ExitStack()
atexit.register(file_manager.close)
augeas_lens_dir_ref = importlib_resources.files("certbot_apache") / "_internal" / "augeas_lens"
return str(file_manager.enter_context(importlib_resources.as_file(augeas_lens_dir_ref)))
AUGEAS_LENS_DIR = _generate_augeas_lens_dir_static()
"""Path to the Augeas lens directory"""
REWRITE_HTTPS_ARGS: List[str] = [

View File

@@ -6,11 +6,10 @@ from typing import Optional
from typing import Sequence
from typing import Tuple
from certbot_apache._internal.obj import VirtualHost
from certbot import errors
from certbot.compat import os
from certbot.display import util as display_util
from certbot_apache._internal.obj import VirtualHost
logger = logging.getLogger(__name__)

View File

@@ -2,6 +2,7 @@
from typing import Dict
from typing import Type
from certbot import util
from certbot_apache._internal import configurator
from certbot_apache._internal import override_arch
from certbot_apache._internal import override_centos
@@ -12,8 +13,6 @@ from certbot_apache._internal import override_gentoo
from certbot_apache._internal import override_suse
from certbot_apache._internal import override_void
from certbot import util
OVERRIDE_CLASSES: Dict[str, Type[configurator.ApacheConfigurator]] = {
"arch": override_arch.ArchConfigurator,
"cloudlinux": override_centos.CentOSConfigurator,

View File

@@ -5,15 +5,14 @@ from typing import List
from typing import Set
from typing import TYPE_CHECKING
from certbot_apache._internal.obj import VirtualHost
from certbot_apache._internal.parser import get_aug_path
from acme.challenges import KeyAuthorizationChallengeResponse
from certbot import errors
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge
from certbot.compat import filesystem
from certbot.compat import os
from certbot.plugins import common
from certbot_apache._internal.obj import VirtualHost
from certbot_apache._internal.parser import get_aug_path
if TYPE_CHECKING:
from certbot_apache._internal.configurator import ApacheConfigurator # pragma: no cover

View File

@@ -7,12 +7,11 @@ from typing import Pattern
from typing import Set
from typing import Union
from certbot.plugins import common
from certbot_apache._internal.apacheparser import ApacheBlockNode
from certbot_apache._internal.augeasparser import AugeasBlockNode
from certbot_apache._internal.dualparser import DualBlockNode
from certbot.plugins import common
class Addr(common.Addr):
"""Represents an Apache address."""

View File

@@ -2,14 +2,13 @@
import logging
from typing import Any
from certbot import errors
from certbot import util
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
from certbot_apache._internal.configurator import OsOptions
from certbot import errors
from certbot import util
logger = logging.getLogger(__name__)

View File

@@ -1,15 +1,14 @@
""" Distribution specific override class for Debian family (Ubuntu/Debian) """
import logging
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal.configurator import OsOptions
from certbot_apache._internal.obj import VirtualHost
from certbot import errors
from certbot import util
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
from certbot_apache._internal.obj import VirtualHost
logger = logging.getLogger(__name__)

View File

@@ -1,14 +1,13 @@
""" Distribution specific override class for Fedora 29+ """
from typing import Any
from certbot import errors
from certbot import util
from certbot_apache._internal import apache_util
from certbot_apache._internal import configurator
from certbot_apache._internal import parser
from certbot_apache._internal.configurator import OsOptions
from certbot import errors
from certbot import util
class FedoraConfigurator(configurator.ApacheConfigurator):
"""Fedora 29+ specific ApacheConfigurator override class"""

View File

@@ -15,11 +15,10 @@ from typing import Tuple
from typing import TYPE_CHECKING
from typing import Union
from certbot_apache._internal import apache_util
from certbot_apache._internal import constants
from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import constants
if TYPE_CHECKING:
from certbot_apache._internal.configurator import ApacheConfigurator # pragma: no cover

View File

@@ -0,0 +1 @@
"""certbot-apache tests"""

View File

@@ -12,9 +12,8 @@ from certbot_integration_tests.utils import acme_server
SCRIPT_DIRNAME = os.path.dirname(__file__)
def main(args=None):
if not args:
args = sys.argv[1:]
def main() -> int:
args = sys.argv[1:]
with acme_server.ACMEServer('pebble', [], False) as acme_xdist:
environ = os.environ.copy()
environ['SERVER'] = acme_xdist['directory_url']

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