Compare commits

...

533 Commits

Author SHA1 Message Date
Will Greenberg
a2f86b5514 Migrate master branch to main
We're a few years behind the curve on this one, but using "master" as a
programming term is a callous practice that explicitly uses the
historical institution of slavery as a cheap, racist metaphor. Switch to
using "main", as it's the new default in git and GitHub.
2024-09-26 14:51:10 -07:00
Brad Warren
4b51e3004c remove certbot_dns_route53.authenticator (#10014)
This is another and very minor piece of https://github.com/certbot/certbot/issues/9988.

We've done nothing to warn/migrate installations using the old `certbot-route53:auth` plugin name and installations like that still exist according to https://gist.github.com/bmw/aceb69020dceee50ba827ec17b22e08a. We could try to warn/migrate these users for a future release or decide it's niche enough that we'll just let it break, but I think it's easy enough to keep the simple shim around.

This PR just moves the code raising a deprecation warning into `_internal` as part of cleaning up all deprecation warnings I found in https://github.com/certbot/certbot/issues/9988. I manually tested this with a Certbot config using the `certbot-route53:auth` plugin name and renewal worked just fine.
2024-09-18 14:07:35 -07:00
ohemorange
018800c5cc specify channel in weekly mm message (#10013) 2024-09-16 12:31:52 -07:00
Brad Warren
2eb4154169 allow manually triggering GH actions (#10015) 2024-09-16 12:16:51 -07:00
Brad Warren
becc2c3fee Remove deprecated --dns-route53-propagation-seconds (#10010)
* remove dns-route53-prop-secs

* document design difference
2024-09-13 12:14:49 -07:00
ldlb
cb5382d4d5 Remove deprecated features:--manual-public-ip-logging-ok (#9991)
* Remove parameter '--manual-public-ip-logging-ok'

* Update changelog with removal of '--manual-public-ip-logging-ok' flag
2024-09-12 07:21:55 -07:00
ohemorange
6975e32998 Fix weekly mattermost notifier (#10009) 2024-09-11 11:11:47 -07:00
Brad Warren
62962357c5 add parenthesis (#10008) 2024-09-10 13:06:48 -07:00
ohemorange
343b540970 Use new mattermost action workflow (#10007) 2024-09-10 12:53:21 -07:00
ohemorange
089b7efacd Update syntax for mattermost webhooks (#10006) 2024-09-10 12:16:53 -07:00
Brad Warren
1584b0b58c add macos qol suggestions (#9995) 2024-09-09 12:34:00 -07:00
Brad Warren
141b15077c Update changelog for 3.0 and remove update_symlinks and {csr,key}_dir (#10004)
* update changelog to 3.0

we did a similar thing in https://github.com/certbot/certbot/pull/9461

* remove update_symlinks

* remove {csr,key}_dir
2024-09-09 12:31:25 -07:00
Brad Warren
ee2c4844b9 fix centos9 test (#9999) 2024-09-05 16:14:10 -07:00
Shubham Sharma
181813b9b2 add mijn.host (#10002) 2024-09-05 08:56:03 -07:00
Alexandre Detiste
43d0652b0d remove six leftovers (#9996) 2024-08-30 11:38:44 -07:00
Adrien Ferrand
80e68bec26 Update dependencies (27-08-2024) (#9993)
Update dependencies & proactively defends against major bump to Josepy 2+

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2024-08-28 07:22:22 -07:00
Brad Warren
7b2b2b1685 switch from gpg2 to gpg (#9985)
The `gnupg` package from Homebrew only installs a `gpg` binary, not a `gpg2` binary. I had previously worked around this by manually creating an alias, but I think we can do better.

GPG version 1 is ancient and [hasn't seen a release since 2006](https://gnupg.org/download/release_notes.html). Additionally, `gpg` has referred to GPG 2 in Ubuntu since at least 20.04 which is the oldest non-EOL'd version as of writing this so I think this change is safe to make.
2024-08-19 15:24:39 -07:00
Will Greenberg
c3c587001f Update python version to 3.12 and base to core24 in snaps (#9983)
Fixes #9872, originally merged in #9956.

To upgrade to python3.12 as 3.8 is reaching EOL, we need to upgrade the core snap that certbot is based on. The latest version is core24, so we're going with that for longevity. We will want to notify third party snaps to make changes as well. They can release their snaps to a version higher than certbot's, and their users will not be upgraded until the matching (or greater) version of certbot is released. They should do this as otherwise including these changes will break their plugins.

Key documents for this migration are https://snapcraft.io/docs/migrate-core22 and https://snapcraft.io/docs/migrate-core24. The discussion at https://forum.snapcraft.io/t/upgrading-classic-snap-to-core24-using-snapcraft-8-3-causes-python-3-12-errors-at-runtime/ is also relevant to understanding some changes, which may become unnecessary in future versions of snapcraft.


* Migrate primary certbot snap to core24 and python 3.12

* Migrate plugin snaps to core24 and python 3.12

* Migrate to core24 in build_remote

* Run snap tests using python 3.12

* Unstage pyvenv.cfg and set PYTHONPATH

---------

Co-authored-by: Erica Portnoy <ebportnoy@gmail.com>
Co-authored-by: Erica Portnoy <erica@eff.org>
2024-08-08 16:24:11 -07:00
Will Greenberg
281b724996 clarify docs (#9984)
Authored-by: Brad Warren <bmw@eff.org>
2024-08-08 16:16:28 -07:00
Will Greenberg
3d5714f499 dns_server: update BIND9 docker image (#9973)
The 9.16 image isn't published anymore
2024-07-30 22:13:48 +00:00
Will Greenberg
ba9f1939ab Merge pull request #9963 from certbot/test-no-centos7
remove centos7 test
2024-07-03 11:14:07 -07:00
Brad Warren
481c8c0600 remove centos7 test 2024-07-03 09:48:55 -07:00
OmniTroid
35b177a1a0 seperate->separate (#9954) 2024-06-21 06:35:42 -07:00
Will Greenberg
95976762ac certbot-compatibility-test: fix breaking tests (#9955)
Recently our test environments were upgraded to use Docker 26, which
enabled ipv6 loopback by default in containers. This caused tests to
start failing due to an nginx test config which was the sole listener
for ipv6.

This simply removes that ipv6 listen directive in the config, and the
archived version we use for testing.
2024-06-20 11:37:28 -07:00
Will Greenberg
bf64e7f4e4 Merge pull request #9953 from certbot/candidate-2.11.0
Candidate 2.11.0
2024-06-05 20:13:22 -07:00
Will Greenberg
9213154e44 Bump version to 2.12.0 2024-06-05 14:34:41 -07:00
Will Greenberg
810d50eb3d Add contents to certbot/CHANGELOG.md for next version 2024-06-05 14:34:41 -07:00
Will Greenberg
99a4129cd4 Remove built packages from git 2024-06-05 14:34:41 -07:00
Will Greenberg
8db8fcf26c Release 2.11.0 2024-06-05 14:34:40 -07:00
Will Greenberg
6d8fec7760 Update changelog for 2.11.0 release 2024-06-05 14:34:02 -07:00
Will Greenberg
4f3af45f5c Merge pull request #9952 from certbot/test-snap-config-nits
suggest snap_config nits
2024-06-05 10:33:26 -07:00
Brad Warren
8ebd8ea9fb suggest snap_config nits 2024-06-04 14:32:34 -07:00
Brad Warren
83d8fbbd75 Merge pull request #9950 from certbot/test-update-deps
update dependencies
2024-06-04 12:58:38 -07:00
Will Greenberg
0c49ab462f snap_config: oops kwargs are important i guess 2024-06-04 10:37:28 -07:00
Will Greenberg
35091d878f snap_config: switch to newer HttpAdapter interface 2024-06-03 18:13:31 -07:00
Brad Warren
c31f53a225 run tools/pinning/current/repin.sh 2024-05-31 10:10:46 -07:00
Brad Warren
d2a13c55f2 pin back mypy (#9939)
while working on https://github.com/certbot/certbot/issues/9938, i updated our dependencies which updated mypy introducing new errors that mypy wanted me to fix. i think this makes the regularly necessary process of updating our dependencies too tedious and we should instead pin our linters that do this to a specific version and update them manually as desired. we already do this with pylint in the lines above my changes in this PR for the same reason
2024-05-30 11:21:32 -07:00
Will Greenberg
de1ce7340f Merge pull request #9937 from ionos-cloud/docs_add_ionos_certbot_plugin
add IONOS Cloud DNS plugin to the documentation
2024-05-23 10:37:17 -07:00
Will Greenberg
929f9e944f Merge pull request #9944 from lukhnos/maintain-checklist-order
Ensure _scrub_checklist_input honors indices order (#9943)
2024-05-22 14:55:40 -07:00
Lukhnos Liu
6c422774d5 Ensure _scrub_checklist_input honors indices order (#9943)
This fixes a bug where, when a user requests a cert interactively, the
CSR's SANs are not listed in the order that the user has in mind. This
is because, during the input validation, the _scrub_checklist_input
method does not produce a list of tags (which represents the domain
names the user has requested a cert for) in the order of in the given
indices. As a result, the CN of the resulting cert, as well as the
directory name used to store the certs, may not always be what the user
has expected, which should be the first item chosen from the interactive
prompt.
2024-05-22 15:50:02 -04:00
Brad Warren
443ec2200f pin back cloudflare (#9940)
* pin back cloudflare

* update readme
2024-05-16 09:18:21 -07:00
zak905
38cbeb560c add IONOS Cloud DNS plugin to the documentation 2024-05-07 12:08:39 +02:00
Will Greenberg
873f979a25 Replace boulder tests with pebble (#9918)
Pebble 2.5.1 supports OCSP stapling, so we can finally replace all boulder tests/harnesses with the much simpler pebble setup.

Closes #9898

* Remove unused `--acme-server` argument

Since this argument is never set and always defaults to 'pebble', just
remove it to simplify assumptions about which test server's being used.

* Remove boulder option from integration tests

Now that pebble supports all of our test cases, we can move off of
the much more complicated boulder test harness.

* pebble_artifacts: bump to latest pebble release

* pebble_artifacts: fix download path

* certbot-ci: unzip pebble assets

* CI: rip out windows tests/jobs

* tox.ini: rm outdated Windows comment

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

* ci: rm redundant integration test

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

* acme_server: raise error if proxy and http-01 port are both set

* acme_server: rm vestigial preterimate commands stuff

---------

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2024-05-02 12:24:00 -07:00
Will Greenberg
2a41402f2a Merge pull request #9919 from certbot/unpin-poetry-tox
Unpin poetry and use tox >= v4
2024-04-10 11:54:31 -07:00
Brad Warren
6ecf3782ac document the github-releases credential (#9925) 2024-04-04 07:36:44 -07:00
Brad Warren
d1347fce9a Merge pull request #9927 from certbot/candidate-2.10.0
Candidate 2.10.0
2024-04-03 16:43:00 -07:00
Will Greenberg
9412ce9f05 Bump version to 2.11.0 2024-04-02 14:20:25 -07:00
Will Greenberg
fabe7bbc78 Add contents to certbot/CHANGELOG.md for next version 2024-04-02 14:20:25 -07:00
Will Greenberg
1e34fb8b51 Remove built packages from git 2024-04-02 14:20:25 -07:00
Will Greenberg
4d7d0d6d04 Release 2.10.0 2024-04-02 14:20:24 -07:00
Will Greenberg
cf77b3c3fa Update changelog for 2.10.0 release 2024-04-02 14:20:00 -07:00
Will Greenberg
a7674bd45a Merge pull request #9926 from certbot/docker-compose-v2
Switch to using docker compose v2
2024-04-02 14:16:45 -07:00
Will Greenberg
cdeac7a745 Remove CHANGELOG entry, update contributing docs 2024-04-02 13:47:56 -07:00
Will Greenberg
50b2097d38 conftest: use docker compose ls to test 2024-04-02 13:46:38 -07:00
Will Greenberg
30e7f23360 Switch to using docker compose v2
Azure recently dropped the `docker-compose` standalone executable (aka
docker-compose v1), and since it's not receiving updates anymore, let's
get with the times and update to v2 as well.
2024-04-02 12:36:29 -07:00
Brad Warren
248455a92b add back package signing (#9913)
* add packages to git commit

* rename deploy stage

* rename deploy jobs

* set up github releases

* remove v

* tweak release script

* remove publishing windows installer

* update changelog
2024-04-01 10:59:55 -07:00
Erica Portnoy
cca30ace31 actually completely unpin poetry 2024-03-29 12:03:04 -07:00
Erica Portnoy
90348bde4e allowlist the apache conf test farm test 2024-03-29 11:24:51 -07:00
Erica Portnoy
54dd12cd57 update azure environment passing to new required but undocumented format 2024-03-28 18:05:47 -07:00
Erica Portnoy
4e6934a4b6 new tox requires local scripts to be explicitly allowlisted 2024-03-28 17:36:02 -07:00
Erica Portnoy
57bb4e40b7 remove accidentally checked in file 2024-03-28 15:52:11 -07:00
Erica Portnoy
7f885292f9 why does this fix it????? 2024-03-28 15:49:20 -07:00
Erica Portnoy
8978e4dbff remove useless editable-legacy flag 2024-03-28 15:42:41 -07:00
Erica Portnoy
920b717c45 update poetry version using urllib3 workaround 2024-03-28 15:34:24 -07:00
Erica Portnoy
54b7b1883e use legacy editable mode for oldest tests 2024-03-27 16:27:42 -07:00
Erica Portnoy
87ab76fc7d allowlist apache conf test external 2024-03-27 15:44:28 -07:00
Erica Portnoy
4925f71933 work around undocumented lack of ability to reference multi-named envs 2024-03-27 15:11:21 -07:00
Erica Portnoy
39fda1d44d make minor changes to support tox v4 based on https://tox.wiki/en/latest/upgrading.html 2024-03-27 14:16:40 -07:00
Erica Portnoy
c8a1e30981 change tox pin to >= 4 and rerun pinning script 2024-03-27 14:05:59 -07:00
Brad Warren
7abf143394 update centos9stream ami (#9914) 2024-03-20 13:18:36 -07:00
ohemorange
f4e031f505 Add troubleshooting instructions to the finish_release script for snapcraft credential expiry. (#9896) 2024-02-08 21:31:36 +00:00
Brad Warren
2844fdd74a Merge pull request #9895 from certbot/candidate-2.9.0
Candidate 2.9.0
2024-02-08 13:05:29 -08:00
Erica Portnoy
3b183961a9 Bump version to 2.10.0 2024-02-08 11:46:08 -08:00
Erica Portnoy
76411ecca7 Add contents to certbot/CHANGELOG.md for next version 2024-02-08 11:46:08 -08:00
Erica Portnoy
725c64d581 Release 2.9.0 2024-02-08 11:46:07 -08:00
Erica Portnoy
99ae4ac5ef Update changelog for 2.9.0 release 2024-02-08 11:45:17 -08:00
Brad Warren
b8b759f1d2 update dependencies (#9893)
Fixes https://github.com/certbot/certbot/issues/9892 and https://github.com/certbot/certbot/security/dependabot

Upgrading the base docker image has been done in previous PRs like https://github.com/certbot/certbot/pull/9415. Doing this was needed because the [newer versions of `cryptography` need a newer version of rust](https://dev.azure.com/certbot/certbot/_build/results?buildId=7451&view=logs&j=fdd3565a-f3c6-5154-eca9-9ae03666f7bd&t=5dbd9851-46a4-524f-73a8-4028241afcde&l=475).

I ran the full test suite on this branch which you can see in the GitHub status checks below. The boulder tests should fail as they're to be fixed by https://github.com/certbot/certbot/pull/9889 but everything else should pass.
2024-02-07 17:55:30 -08:00
Brad Warren
8b5a017b05 use our own boulder rate limit file (#9889)
* use our own rate limit file

* clarify path
2024-02-07 17:33:07 -08:00
ohemorange
b7ef536ec3 Use the legacy snapcraft build until #9890 is fixed (#9891) 2024-02-07 16:29:08 -08:00
Simon Stier
282df74ee9 add 3rd party certbot-dns-stackit to the docs (#9885) 2024-02-02 08:38:55 -08:00
Alexis
0a565815f9 Docs: Reset requirements.txt path (#9877)
* Reset requirements.txt path

* Add requirements.txt path

* Test config path

* Change docs path

* Amend paths for successful builds

* Place copyright for epub

- Will amend copyright parameter at a later date
2024-02-01 08:27:45 -08:00
ohemorange
d33bbf35c2 Make reconfigure use staging server (#9870)
* Make reconfigure use staging server

* lint and imports

* Unset the account if it's been set in preparation for a dry run

* Add unit tests for checking we switch to staging and don't accidentally modify anything else

* add docstring

* Add test to make sure a requested new account id is saved

* update changelog

* set noninteractive mode for dry run

* error when account or server is set by the user

* switch to checking for changed values in account and server

* recommend using renew instead of certonly for forbidden fields

* change link to renew-reconfiguration
2024-01-26 12:09:20 -08:00
Brad Warren
714a0b348d offer poetry verbosity (#9881) 2024-01-24 16:15:26 -08:00
Alexis
7ca1b8f286 Merge pull request #9876 from certbot/zoraconpatch-yaml-error
Fix YAML Errors in "Formats" section
2024-01-18 10:45:09 -08:00
zoracon
be40e377d9 Move YAML file back and amend paths 2024-01-17 14:51:37 -08:00
zoracon
01cf4bae75 Amend YAML error on reeadthedocs yaml files 2024-01-17 14:46:12 -08:00
Will Greenberg
ef949f9149 Merge pull request #9858 from certbot/zoracon-patch-readthedocs-test
Move .readthedocs.yaml
2024-01-16 14:03:25 -08:00
ohemorange
926d0c7e0f Fix mypy joinpath errors (#9871)
* Fix mypy joinpath errors

* update changelog
2024-01-05 16:35:37 -08:00
Brad Warren
9d8eb6ccfd Add Python 3.12 support (#9852)
* add py312 support

* sed -i "s/\( *'Pro.*3\.1\)1\(',\)/\11\2\n\12\2/" */setup.py

* update pytest.ini comment

* upgrade macos version

* fixup changelog
2023-12-13 10:02:38 -08:00
Alexis
585f70e700 Create .readthedocs.yaml
Test moving config file in attempt to solve build errors
2023-12-07 18:52:05 -08:00
Alexis
21e24264f4 Bump Hardcoded RSA Default in API (#9855)
Rectifies: https://github.com/certbot/certbot/security/advisories/GHSA-pcq2-mjvr-m4jj
2023-12-06 13:00:55 -08:00
Brad Warren
cf78ad3a3d Merge pull request #9853 from certbot/candidate-2.8.0
Candidate 2.8.0
2023-12-05 16:48:55 -08:00
Will Greenberg
dccb92d57f Bump version to 2.9.0 2023-12-05 11:14:39 -08:00
Will Greenberg
f9d31faadc Add contents to certbot/CHANGELOG.md for next version 2023-12-05 11:14:39 -08:00
Will Greenberg
e9225d1cc2 Release 2.8.0 2023-12-05 11:14:38 -08:00
Will Greenberg
3dd1f0eea9 Update changelog for 2.8.0 release 2023-12-05 11:13:52 -08:00
Brad Warren
917e3aba6b add pkg_resources changelog (#9851) 2023-12-05 10:33:49 -08:00
Brad Warren
3833255980 update dependencies (#9848) 2023-12-05 10:33:31 -08:00
Francesco Colista
619654f317 Add support for Alpine Linux (#9834)
Signed-off-by: Francesco Colista <fcolista@alpinelinux.org>
2023-11-22 13:53:31 +01:00
Brad Warren
76f9a33e45 Upgrade the pinned version of pylint (#9839)
* upgrade pylint

* fix upgraded pylint

* downgrade pyopenssl

* remove unneeded ignores

* stop using text

* update sphinx-rtd-theme
2023-11-15 09:52:37 +01:00
Adrien Ferrand
5f67bb99a8 Full cleanup of pkg_resources (#9797)
Fixes #9606

This PRs removes some elements that were related to pkg_resources dependency and its deprecation.
2023-11-13 15:50:32 -08:00
Will Greenberg
d8392bf394 Merge pull request #9832 from certbot/candidate-2.7.4
Update master from 2.7.4 release
2023-11-01 11:36:29 -07:00
Brad Warren
6a89fcbc56 Merge branch 'master' into candidate-2.7.4 2023-11-01 07:50:54 -07:00
Brad Warren
2adaacab82 Bump version to 2.8.0 2023-11-01 06:24:20 -07:00
Brad Warren
2ae810c45a Add contents to certbot/CHANGELOG.md for next version 2023-11-01 06:24:19 -07:00
Brad Warren
b62133e3e1 Release 2.7.4 2023-11-01 06:24:18 -07:00
Brad Warren
a92bb44ff9 Update changelog for 2.7.4 release 2023-11-01 06:23:12 -07:00
Brad Warren
9650c25968 Fix change detection on mutable values (#9829) (#9830)
* handle mutable values

* add unit test

* add changelog entry

* fix typo

(cherry picked from commit c3c29afdca)
2023-11-01 00:10:11 +00:00
Brad Warren
c3c29afdca Fix change detection on mutable values (#9829)
* handle mutable values

* add unit test

* add changelog entry

* fix typo
2023-10-31 16:28:16 -07:00
Brad Warren
dca4ddd3d8 Prep for 2.7.4 (#9823)
* Set the delegated field in Lexicon config to bypass subdomain resolution (#9821)

The Lexicon-based DNS plugins use a mechanism to determine which actual segment of the input domain is actually the DNS zone in which the DNS-01 challenge has to be initiated (eg. `subdomain.domain.com` or `domain.com` for input `subdomain.domain.com`): they tries recursively to configure Lexicon and initiate authentication from the most specific to most generic domain segment, and select the first segment where Lexicon stop erroring out.

This mechanism broke with #9746 because now the plugins call Lexicon client instead of the underlying providers, and the client makes guess on the actual domain requested. Typically for `subdomain.domain.com` it will actually try to authenticate against `domain.com`, and so the mechanism above does not work anymore.

This PR fixes the issue by using the `delegated` field in Lexicon config each time the plugin needs it. This field is designed for this kind of purpose: it will instruct Lexicon what is the actual DNS zone domain instead of guessing it.

I tested the change with one of my OVH account. The expected behavior is re-established and the plugin is able to test `subdomain.domain.com` then `domain.com` as before.

Fixes #9791
Fixes #9818

(cherry picked from commit cf4f07d17e)

* add changelog entry for 9821 (#9822)

(cherry picked from commit 7bb85f8440)

---------

Co-authored-by: Adrien Ferrand <adferrand@users.noreply.github.com>
2023-10-30 10:34:30 -07:00
Brad Warren
7bb85f8440 add changelog entry for 9821 (#9822) 2023-10-28 00:04:11 +02:00
Adrien Ferrand
cf4f07d17e Set the delegated field in Lexicon config to bypass subdomain resolution (#9821)
The Lexicon-based DNS plugins use a mechanism to determine which actual segment of the input domain is actually the DNS zone in which the DNS-01 challenge has to be initiated (eg. `subdomain.domain.com` or `domain.com` for input `subdomain.domain.com`): they tries recursively to configure Lexicon and initiate authentication from the most specific to most generic domain segment, and select the first segment where Lexicon stop erroring out.

This mechanism broke with #9746 because now the plugins call Lexicon client instead of the underlying providers, and the client makes guess on the actual domain requested. Typically for `subdomain.domain.com` it will actually try to authenticate against `domain.com`, and so the mechanism above does not work anymore.

This PR fixes the issue by using the `delegated` field in Lexicon config each time the plugin needs it. This field is designed for this kind of purpose: it will instruct Lexicon what is the actual DNS zone domain instead of guessing it.

I tested the change with one of my OVH account. The expected behavior is re-established and the plugin is able to test `subdomain.domain.com` then `domain.com` as before.

Fixes #9791
Fixes #9818
2023-10-27 10:04:40 -07:00
Will Greenberg
36c78b3717 Merge pull request #9819 from certbot/candidate-2.7.3
Update master from 2.7.3 release
2023-10-26 14:01:31 -07:00
Will Greenberg
bf5475fa74 Merge pull request #9820 from certbot/2.7.3-update
Update 2.7.x from 2.7.3 release
2023-10-26 14:00:37 -07:00
Brad Warren
9bfc9dda5c Merge branch 'master' into candidate-2.7.3 2023-10-25 08:27:20 -07:00
Brad Warren
e904bd4e29 Bump version to 2.8.0 2023-10-24 13:43:22 -07:00
Brad Warren
d140a7df52 Add contents to certbot/CHANGELOG.md for next version 2023-10-24 13:43:22 -07:00
Brad Warren
bd550c09c2 Release 2.7.3 2023-10-24 13:43:20 -07:00
Brad Warren
01405a8fa6 Update changelog for 2.7.3 release 2023-10-24 13:42:05 -07:00
Brad Warren
5bf833fe28 2.7.3 prep (#9817)
* Update changelog for 2.7.2 release

* Release 2.7.2

* helpful: Add an edge case for arguments w/ contained spaces (#9813)

Fixes #9811

(cherry picked from commit 3ae9d7e03a)

* fixes #9805 (#9816)

(cherry picked from commit d1577280ad)

---------

Co-authored-by: Will Greenberg <willg@eff.org>
2023-10-24 12:49:04 -07:00
Brad Warren
d1577280ad fixes #9805 (#9816) 2023-10-24 12:27:19 -07:00
Will Greenberg
3ae9d7e03a helpful: Add an edge case for arguments w/ contained spaces (#9813)
Fixes #9811
2023-10-24 08:26:00 -07:00
Will Greenberg
5594ac20e0 Merge pull request #9809 from certbot/candidate-2.7.2
Candidate 2.7.2
2023-10-19 17:49:02 -07:00
Brad Warren
7f6000f1d4 Merge branch 'master' into candidate-2.7.2 2023-10-19 17:35:05 -07:00
Will Greenberg
1863c66179 Bump version to 2.8.0 2023-10-19 15:34:19 -07:00
Will Greenberg
185c20c71b Add contents to certbot/CHANGELOG.md for next version 2023-10-19 15:34:19 -07:00
Will Greenberg
a1b773cbdc Release 2.7.2 2023-10-19 15:34:18 -07:00
Will Greenberg
937eaef621 Update changelog for 2.7.2 release 2023-10-19 15:33:34 -07:00
Brad Warren
e40741955f Prep for 2.7.2 (#9808)
* helpful: fix handling of abbreviated ConfigArgparse arguments (#9796)

* helpful: fix handling of abbreviated ConfigArgparse arguments

ConfigArgparse allows for "abbreviated" arguments, i.e. just the prefix
of an argument, but it doesn't set the argument sources in these cases.
This commit checks for those cases and sets the sources appropriately.

* failing to find an action raises an error instead of logging

* Update changelog

* Add handling for short arguments, fix equals sign handling

These were silently being dropped before, possibly leading to instances
of `NamespaceConfig.set_by_user()` returning false negatives.

(cherry picked from commit 11e17ef77b)

* Fix finish_release.py (#9800)

* response is value

* rename vars

(cherry picked from commit a96fb4b6ce)

* Merge pull request #9762 from certbot/docs/yaml-config

Add YAML files for Readthedocs requirements

(cherry picked from commit 44046c70c3)

* Update Lexicon requirements to stabilize certbot-dns-ovh behavior (#9802)

* Update minimum Lexicon version required for certbot-dns-ovh

* Add types

* FIx mypy

* Fix lint

* Fix BOTH lint and mypy

(cherry picked from commit 5cf5f36f19)

* simplify code (#9807)

(cherry picked from commit 6f7b5ab1cd)

* Include linting fixes from 8a95c03

---------

Co-authored-by: Will Greenberg <willg@eff.org>
Co-authored-by: Alexis <alexis@eff.org>
Co-authored-by: Adrien Ferrand <adferrand@users.noreply.github.com>
2023-10-19 11:27:21 -07:00
Brad Warren
6f7b5ab1cd simplify code (#9807) 2023-10-18 14:32:07 -07:00
Adrien Ferrand
5cf5f36f19 Update Lexicon requirements to stabilize certbot-dns-ovh behavior (#9802)
* Update minimum Lexicon version required for certbot-dns-ovh

* Add types

* FIx mypy

* Fix lint

* Fix BOTH lint and mypy
2023-10-18 13:19:26 -07:00
Brad Warren
a96fb4b6ce Fix finish_release.py (#9800)
* response is value

* rename vars
2023-10-16 17:54:24 -07:00
Will Greenberg
11e17ef77b helpful: fix handling of abbreviated ConfigArgparse arguments (#9796)
* helpful: fix handling of abbreviated ConfigArgparse arguments

ConfigArgparse allows for "abbreviated" arguments, i.e. just the prefix
of an argument, but it doesn't set the argument sources in these cases.
This commit checks for those cases and sets the sources appropriately.

* failing to find an action raises an error instead of logging

* Update changelog

* Add handling for short arguments, fix equals sign handling

These were silently being dropped before, possibly leading to instances
of `NamespaceConfig.set_by_user()` returning false negatives.
2023-10-13 12:02:01 -07:00
Adrien Ferrand
8a95c030e6 Drop Python 3.7 support (#9792)
* Drop Python 3.7 support

* Fix lint and test

* Check for venv generation

* Update requirements

* Update oldest constaints and compatibility tests runtime
2023-10-13 06:57:42 -07:00
Brad Warren
d9d825ac50 Merge pull request #9794 from certbot/candidate-2.7.1
Update master from 2.7.1 release
2023-10-11 16:37:57 -07:00
Brad Warren
07b1b0d8b2 Merge pull request #9795 from certbot/candidate-2.7.1-2.7.x
Update 2.7.x from 2.7.1 release
2023-10-11 16:37:48 -07:00
Brad Warren
beec975379 Merge branch 'master' into candidate-2.7.1 2023-10-10 08:50:31 -07: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
Brad Warren
8bf21cad25 Bump version to 2.8.0 2023-10-10 06:40:53 -07:00
Brad Warren
dcac5ed8f0 Add contents to certbot/CHANGELOG.md for next version 2023-10-10 06:40:53 -07:00
Brad Warren
228e3f2a8d Release 2.7.1 2023-10-10 06:40:52 -07:00
Brad Warren
6624e0b65c Update changelog for 2.7.1 release 2023-10-10 06:39:19 -07:00
Brad Warren
21113d17c7 Prep for 2.7.1 (#9790)
* 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.

(cherry picked from commit 02efc8c5ca)

* Fix dnsimple typo (#9787)

Fixes https://github.com/certbot/certbot/issues/9786.

(cherry picked from commit 4e60a0d03a)

* update pinned dependencies (#9788)

This fixes the security alerts those with access can see at https://github.com/certbot/certbot/security/dependabot.

(cherry picked from commit 5849ff73fb)

* 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.

(cherry picked from commit 61773be971)

---------

Co-authored-by: Will Greenberg <willg@eff.org>
2023-10-06 18:59:26 +00: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
ohemorange
23090198bf Configuration File Update w/o Certificate Issuance (#9355)
* Add command to update config files without issuing/renewing cert

* toss up a vague untested skeleton

* remove duplicated code

* set certname in config

* consistent name, no zope

* import copy

* reconsitute is in renewal

* import renewal

* import cli

* fix lint errors

* call choose_configurator_plugins for its side effect of writing to config

* Set certonly in choose config plugins as we do for renew

* rewrite by piggybacking on existing side effects of a dry run instead

* do not allow domains to be set while reconfiguring

* remove unused cert_manager.reconfigure

* remove unused imports

* Add comments and messages

* add cli information

* start adding tests

* remove test code

* get certname before setting up plugins

* get plugin from lineage if not set on cli

* import copy

* always reconstitute

* only load cert once

* add error message

* improve comment

* mock everything out for tests

* test functionality is working!

* add tests for adding and modifying hooks

* test that we don't modify the config if the dry run fails

* improve documentation

* add webroot to reconfigure common options

* lint and clean up intermediate artifacts

* mock validate_hooks for windows

* print success message with updated parameters

* Improve success message

* add message for no changes have been made

* improve changed message to show before as well

* syntax

* Add changes will apply at the next renewal message

* lint

* lint really likes dict.items() for some reason

* run the deploy hook

* turn off dry run to test deploy hook

* patch list_hooks call for tests

* factor out reporting results code

* Remove reporting of which values were changed

* add flag to run deploy hook despite doing a dry run, and recommend setting that to yes when running reconfigure and modifying the deploy hook

* missing () around multi-line string

* test if the two dicts are equal instead of finding the actual changes, thus avoiding having to deal with webroot_map being a list

* refer to --deploy-hook instead of deploy hook

* use renewal configuration instead of configuration information

* mention that the deploy hook will use the active cert not the test one

* disable lint and remove new from language asking about running a deploy hook

* pluralize run deploy hook(s)

* Add test for reporting results when there is a webroot map

* update changelog

* Update error message about modifying domains on the certificate

* update changelog

* Add basic integration tests

* Just set -a rather than redoing the whole testing infrastructure

* used webroot in integration test since it's already installed

* file contents are accessed twice now

---------

Co-authored-by: Alex Zorin <alex@zorin.au>
2023-02-04 08:46:08 +11:00
alexzorin
724635bbbd docs: generate a man page with a structure (#9561)
If you looked at [the Debian man page for Certbot](https://manpages.debian.org/bullseye/certbot/certbot.1.en.html) or [the FreeBSD one](https://man.freebsd.org/cgi/man.cgi?query=certbot&sektion=1&apropos=0&manpath=FreeBSD+13.1-RELEASE+and+Ports), you will notice that the entire document is in the "NAME" section. It looks weird in particular on the [FreeBSD man page listing](https://man.freebsd.org/cgi/man.cgi?query=certbot&apropos=1&sektion=0&manpath=FreeBSD+13.1-RELEASE+and+Ports&arch=default&format=html).

This PR adds some structure to the man page by adding a new "Synopsis" section (lifted from the Certbot snap's synopsis) and shoving the `certbot --help all` output into a new "Options" section. I think this should be sustainable for us, without having to worry about the man page in particular.

Fixes #9560.
2023-02-03 11:35:15 -08:00
Daniel McMahon
71a14f5193 Fix docs google permissions (#9556)
* include project level IAM requirements

* add name to authors.md

* Update certbot-dns-google/certbot_dns_google/__init__.py

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

* Update certbot-dns-google/certbot_dns_google/__init__.py

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

* Update certbot-dns-google/certbot_dns_google/__init__.py

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

---------

Co-authored-by: Daniel McMahon <daniel@igloocontrols.com>
Co-authored-by: alexzorin <alex@zorin.au>
2023-02-02 07:59:35 +11:00
alexzorin
cea717db3e docs: update -d flag copy to be CA-agnostic (#9542)
Some confusion ensued in [this community thread](https://community.letsencrypt.org/t/connection-between-ios-9-support-and-subject-common-name-or-x509v3-subject-alternative-name-critical/191619) about the Subject CN, which Certbot omits from the CSR, Let's Encrypt includes in the issued certificate, but some other CAs do not. 

It's probably for the best that we do not entomb Let's Encrypt's current issuance practices in Certbot's documentation.
2023-02-01 10:49:37 -08:00
alexzorin
e75dc1dfd0 show_account: display account thumbprint (#9540)
In #9127, where @osirisinferi added the `show_account` verb, I made a call not to include the thumbprint in the output of `certbot show_account`.

In hindsight, and after a community member asked for this feature, I think it's better to include it. 

It is useful on occasion and `show_account` is fairly specialized anyway. It's only really good for getting your account URL for rate limit increases, checking your contacts, and (now) and doing *magic* with the thumbprint for stateless/distributed HTTP-01 responders.

Without this feature, a clever user might figure out their thumbprint by doing a `certonly --manual --preferred-challenges http` request, but most users would probably be lost.

* show_account: display account thumbprint

* use local key for display
2023-02-01 10:48:13 -08:00
Brad Warren
1b1b27df28 Change coverage upload condition (#9552)
* change coverage upload condition

* fix typo

* set uploadCoverage

* add comment

* change coverage upload condition

* verbose version
2023-02-01 17:08:43 +11:00
Brad Warren
00f8d82808 double progressive percentage (#9557) 2023-02-01 07:05:01 +11:00
Will Greenberg
8226d30af0 Bump up the number of operations to 30 (#9554)
This is the default value, which is sensible since an "operation"
basically corresponds to a GH API call, and 1 won't really let us
do anything.
2023-01-28 08:16:15 +11:00
alexzorin
f0b6ba072f certbot-ci: boulder only supports port 80 for http-01 (#9548)
* certbot-ci: boulder will now only supports port 80 for http-01

* forgot to actually use the http_01_port argument

* print the port the proxy listens on

* try allow binding to privileged ports
2023-01-27 14:44:17 +11:00
Will Greenberg
99fea03c50 Merge pull request #9541 from certbot/remove-legacy-new-authz-support
account: stop storing legacy new_authzr_uri
2023-01-26 17:52:33 -08:00
Alex Zorin
08e008ac54 remove unused attributes from test 2023-01-27 10:41:45 +11:00
Alex Zorin
2e3cace739 remove docstring for removed argument 2023-01-27 10:38:00 +11:00
Alex Zorin
f3c6f7d46e Merge remote-tracking branch 'origin/master' into remove-legacy-new-authz-support 2023-01-27 09:04:16 +11:00
Will Greenberg
b0748b69e7 Replace probot/stale app with a Github Action (#9466)
* Replace probot/stale app with a Github Action

This creates a Github Actions workflow which seems to be the supported
way of automarking issues as stale. Adds a dry-run flag to test it out.

* small fixups

* cron typo

* disable unnecessary permissions

* use friendlier name
2023-01-25 15:59:22 -08:00
Brad Warren
c79a5d4407 Start sending coverage data to codecov (#9544)
* set up codecov

* export coverage data to xml
2023-01-26 08:15:51 +11:00
Brad Warren
4ad71ab5ae Fix tox environments (#9547)
* fix cover tox envs

* make test work on all Pythons

* Remove unused import

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

Co-authored-by: alexzorin <alex@zorin.id.au>
2023-01-25 12:00:06 +11:00
Will Greenberg
81ff6fcc0d acme.messages.Error: add mutability (#9546)
* acme.messages.Error: add mutability

As of Python 3.11, an exception caught within a `with` statement will
update the __traceback__ attribute. Because acme.messages.Error was
immutable, this was causing a knock-on exception, causing certbot to
exit abnormally. This commit hacks in mutability for acme.messages.Error

Fixes #9539

* Add CHANGELOG entry
2023-01-25 09:06:53 +11:00
Brad Warren
613e698199 disable random sleep in lock_test.py (#9545) 2023-01-25 08:05:01 +11:00
Alex Zorin
554143e187 fix lint 2023-01-23 19:43:34 +11:00
Alex Zorin
6505054f62 account: stop storing legacy new_authzr_uri 2023-01-23 18:41:25 +11:00
alexzorin
be3bf316c0 Deprecate {csr, keys} dirs & automatically truncate lineages (#9537)
Based on my design [here](https://docs.google.com/document/d/1jGh_bZPnrhi96KzuIcyCJfnudl4m3pRPGkiK4fTo8e4/edit?usp=sharing). 

Fixes https://github.com/certbot/certbot/issues/4634 and https://github.com/certbot/certbot/issues/4635.

- [x] Deprecate `NamespaceConfig.csr_dir`,`NamespaceConfig.key_dir`, ~~`constants.CSR_DIR` and `constants.KEY_DIR`~~. (`constants` is `_internal` so we can just delete it eventually).
- [x] Update `certbot.crypto_util.generate_csr` and `.generate_key` to make `csr_dir` and `key_dir` optional, respectively.
- [x] Change `certbot._internal.client.Client.obtain_certificate` to no longer include `csr_dir` and `key_dir` to the `.generate_csr` and `.generate_key` calls, respectively.
- Automatically delete unwanted lineage items:
  - [x] In `certbot._internal.storage.RenewableCert`, add a function to truncate the lineage history according to the criteria (keep the current and the 5 prior certificates). 
      - [x] Add a test suite for `truncate` 
  - [x] In `certbot._internal.renewal.renew_cert`, call the lineage truncation function after the symlinks have been updated for the renewal.


* Stop writing new files to /csr and /keys

* storage: add lineage truncation

* remove unused code

* deprecate keys_dir and csr_dir

* update CHANGELOG

* just keep 5 prior certificates, dont be clever with expiry

* docs: remove reference to /archive and /keys

* filter {csr,key}_dir deprecations directly in tests
2023-01-19 17:21:26 -08:00
alexzorin
e7fcd0e08d docs: give webroot and standalone better descriptions (#9536) 2023-01-12 08:03:51 -08:00
alexzorin
8149e255c8 Merge pull request #9534 from certbot/candidate-2.2.0
Update files from 2.2 release
2023-01-12 15:11:23 +11:00
Brad Warren
32a233d93b Bump version to 2.3.0 2023-01-11 13:21:23 -08:00
Brad Warren
a63bf5f88b Add contents to certbot/CHANGELOG.md for next version 2023-01-11 13:21:23 -08:00
Brad Warren
4ab4c9b65d Release 2.2.0 2023-01-11 13:21:22 -08:00
Brad Warren
b56df2fdd9 Update changelog for 2.2.0 release 2023-01-11 13:20:17 -08:00
Brad Warren
b1f22aa8a2 Add progressive release tooling (#9532)
This is based on what I wrote at https://opensource.eff.org/eff-open-source/pl/k1b4pcxnifyj9m7o4wdq7cka8h.
2023-01-11 12:27:38 -08:00
alexzorin
d641f062f2 limit challenge polling to 30 minutes (#9527)
* limit challenge polling to 30 minutes

* Fix docstring typo

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

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2023-01-06 09:24:58 +11:00
Brad Warren
666e12b25d upgrade shellingham (#9529) 2023-01-05 19:30:47 +11:00
Alex Bouma
b81ef33f33 Add link to dns-dnsmanager third party plugin (#9523) 2022-12-25 09:07:12 +11:00
Brad Warren
8155d60e9a remove setuptools pin (#9520) 2022-12-21 10:59:41 +11:00
Brad Warren
124e6d80c3 separate cover environment to workaround tox bug (#9519) 2022-12-19 13:38:04 -08:00
Brad Warren
ac75977156 update 1.32.x reqs (#9516) 2022-12-18 08:16:36 +11:00
alexzorin
63ff1f2a3a Merge pull request #9517 from certbot/candidate-1.32.2
Update master from 1.32.2 release
2022-12-18 08:13:52 +11:00
Brad Warren
74af586f4b Merge branch 'master' into candidate-1.32.2 2022-12-16 14:16:58 -08:00
Brad Warren
c3e1d7e560 Bump version to 2.2.0 2022-12-16 12:46:36 -08:00
Brad Warren
8e30f13e57 Add contents to certbot/CHANGELOG.md for next version 2022-12-16 12:46:36 -08:00
Brad Warren
06bba7167d Release 1.32.2 2022-12-16 12:46:34 -08:00
Brad Warren
118fce34d3 Update changelog for 1.32.2 release 2022-12-16 12:45:28 -08:00
Brad Warren
746631351f Prep for 1.32.2 (#9514)
* Update dependencies (#9505)

* upgrade dependencies

* forbid old setuptools

(cherry picked from commit 70a36fdf00)

* fix help output (#9509)

(cherry picked from commit 27af7b5d15)

* add reminder to repin.sh

* write changelog entry

* pin back mypy
2022-12-16 12:43:17 -08:00
alexzorin
3bc463a66f Merge pull request #9512 from certbot/candidate-2.1.1
Update master from 2.1.1 release
2022-12-17 07:36:22 +11:00
Brad Warren
ac0f4ba3ee Merge branch 'master' into candidate-2.1.1 2022-12-15 08:21:28 -08:00
Brad Warren
d47242296d Bump version to 2.2.0 2022-12-15 07:14:18 -08:00
Brad Warren
edfd84fab5 Add contents to certbot/CHANGELOG.md for next version 2022-12-15 07:14:18 -08:00
Brad Warren
af503ad836 Release 2.1.1 2022-12-15 07:14:17 -08:00
Brad Warren
06d40ec272 Update changelog for 2.1.1 release 2022-12-15 07:13:22 -08:00
Brad Warren
1615185a14 Prepare for 2.1.1 (#9508)
* Update dependencies (#9505)

* upgrade dependencies

* forbid old setuptools

(cherry picked from commit 70a36fdf00)

* prep changelog

* also mention windows
2022-12-15 11:31:56 +11:00
Brad Warren
27af7b5d15 fix help output (#9509) 2022-12-15 11:27:23 +11:00
Brad Warren
a807240db7 add 1.32.x/requirements.txt (#9506) 2022-12-13 11:00:30 +11:00
Brad Warren
70a36fdf00 Update dependencies (#9505)
* upgrade dependencies

* forbid old setuptools
2022-12-13 10:48:17 +11:00
Marcel Robitaille
6b7549bf3a Add filename to dns_common.py configuration errors (#9501)
Fixes #9500

Also print the path to the file with errors for the error "Error parsing credentials configuration" of `dns_common.py`. This makes debugging this error much easier.
2022-12-09 14:55:07 -08:00
alexzorin
4c04328e6d Merge pull request #9498 from certbot/candidate-2.1.0
Update files from 2.1.0 release
2022-12-08 06:37:34 +11:00
Brad Warren
7240e06613 Bump version to 2.2.0 2022-12-07 06:51:42 -08:00
Brad Warren
51bf92f353 Add contents to certbot/CHANGELOG.md for next version 2022-12-07 06:51:42 -08:00
Brad Warren
5e193eb12f Release 2.1.0 2022-12-07 06:51:41 -08:00
Brad Warren
63ea7d54e7 Update changelog for 2.1.0 release 2022-12-07 06:50:45 -08:00
alexzorin
26d3ab86b8 dns-linode: fix confusing credentials example (#9493) 2022-12-05 14:25:07 -08:00
ohemorange
b6695b7213 Merge pull request #9496 from certbot/1.32.x-candidate-1.32.1
Update 1.32.x from 1.32.1 release
2022-12-05 14:22:58 -08:00
ohemorange
1f262e677c Merge pull request #9494 from certbot/candidate-1.32.1
Update master changelog from 1.32.1 release
2022-12-05 14:22:53 -08:00
Brad Warren
023bb494b5 undo help text changes 2022-12-05 08:05:50 -08:00
Brad Warren
70d3fc5916 Merge branch 'master' into candidate-1.32.1 2022-12-05 08:00:21 -08:00
Brad Warren
e22d78b36c Bump version to 2.0.0 2022-12-05 07:04:31 -08:00
Brad Warren
17a7097011 Add contents to certbot/CHANGELOG.md for next version 2022-12-05 07:04:31 -08:00
Brad Warren
27809fbc59 Release 1.32.1 2022-12-05 07:04:30 -08:00
Brad Warren
a6ef3245ae Update changelog for 1.32.1 release 2022-12-05 07:03:16 -08:00
Brad Warren
1b5afb179f Prep for 1.32.1 (#9492)
I wanted to do this because we were notified that https://ubuntu.com/security/notices/USN-5638-3/ affects our snaps. This probably doesn't affect us, but rebuilding to be safe seems worth it to me personally.

I started to just trigger a new v1.32.0 release build, but I don't want to overwrite our 2.0 Docker images under the `latest` tag.

Changelog changes here are similar to what has been done for past point releases like https://github.com/certbot/certbot/pull/8501.

I also cherry picked #9474 to this branch to help the release process pass.

* add changelog

* Use a longer timeout for releases (#9474)

This is in response to the thread starting at https://github.com/certbot/certbot/pull/9330#issuecomment-1320416069.

In addition to this, I plan to add the following text to the step of the release instructions that tells you to wait until Azure Pipelines for the release has finished running:

> Some jobs such as building our snaps can take a long time to complete, however, if the process seems hung, you can cancel the build and then rerun the failed jobs. To do this, click on the build for the release in the link above, make sure you're logged into Azure Pipelines, and then use the cancel/rerun buttons in the top right of the web page.

(cherry picked from commit 30b4fd59a5)
2022-12-05 07:00:44 -08:00
Brad Warren
f0251a7959 fix apache unit tests (#9490)
Fixes https://github.com/certbot/certbot/issues/9481.

I poked around our other uses of this function and they seem OK to me for now, however, I opened https://github.com/certbot/certbot/issues/9489 to track the bigger refactor I think we should do here.
2022-12-01 12:27:24 -08:00
Brad Warren
8390c65a95 fix certbot plugins output (#9488) 2022-12-01 08:56:09 +11:00
alexzorin
fe5e56a52c certbot.interfaces: reintroduce empty zope interfaces (#9486)
* reintroduce certbot.interfaces.I* classes

* add wiki link
2022-12-01 08:42:54 +11:00
alexzorin
c178fa8c0b nginx: on encountering lua directives, produce a better warning (#9475)
* nginx: capitalise product names in warning message properly

* nginx: don't crash on encountering lua directives, warn instead

* add tests

* undo excess newline

* fix oldest tests: use old camelCase function name

* add missing newline in new testdata

* add tests for _by_lua, which should parse fine
2022-11-30 12:03:51 +11:00
Will Greenberg
c78503f21d Merge pull request #9477 from certbot/candidate-2.0.0
Release 2.0.0
2022-11-21 12:12:00 -08:00
Brad Warren
f171f0fcd9 remove botocore warning exceptions (#9476) 2022-11-22 06:42:00 +11:00
Will Greenberg
1e61513859 Bump version to 2.1.0 2022-11-21 09:59:06 -08:00
Will Greenberg
7b27d98370 Add contents to certbot/CHANGELOG.md for next version 2022-11-21 09:59:06 -08:00
Will Greenberg
3d0c2abd3b Release 2.0.0 2022-11-21 09:59:04 -08:00
Will Greenberg
f11dad9e04 Update changelog for 2.0.0 release 2022-11-21 09:58:20 -08:00
Brad Warren
30b4fd59a5 Use a longer timeout for releases (#9474)
This is in response to the thread starting at https://github.com/certbot/certbot/pull/9330#issuecomment-1320416069.

In addition to this, I plan to add the following text to the step of the release instructions that tells you to wait until Azure Pipelines for the release has finished running:

> Some jobs such as building our snaps can take a long time to complete, however, if the process seems hung, you can cancel the build and then rerun the failed jobs. To do this, click on the build for the release in the link above, make sure you're logged into Azure Pipelines, and then use the cancel/rerun buttons in the top right of the web page.
2022-11-21 08:18:06 -08:00
alexzorin
b2dc3e99d6 docs: remove section about dual RSA/ECDSA from User Guide (#9473)
As agreed here: https://github.com/certbot/certbot/pull/9465#discussion_r1025498427
2022-11-17 13:35:20 -08:00
Brad Warren
1c5e56d9c7 Claim Python 3.11 support and add tests (#9471)
* set up 3.11 tests

* fixup warnings

* sed -i "s/\( *'Pro.*3\.1\)0\(',\)/\10\2\n\11\2/" */setup.py

* update changelog
2022-11-18 07:55:27 +11:00
Brad Warren
ad708a0299 remove pylint pinning (#9472) 2022-11-18 07:36:50 +11:00
alexzorin
371cc6f9f1 docs: rewrite ecdsa section of user guide (#9465)
At the time this section was written, it was all about the introduction of support for ECDSA and how users can start taking advantage of that support.

Now that we use ECDSA by default, this piece of documentation probably should serve a new purpose. My idea here is to document the new behavior that we have in 2.0:  new key type on new certificates, old certificates will keep their existing key type.

Users may now be going in the reverse direction with their changes ("I got an ECDSA certificate but I need RSA because I have an old load balancer appliance!") so I have also updated some section titles to be less about ECDSA and more about Key Types in general.

Fixes #9442.
2022-11-17 09:41:34 -08:00
Brad Warren
d244013355 Upgrade pylint (#9470)
* upgrade pylint

* pylint --generate-rcfile > .pylintrc

* fixup pylintrc

* Remove unnecessary lambdas

* fix broad-except

* fix missing timeouts

* fix unit tests

* catch more generic exception
2022-11-17 18:21:14 +11:00
Brad Warren
652d5e96be Drop awscli dependency (#9459)
Fixes https://github.com/certbot/certbot/issues/9458.

* update readme

* drop awscli

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2022-11-16 17:10:18 -08:00
Brad Warren
455f9a0d6c Explain Certbot 2.0 snaps in changelog (#9469) 2022-11-17 11:40:17 +11:00
Brad Warren
9c003bc2d6 Add 2.0 release logic (#9467) (#9468)
This PR:

* Deletes the 2.0 pre-release pipeline
* Causes 1.x releases to be released to Docker Hub without updating the latest tag, PyPI, and the candidate and stable channels of the snap store
* Causes 2.x releases to be released to Docker Hub, PyPI, the beta channel of the snap store, and our Windows installer
We could potentially look into how to continue to do 1.x Windows installer releases through GitHub releases and tech ops tooling, but I personally don't think it's worth it right now.

This PR DOES NOT do anything about progressive snap releases. I think we can revisit this when/if we decide (how) to do them.

(cherry picked from commit 09af133af3)
2022-11-17 11:38:40 +11:00
Brad Warren
09af133af3 Add 2.0 release logic (#9467)
This PR:

* Deletes the 2.0 pre-release pipeline
* Causes 1.x releases to be released to Docker Hub without updating the latest tag, PyPI, and the candidate and stable channels of the snap store
* Causes 2.x releases to be released to Docker Hub, PyPI, the beta channel of the snap store, and our Windows installer
We could potentially look into how to continue to do 1.x Windows installer releases through GitHub releases and tech ops tooling, but I personally don't think it's worth it right now.

This PR DOES NOT do anything about progressive snap releases. I think we can revisit this when/if we decide (how) to do them.
2022-11-16 15:29:53 -08:00
Will Greenberg
21ef8e4332 main: set more permissive umask when creating work_dir (#9448)
* main: set more permissive umask when creating work_dir

This'll guarantee our working dir has the appropriate permissions,
even when a user has a strict umask

* update changelog

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2022-11-14 14:35:29 -08:00
Brad Warren
383a42851c Merge pull request #9461 from certbot/merge-2.0.x
Merge 2.0.x
2022-11-14 09:50:15 -08:00
Alex Zorin
f9962c3013 changelog: add 2.0 entries 2022-11-12 17:00:06 +11:00
Alex Zorin
a384886a15 changelog: update latest section to 2.0.0 2022-11-12 16:48:40 +11:00
Brad Warren
10f60bab0c Merge pull request #9460 from alexzorin/2.0.x
Merge `master` into `2.0.x`
2022-11-11 12:36:48 -08:00
Alex Zorin
202db15274 fix new mypy complaints 2022-11-11 18:03:57 +11:00
Alex Zorin
1773edcad0 Merge remote-tracking branch 'origin/master' into 2.0.x 2022-11-11 17:25:42 +11:00
Brad Warren
a8015fa102 Merge pull request #9457 from certbot/candidate-1.32.0
Release 1.32.0
2022-11-09 14:00:14 -08:00
Erica Portnoy
fd22bd0f66 Bump version to 1.33.0 2022-11-08 15:23:35 -08:00
Erica Portnoy
c087b6f6c9 Add contents to certbot/CHANGELOG.md for next version 2022-11-08 15:23:35 -08:00
Erica Portnoy
d88b9a5d11 Release 1.32.0 2022-11-08 15:23:34 -08:00
Erica Portnoy
dd2df86625 Update changelog for 1.32.0 release 2022-11-08 15:22:20 -08:00
alexzorin
7ab82b6f64 repin dependencies (#9454) 2022-11-02 12:32:00 -07:00
Brad Warren
9cf062d8d4 disable poetry's cache (#9453) 2022-11-02 10:23:57 -07:00
Kevin Jones
63de0ca9e6 Use https: protocol instead of deprecated git: protocol (#9452) 2022-10-31 14:17:50 -07:00
Will Greenberg
f73e062c7a Fix changelog entry (#9444)
* Fix changelog entry

* move to 1.32.0

Co-authored-by: Brad Warren <bmw@eff.org>
2022-11-01 07:22:07 +11:00
Will Greenberg
7865bbd39a Add comment explainig the load-bearing debug flags (#9443) 2022-10-27 14:47:29 +11:00
Will Greenberg
eed1afb808 certbot-apache: use httpd by default for CentOS/RHEL (#9402)
* certbot-apache: use httpd for newer RHEL derived distros

A change in RHEL 9 is causing apachectl to error out when used
with additional arguments, resulting in certbot errors. The CentOS
configurator now uses httpd instead for RHEL 9 (and later) derived
distros.

* Single CentOS class which uses the apache_bin option

* soothe mypy

* Always call super()._override_cmds()
2022-10-26 15:07:02 -07:00
Brad Warren
529942fe4b Unpin poetry (#9438)
* unpin poetry

* export constraints
2022-10-21 10:59:33 +02:00
Brad Warren
3a738cadc3 Remove docker-compose dependency (#9436)
This is progress towards https://github.com/certbot/certbot/issues/9370 as discussed at https://github.com/certbot/certbot/pull/9435.

I kept the command using `docker-compose` because `docker compose` doesn't seem that widely recognized yet and https://www.docker.com/blog/announcing-compose-v2-general-availability/ describes aliasing `docker-compose` to `docker compose` on newer systems by default.

* refactor boulder shutdown

* remove docker-compose dep

* Reorder shutdown process
2022-10-20 13:07:18 -07:00
alexzorin
5270c34dd7 docs: use modern tsig-keygen util in certbot-dns-rfc2136 (#9424)
Fixes #7206.

I think it's about time we did this:

- `dnssec-keygen` on new distros doesn't support the HMAC algorithms anymore, so our instructions don't work.
- The oldest distros we support are Debian Buster (`9.11.5.P4+dfsg-5.1+deb10u7`) and CentOS 7 (`9.11.4-26.P2.el7_9.9`), which ship `tsig-keygen` and support `HMAC-SHA512`.
2022-10-17 16:55:00 -07:00
alexzorin
314ded348e docs: add third-party dns-multi plugin (#9430) 2022-10-13 17:58:18 -07:00
Phil Martin
92aaa9703b TSIG SOA query fix (#9408)
* Use the TSIG keyring for the initial SOA request

Helps allow the use of keys in BIND ACLs to help certbot update the correct zone. Previously TSIG was only used for zone updates, rather than for both the authoritative SOA request and zone update.

* Update CHANGELOG.md

* Update AUTHORS.md

* Workaround for mypy failure due to dnspython stubs

As per https://github.com/certbot/certbot/pull/9408#issuecomment-1257868864

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2022-10-14 08:52:08 +11:00
alexzorin
f5e7d16303 don't superfluously ask whether to renew, when changing key type (#9421)
* dont superfluously ask whether to renew, when changing key type

* reorder conditions

this prevents "Certificate not yet due for renewal" being printed

* and replace superfluous mock

* mock renewal.should_renew
2022-10-06 14:29:58 -07:00
Brad Warren
a0b8a2cc62 Merge pull request #9426 from certbot/2.0-merge-master
2.0.x: merge master and bump version to 2.0.0.dev0
2022-10-06 12:04:35 -07:00
Alex Zorin
d5d8739783 bump version to 2.0.0.dev0 2022-10-05 05:17:29 +11:00
Alex Zorin
4fcc0f7c2a Merge branch 'master' into 2.0-merge-master 2022-10-05 05:15:39 +11:00
alexzorin
e84271b36b Merge pull request #9425 from certbot/candidate-1.31.0
Release 1.31.0
2022-10-05 05:09:37 +11:00
Brad Warren
3eac48ba5a Bump version to 1.32.0 2022-10-04 07:41:45 -07:00
Brad Warren
9409c086d4 Add contents to certbot/CHANGELOG.md for next version 2022-10-04 07:41:45 -07:00
Brad Warren
d0fbde9126 Release 1.31.0 2022-10-04 07:41:44 -07:00
Brad Warren
049e29cc1c Update changelog for 1.31.0 release 2022-10-04 07:40:41 -07:00
osirisinferi
e3448fa0d5 Fix typo in install.rst (#9422) 2022-10-02 10:06:27 +11:00
Alexis
2460d9ad0c Docs: Rewrite Installation Instructions: User Guide (#9220)
* Rewrite Installation Instrcutions: User Guide

Simplifying Installation instructions in User Guide

- First step in simplifying docs for Certbot Users

* Amend Install Doc

- Address errors
- Clean up links

* Update certbot/docs/install.rst

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

* Update certbot/docs/install.rst

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

* Update certbot/docs/install.rst

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

* Amend instructions
- clarify requirements
- update outdated advice
- remove direct link

* Remove unintentinally added files

Co-authored-by: alexzorin <alex@zor.io>
2022-10-01 09:13:30 +10:00
Charlie Britton
4ec115cca5 Add single domain option for OVH DNS creds (#9419) 2022-09-29 19:06:41 -07:00
alexzorin
fdd2a7e937 plugins: remove support for dist:plugin plugin names (#9359)
* plugins: remove support for dist:plugin plugin names

* address feedback
2022-09-30 07:09:03 +10:00
Will Greenberg
26d479d6e3 Remove external mock dependency (#9331)
* Remove external mock dependency

This also removes the "external-mock" test environment

* remove superfluous ignores

* remove mock warning ignore from pytest.ini

* drop deps on mock in oldest, drop dep on types-mock

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2022-09-28 16:17:03 -07:00
Will Greenberg
c9eba6ccd3 Merge pull request #9353 from alexzorin/ecdsa-default-flag
change default key_type from rsa to ecdsa
2022-09-27 12:12:48 -07:00
Alex Zorin
5d6e067a74 fix tests broken by #9262 2022-09-27 13:51:35 +10:00
Alex Zorin
652c06a8ae fix typo in key conflict error message 2022-09-27 13:51:16 +10:00
Alex Zorin
f6d532a15b Merge remote-tracking branch 'origin/2.0.x' into ecdsa-default-flag 2022-09-27 12:38:20 +10:00
alexzorin
212c2ba990 error out when --reuse-key conflicts with other flags (#9262)
* error out when --reuse-key conflicts with other flags

* add unit test

* add integration tests

* lint
2022-09-27 12:37:24 +10:00
Brad Warren
c42dd567ca remove source_address arg (#9418) 2022-09-27 12:30:05 +10:00
osirisinferi
a845ab8446 Fix regression in Cloudflare library (#9417)
* Fix regression in CF library

* Add changelog entry

* Fix typo

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

* Add note to docs

Co-authored-by: alexzorin <alex@zor.io>
2022-09-27 07:48:30 +10:00
Brad Warren
758cfb9f79 upgrade base docker image (#9415) 2022-09-26 20:36:08 +10:00
Will Greenberg
7c3b9043a1 Merge pull request #9414 from certbot/simplify-ci
Actually test everything in test- branches (besides deployment)
2022-09-22 14:09:20 -07:00
Brad Warren
e0b639397b actually test everything in test- branches 2022-09-22 07:07:43 -07:00
Brad Warren
db31a8c1f5 Upgrade dependency pinnings (#9412)
* upgrade dependencies

* remove unused ignore
2022-09-21 18:37:30 +10:00
osirisinferi
d214da191d Certbot-specific temp log dir prefix (#9406)
Fixes #9405.
2022-09-16 06:34:02 -07:00
Patrick Neumann
0326cbf95e Update generate_dnsplugins_snapcraft.sh (#9398)
There is no need for two interconneced (pipe) processes.
The regular expression in the grep part is not strict enough in some cases (presence of long_description.
sed does not seem to support perl regular expressions ("\s").
Some Python developers prefer single quotes to double qoutes. Some even go so far as to adapt generated templates (setup.py).
This update will (hopefully) fix this all.
This was tested on Ubuntu 20.04.5 LTS (Focal Fossa) and macOS 12.5.1 (Monterey).
2022-09-13 07:16:27 -07:00
ohemorange
314b2ef89b Merge pull request #9404 from certbot/master
Add 2.0 pre-release pipeline to 2.0.x branch
2022-09-12 15:56:54 -07:00
Brad Warren
39e8d14e1b Set up 2.0 pre-releases (#9400)
* update credential info

* update release tooling to use candidate channel

* split deploy jobs

* pass parameter through

* add 2.0 pipeline prerelease

* add comments

* quote file path
2022-09-09 14:23:39 -07:00
alexzorin
f4db687130 Merge pull request #9401 from alexzorin/update-2.0.x
Merge master into 2.0.x
2022-09-09 09:59:52 +10:00
Alex Zorin
63771b48bb Merge remote-tracking branch 'origin/master' into update-2.0.x 2022-09-09 08:37:56 +10:00
Brad Warren
80071c86f5 Merge pull request #9399 from certbot/candidate-1.30.0
Release 1.30.0
2022-09-08 10:28:45 -07:00
Will Greenberg
614eaf6898 Bump version to 1.31.0 2022-09-07 11:09:12 -07:00
Will Greenberg
0b284125d2 Add contents to certbot/CHANGELOG.md for next version 2022-09-07 11:09:12 -07:00
Will Greenberg
667b736879 Release 1.30.0 2022-09-07 11:09:11 -07:00
Will Greenberg
c68d4d6389 Update changelog for 1.30.0 release 2022-09-07 11:08:15 -07:00
Adrien Ferrand
9d736d5c9c Remove zope from Certbot (#9161)
* Remove zope and the internal reporter util.

* remove zope references from .pylintrc and pytest.ini

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2022-09-07 15:09:32 +10:00
Will Greenberg
529a0e2272 Remove deprecated functions (#9315)
* Remove deprecated functions

* rm unused imports

* actually remove execute_command!

* revert changelog

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2022-09-07 13:31:21 +10:00
Will Greenberg
a4a2315537 Removed deprecated functions (#9314)
* Removed deprecated functions

* rm import of distutils.version

* revert changelog

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2022-09-07 13:20:56 +10:00
alexzorin
5e247d1683 unexport attributes in certbot.display.util (#9358) 2022-09-07 13:00:05 +10:00
Will Greenberg
20ca9288d5 Add UI text recommending multi-domain certs (#9393)
* Suggest multi-domain certs in domain selection menu

* Update changelog

* lint: fix long line

Co-authored-by: Alex Zorin <alex@zorin.id.au>
2022-09-07 12:55:58 +10:00
alexzorin
804ca32314 acme: remove Client and BackwardsCompatibleClientV2 (#9356)
* acme: remove Client and BackwardsCompatibleClientV2

* remove ClientTestBase and some unused variables

* add ClientV2.get_directory

* tweak ToS callback code

* acme: update example to use ClientV2.get_directory

* simplify ToS callback further into one step

* further removal of acmev1-related code

- remove acme.client.ClientBase
- remove acme.mixins.VersionedLEACMEMixin
- remove acme.client.DER_CONTENT_TYPE
- remove various ACMEv1 special cases
- remove acme.messages.ChallengeResources.combinations

* remove .mixins.ResourceMixin, fields.resource, fields.Resource
and resource field from various .message classes.

* simplify acme.messages.Directory:

- remove Directory.register
- remove HasResourceType and GenericHasResourceType
- remove ability to look up Directory resources by anything other
  than the exact field name in RFC8555 (section 9.7.5)

* remove acme.messages.OLD_ERROR_PREFIX and support the old prefix

* remove acme.mixins

* reorder imports

* add comment to Directory about resource lookups

* s/new-cert/newOrder/

* get rid of `resource` sillyness in tests

* remove acmev1 terms-of-service support from directory
2022-09-06 14:36:55 -07:00
alexzorin
c20d40ddba acme: further deprecations (#9395)
* acme: deprecate acme.fields.Resource and .resource

* acme: deprecate .messages.OLD_ERROR_PREFIX

* acme: deprecate .messages.Directory.register

* acme: clean up deprecations

* dont use unscoped filterwarnings

* change deprecation approach for acme.fields

* warn on non-string keys in acme.messages.Directory

* remove leaked filterwarnings in BackwardsCompatibleClientV2Test

* remove non-string lookups of acme.messages.Directory
2022-09-02 06:55:04 -07:00
alexzorin
f7e61edcb2 deprecate more attributes in acme (#9369)
* deprecate more attributes in acme

* Deprecate .Authorization.combinations by renaming the field and
  deprecating in getters/setters

* Silence deprecation warnings from our own imports of acme.mixins

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2022-08-30 14:41:53 -07:00
Brad Warren
f9d148be56 Upgrade CI OS (#9391)
* upgrade ubuntu

* upgrade macos

* use python3
2022-08-30 16:39:48 +10:00
Brad Warren
012314d946 Deprecate source address (#9389)
* deprecate source_address

* filter warnings

* fix route53 tests

* test warning

* update docstring
2022-08-30 10:28:47 +10:00
alexzorin
d8e45c286d apache: remove support for Apache 2.2 and CentOS 6 (#9354)
* apache: remove support for Apache 2.2 and CentOS 6

* delete more unused code

* remove unused attributes

* reorganize REWRITE_HTTPS_ARGS*
2022-08-29 10:05:48 -07:00
alexzorin
a81d58fa6e deprecate certbot-dns-cloudxns (#9367) 2022-08-27 07:25:37 +10:00
Brad Warren
cb632c376f encourage words before code (#9377) 2022-08-17 09:01:51 +10:00
Matthew W. Thomas
94bbb4c44c docs: add BunnyDNS to list of 3rd-party plugins (#9375)
* docs: add BunnyDNS to list of 3rd-party plugins

You can find the plugin here:
https://github.com/mwt/certbot-dns-bunny
It's for [BunnyDNS](https://bunny.net/dns/).

* Update AUTHORS.md
2022-08-12 14:03:08 -07:00
alexzorin
2574a8dfb5 remove all cloudxns-related code (#9361) 2022-08-10 11:01:11 -07:00
Gusmanov Timur
1b79c077a6 add dns-yandexcloud authentication plugin to third-party plugins (#9371) 2022-07-29 12:01:01 -07:00
Brad Warren
b73f3e2b16 pin back pylint (#9368) 2022-07-29 12:58:47 +10:00
alexzorin
42a4d30267 deps: remove pyjwt dependency (#9337)
* deps: remove pyjwt dependency

* pinning: strip extras from dependencies

`poetry export` outputs in requirements.txt format, which is now
apparently producing "dep[extra]==...". We are using this output
as the constraints file for pip and pip's new resolver does not
permit extras in the constraints file.

This change filters out the extras specifiers.

* repin current dependencies

* fix new pylint complaints

* silence lint about distutils.version

We have already deprecated the function and it'll be removed in
2.0.

* docs: set sphinx language to 'en'

this is emitting a warning and failing the build

* Revert "pinning: strip extras from dependencies"

This reverts commit 11268fd23160ac53fd8dad7a2ff15e453678e159.

* pin poetry back to avoid extras issue

* repin

* fix new mypy complaints in acme/
2022-07-28 17:26:12 -07:00
Brad Warren
e9e7a69c7b Update Azure Docker docs (#9363)
* describe docker access token

more

* Remove extra spaces

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

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2022-07-28 13:28:36 -07:00
Preston Locke
495b97aafe Clarify in docs that deletion does not revoke (#9348)
* Clarify in docs that deletion does not revoke

* Add myself to AUTHORS.md

* Move new paragraph below first note and change its wording
2022-07-26 16:03:53 -07:00
alexzorin
f82530d8c0 letstest: replace ubuntu 21.10 with 22.04 (#9364)
as ubuntu 21.10 is now EOL
2022-07-25 13:43:49 -07:00
alexzorin
ae7967c8ae docs: how to override the trusted CA certificates (#9357)
* docs: how to override the trusted CA certificates

* Update certbot/docs/using.rst

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

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2022-07-19 16:17:27 -07:00
Alex Zorin
82b6e15be7 change default key_type from rsa to ecdsa 2022-07-18 18:27:19 +10:00
Shahar Naveh
32608a142b DOC: Fix typo (#9346)
Co-authored-by: Shahar Naveh <>
2022-07-11 11:30:50 -07:00
Shahar Naveh
b9f6c3e5b6 DEP: Pin version of cryptography (#9339)
* DEP: Pin version of cryptography

* Added myself to authors:)

Co-authored-by: Shahar Naveh <>
2022-07-08 12:57:48 -07:00
ohemorange
184e087edf Prompt for username in finish_release.py (#9343)
The local machine's username may not be the same as the one on the CSS, so let's prompt for it instead.
2022-07-08 12:27:50 -07:00
Will Greenberg
1da36a9278 If a snap build times out, dump the logs (#9340) 2022-07-07 14:31:48 -07:00
Will Greenberg
2b1255cd6a finish_release.py: fix revision regex, add more logging (#9342) 2022-07-06 17:40:27 -07:00
ohemorange
c599aa08ad Merge pull request #9341 from certbot/candidate-1.29.0
Release 1.29.0
2022-07-06 13:18:26 -07:00
Will Greenberg
f1f526d63c Bump version to 1.30.0 2022-07-05 11:16:40 -07:00
Will Greenberg
ef0746eb1d Add contents to certbot/CHANGELOG.md for next version 2022-07-05 11:16:40 -07:00
Will Greenberg
befa4434ad Release 1.29.0 2022-07-05 11:16:39 -07:00
Will Greenberg
7e2105fca8 Update changelog for 1.29.0 release 2022-07-05 11:15:47 -07:00
Alexis
6e1696ba32 Add Signed Windows Installer Workflow (#9076)
* Add Code Signing action for Windows Installer

* Clean up variable names and input

* Amend and add to documentation per PR guidelines

* Update tools/finish_release.py

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

* Update tools/finish_release.py

Amend typo

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

* Amend release script for better work flow

- SCP commands to upload and download unsigned & signed installers from CSS

* Collapse spaces

* Update tools/finish_release.py

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

* Create new windows signer function

* Update Windows Installer Script

- Update change log
- add new function for signing and document
- @TODO Streammline SSH session

* Remove Azure and Github release methods

- Methods moved to CSS
- Reduced to a ssh function that triggers the process on a CSS

* Amend Chnagelog and Remove Unneeded Deps

* Update tools/finish_release.py

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

* Add Verison Fetch Function

- For the purpose of snap releases
- Add back package to dev extras for function

* Chaneg path in ssh command

* Amend release script

* Amend the ssh command for CSS

* Update tools/finish_release.py

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

* Update script with proper path and subprocess call

* Update ssh command

* Correct typo in path

* Fix typo in path

* Update certbot/CHANGELOG.md

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

* Remove missed conflict text

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
Co-authored-by: ohemorange <ebportnoy@gmail.com>
2022-06-29 15:52:50 -07:00
Amir Omidi
dedbdea1d9 Update generated CSRs to create V1 CSRs (#9334)
* Update generated CSRs to create V1 CSRs

Per the RFC: https://datatracker.ietf.org/doc/html/rfc2986#section-4

Version 3 CSRs, as far as I can tell, are not a thing (yet).

Relevant code in Go, for example: https://cs.opensource.google/go/go/+/refs/tags/go1.18.3:src/crypto/x509/x509.go;l=1979

* Update AUTHORS.md

* Unit test for PR #9334

* Add a small comment explaining this line for future readers.

* Add info to changelog

Co-authored-by: Paul Buonopane <paul@namepros.com>
2022-06-29 14:24:24 +10:00
Alexis Kim
b9f9952660 removed certbot-auto references from docs (#9333) 2022-06-28 11:43:57 +10:00
ohemorange
1d2540629f Use a different timeout for nightly vs daytime (release and extended) builds (#9330) 2022-06-22 18:06:53 -07:00
alexzorin
49f21bcc9f deps: bump pyOpenSSL in oldest pinnings (#9329) 2022-06-22 16:38:32 -07:00
ohemorange
885ebf80e3 Change snapcraft authentication to use SNAPCRAFT_STORE_CREDENTIALS (#9326)
* try the easy thing of just doing what the error message says

* temporarily add deploy stage to extended tests to see if it uploads properly

* follow instructions on https://forum.snapcraft.io/t/snapcraft-authentication-options/30473

* just run the packaging jobs for speed

* fix formatting

* import changes from test- branch and revert temporary changes

* Update instructions in deploy-stage.yml
2022-06-20 06:37:40 +10:00
Will Greenberg
7505bb0c60 Drop the snap build tiemout to 90 minutes (#9320)
It was previously 5.5 hours, which was just to have an exception thrown
before Azure's 6 hour timeout. Generally we aren't seeing this step take
more than 45 minutes, so 90 minutes seems like more than enough.
2022-06-14 15:09:09 -07:00
Will Greenberg
99da999b2b Merge pull request #9318 from certbot/docs-clarify-plugin-contributions
docs: clarify that we're not merging any new plugins (not just DNS)
2022-06-13 11:37:52 -07:00
Alex Zorin
7197ae4b77 docs: clarify that we're not merging any new plugins (not just DNS) 2022-06-09 07:51:28 +10:00
osirisinferi
1a25c4052c Change query_registration() to use _get_v2_account() (#9307)
* Change `query_registration()` to use `_get_v2_account()`

* Improve `_get_v2_account()`

Required for proper working of `certbot.main.update_registration()`. This
function updates the `regr.body` locally instead of passing the fields
which need to be updated to `acme.client.update_registration()` as a
separate argument in the `update` parameter.

* Revert "Improve `_get_v2_account()`"

This reverts commit e88a23ad76b6dc092645a870b3b5f99bd4fbd095.

* Improve `_get_v2_account() (version 2)

Instead of e88a23a, this change should be more compatible with older
ACMEv1 accounts used through symlinking ACMEv2 account dirs to the
existing ACMEv1 account dirs.
It should also still be compatible with `certbot.main.update_registration`.

* Move and slightly update CHANGELOG entry
2022-06-09 07:49:40 +10:00
James Balazs
a73a86bbc0 Retry errors with subproblems in obtain_certificate with --allow-subset-of-names (#9251) (#9272)
* Handle CAA failure on finalize_order during renewal (#9251)

* Fix CAA error on renewal test

* Attempt to fix failing test in CI

* Retry errors with subproblems in obtain_certificate_from_csr with allow_subset_of_names

Only retry if not all domains succeeded

* Back out renewal changes

* Fix linting error line too long

* Update log message for more general case and only log on retry

* Changelog entry

* Add retry logic to order creation

* Changelog entry wording

* Fix acme error handling when no subproblems provided

* Fix test name

* Use summarize domain list to display list of failed domains

* Tidy up incorrect client tests

* Remove unused var and output all failed domains

* Add logging to failed authorization case

* use _retry_obtain_certificate for failed authorizations

* Fix typo failing in CI

* Retry logic comments

* Preserve original error

* Move changelog entry to latest version
2022-06-08 18:36:13 +10:00
alexzorin
3b211a6e1b Merge pull request #9317 from certbot/candidate-1.28.0
Candidate 1.28.0
2022-06-08 16:48:40 +10:00
Will Greenberg
4dd603f786 Bump version to 1.29.0 2022-06-07 12:43:12 -07:00
Will Greenberg
0dac0f173a Add contents to certbot/CHANGELOG.md for next version 2022-06-07 12:43:12 -07:00
Will Greenberg
b9f9ebc4fc Release 1.28.0 2022-06-07 12:43:11 -07:00
Will Greenberg
bcf1ce3f33 Update changelog for 1.28.0 release 2022-06-07 12:41:07 -07:00
alexzorin
295fc5e33a cli: fix help text for --no-autorenew (#9312) 2022-06-04 11:37:05 +10:00
Will Greenberg
d13131e303 Merge pull request #9309 from certbot/test-account-updates
certbot-ci: improve tests for update_account/show_account
2022-05-31 12:58:19 -07:00
Alex Zorin
7758a03b5b skip boulder for show_account assertions 2022-05-31 17:31:52 +10:00
Alex Zorin
cf63470db9 certbot-ci: improve tests for update_account/show_account 2022-05-31 17:02:43 +10:00
amplifi
5c111d0bd1 Cite Mozilla ssl-config in Apache/NGINX TLS configs (#8670) (#9295)
* Cite Mozilla ssl-config in Apache/nginx TLS configs (certbot#8670)

* Update CHANGELOG

* Add TLS config hashes to ALL_SSL_OPTIONS_HASHES

* Update wording in CHANGELOG
2022-05-13 10:59:49 -07:00
alexzorin
ec49b94acb acme: use order "status" to determine action during finalization (#9297)
Rather than deducing the status of an order by the "certificate"
and "error" fields, use the "status" field directly.
2022-05-13 09:51:11 -07:00
Brad Warren
7dd1e814fb Ignore parallel coverage files (#9293)
* ignore parallel coverage files

* Properly shutdown & close HTTP server
2022-05-07 13:31:59 +10:00
Brad Warren
2017669544 Merge pull request #9292 from certbot/candidate-1.27.0 2022-05-04 07:36:23 -07:00
Will Greenberg
8d7ced5e12 Bump version to 1.28.0 2022-05-03 11:35:09 -07:00
Will Greenberg
e593921560 Add contents to certbot/CHANGELOG.md for next version 2022-05-03 11:35:09 -07:00
Will Greenberg
373ff0e6e9 Release 1.27.0 2022-05-03 11:35:08 -07:00
Will Greenberg
103b8bc8f9 Update changelog for 1.27.0 release 2022-05-03 11:33:11 -07:00
Will Greenberg
828be0071e Add new signing key (#9288)
* Add new signing key

* Update certbot/CHANGELOG.md
2022-04-28 11:04:43 -07:00
Will Greenberg
71a3d8fffb Merge pull request #9289 from certbot/9184-fix-changelog
changelog: move entry for #9184
2022-04-27 12:19:53 -07:00
Alex Zorin
48155b1ec7 changelog: move entry for #9184 2022-04-27 13:19:42 +10:00
Will Greenberg
8066f230f5 If an installer is provided to certonly, restart after cert issuance (#9184)
* If an installer is provided to certonly, restart after cert issuance

* Add myself to AUTHORS.md

* Handle certonly's "installer" error case

* Handle interactive case, use lazy interpolation

* fix trailing whitespace

* fix whitespace in error message, re-raise exception

* Handle cases where user specified an authenticator but no installer

* make tox happy

* Clarify comment in selection.py

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

* Add tests for the certonly installer changes

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2022-04-26 18:51:57 -07:00
Will Greenberg
3b6f3450c2 Add --debug to docker push (#9286)
This'll (hopefully) help us debug the connectivity issues during
the deploy CI
2022-04-22 08:07:59 -07:00
Richard "mtfnpy" Harman
20336266fd Add documentation on interactions between multiple views in BIND and the dns_rfc2136 plugin (#9284)
* Add documentation on interactions between multiple views in BIND and the dns_rfc2136 plugin

* Missing ; in example config

* Make lines shorter

* Missed one long line, and move Examples up in the documentation

* Apply suggestions from code review

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

Co-authored-by: alexzorin <alex@zor.io>
2022-04-22 10:31:46 +10:00
Will Greenberg
549bc0a5fd Use win32 as platform in tox.ini (#9277)
This is used to match against sys.platform, which for windows is
win32 regardless of bitness
2022-04-19 07:40:46 +10:00
osirisinferi
0ca8ec6f7f Add missing closing parenthesis (#9279) 2022-04-13 11:47:19 +10:00
Brad Warren
df982b33b9 cleanup renewer defaults (#9274) 2022-04-09 19:20:03 +10:00
alexzorin
7a2c26fd22 docs: in contributing, ipdb→ipdb3 (#9271)
The binary is renamed in Python 3.
2022-04-07 23:27:16 +02:00
James Balazs
0fb5094250 Add subproblems to errors (#7046) (#9258)
* Add subproblems to errors (#7046)

* Fix can't assign attribute

* Tidy up string representations of errors and add decoders for subproblems / identifiers

* Add missing attributes to docstring

* Move change to 1.27.0 in changelog
2022-04-06 09:34:26 -07:00
Brad Warren
87216372dd Fix race condition and uncaught exception (#9264)
* Fix race condition and uncaught exception

* fix typo
2022-04-06 09:12:38 +10:00
alexzorin
b7df4416b5 Merge pull request #9267 from certbot/candidate-1.26.0
Update files from 1.26.0 release
2022-04-06 08:59:07 +10:00
Brad Warren
b9a7d771bc Bump version to 1.27.0 2022-04-05 10:43:01 -07:00
Brad Warren
3f8fde4270 Add contents to certbot/CHANGELOG.md for next version 2022-04-05 10:43:01 -07:00
Brad Warren
5b8cc18456 Release 1.26.0 2022-04-05 10:43:00 -07:00
Brad Warren
e8a1e6deb1 Update changelog for 1.26.0 release 2022-04-05 10:41:26 -07:00
alexzorin
b5a187841e certbot-ci: upgrade pebble to v2.3.1 (#9260) 2022-04-02 08:17:08 +11:00
alexzorin
d45a702649 changelog: clarify --new-key entry (#9259)
@osirisinferi pointed out [in chat](https://opensource.eff.org/eff-open-source/pl/y5whp5ny378wuedi8gd7995qbo) that the way this entry was written, suggested that `--new-key` might affect whether `--reuse-key` is set or not.

I think the second sentence was the main culprit, so I've nixed it and replaced it with a reminder about our other flags.

This maybe calls out more for a documentation section but let's fix this quickly before the release.
2022-04-01 13:27:11 -07:00
alexzorin
fe0b637e4d display acme.Errors less verbosely (#9255)
* display acme.Errors less verbosely

* remove superfluous import
2022-03-31 13:48:47 -07:00
alexzorin
284023a1b7 Add --new-key (#9252)
* add --new-key

* add tests
2022-03-31 11:40:21 -07:00
osirisinferi
4456a6ba0b Add error message to account registration error (#9233)
* Add  message to account reg. error

* Changelog

* Remove forced lowercase first char

* Catch errors raised by acme library

* Fix mypy and add some comments

* Add some tests

* Move changelog entry to current version

* Address comments

* Address additional comments

Put everything in this commit instead of using the "Commit suggestion"
feat on Github, which would resolve in 4 different tiny commits.
2022-03-31 07:36:15 +11:00
Mads Jensen
142fcad28b Update various references to draft RFC to published versions. (#9250) 2022-03-28 17:26:06 -07:00
osirisinferi
1d45939cab Skip ToS agreement question if ToS value is None (#9245)
* Skip ToS agreement question if ToS value is None

* Add changelog entry

* Typo in CHANGELOG

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

* Typo in CHANGELOG

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

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2022-03-24 15:42:47 -07:00
Will Greenberg
9ef6110e36 Point pip to filesystem packages rather than local HTTP server (#9240) 2022-03-24 13:32:03 -07:00
alexzorin
05a9ded297 pinning: update awscli pin (#9242) 2022-03-23 15:13:05 -07:00
alexzorin
690f62bae2 dns-ovh: increase default propagation timeout to 120s (#9244) 2022-03-23 15:07:29 -07:00
alexzorin
5404701111 windows: upgrade Python to 3.9.11 (#9241) 2022-03-18 10:03:49 +11:00
677 changed files with 15401 additions and 17651 deletions

View File

@@ -1,8 +1,8 @@
# Configuring Azure Pipelines with Certbot
Let's begin. All pipelines are defined in `.azure-pipelines`. Currently there are two:
* `.azure-pipelines/main.yml` is the main one, executed on PRs for master, and pushes to master,
* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for master.
* `.azure-pipelines/main.yml` is the main one, executed on PRs for main, and pushes to main,
* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for main.
Several templates are defined in `.azure-pipelines/templates`. These YAML files aggregate common jobs configuration that can be reused in several pipelines.
@@ -64,7 +64,7 @@ Azure Pipeline needs RW on code, RO on metadata, RW on checks, commit statuses,
RW access here is required to allow update of the pipelines YAML files from Azure DevOps interface, and to
update the status of builds and PRs on GitHub side when Azure Pipelines are triggered.
Note however that no admin access is defined here: this means that Azure Pipelines cannot do anything with
protected branches, like master, and cannot modify the security context around this on GitHub.
protected branches, like main, and cannot modify the security context around this on GitHub.
Access can be defined for all or only selected repositories, which is nice.
```
@@ -91,11 +91,11 @@ grant permissions from Azure Pipelines to GitHub in order to setup a GitHub OAut
then are way too large (admin level on almost everything), while the classic approach does not add any more
permissions, and works perfectly well.__
- Select GitHub in "Select your repository section", choose certbot/certbot in Repository, master in default branch.
- Select GitHub in "Select your repository section", choose certbot/certbot in Repository, main in default branch.
- Click on YAML option for "Select a template"
- Choose a name for the pipeline (eg. test-pipeline), and browse to the actual pipeline YAML definition in the
"YAML file path" input (eg. `.azure-pipelines/test-pipeline.yml`)
- Click "Save & queue", choose the master branch to build the first pipeline, and click "Save and run" button.
- Click "Save & queue", choose the main branch to build the first pipeline, and click "Save and run" button.
_Done. Pipeline is operational. Repeat to add more pipelines from existing YAML files in `.azure-pipelines`._

View File

@@ -9,6 +9,7 @@ variables:
# We don't publish our Docker images in this pipeline, but when building them
# for testing, let's use the nightly tag.
dockerTag: nightly
snapBuildTimeout: 5400
stages:
- template: templates/stages/test-and-package-stage.yml

View File

@@ -1,8 +1,18 @@
trigger: none
# We run the test suite on commits to main so codecov gets coverage data
# about the main branch and can use it to track coverage changes.
trigger:
- main
pr:
- master
- main
- '*.x'
variables:
# We set this here to avoid coverage data being uploaded from things like our
# nightly pipeline. This is done because codecov (helpfully) keeps track of
# the number of coverage uploads for a commit and displays a warning when
# comparing two commits with an unequal number of uploads. Only uploading
# coverage here should keep the number of uploads it sees consistent.
uploadCoverage: true
jobs:
- template: templates/jobs/standard-tests-jobs.yml

View File

@@ -1,4 +1,4 @@
# Nightly pipeline running each day for master.
# Nightly pipeline running each day for main.
trigger: none
pr: none
schedules:
@@ -6,13 +6,14 @@ schedules:
displayName: Nightly build
branches:
include:
- master
- main
always: true
variables:
dockerTag: nightly
snapBuildTimeout: 19800
stages:
- template: templates/stages/test-and-package-stage.yml
- template: templates/stages/deploy-stage.yml
- template: templates/stages/nightly-deploy-stage.yml
- template: templates/stages/notify-failure-stage.yml

View File

@@ -8,11 +8,10 @@ pr: none
variables:
dockerTag: ${{variables['Build.SourceBranchName']}}
snapBuildTimeout: 19800
stages:
- template: templates/stages/test-and-package-stage.yml
- template: templates/stages/changelog-stage.yml
- template: templates/stages/deploy-stage.yml
parameters:
snapReleaseChannel: beta
- template: templates/stages/release-deploy-stage.yml
- template: templates/stages/notify-failure-stage.yml

View File

@@ -0,0 +1,128 @@
# As (somewhat) described at
# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops#context,
# each template only has access to the parameters passed into it. To help make
# use of this design, we define snapReleaseChannel without a default value
# which requires the user of this template to define it as described at
# https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema/parameters-name?view=azure-pipelines#remarks.
# This makes the user of this template be explicit while allowing them to
# define their own parameters with defaults that make sense for that context.
parameters:
- name: snapReleaseChannel
type: string
values:
- edge
- beta
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=beta,edge snapcraft.cfg
# (provide the shared snapcraft credentials when prompted)
#
# Then the file was added as a secure file in Azure pipelines
# with the name snapcraft.cfg by following the instructions at
# https://docs.microsoft.com/en-us/azure/devops/pipelines/library/secure-files?view=azure-devops
# including authorizing the file for use in the "nightly" and "release"
# pipelines as described at
# 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 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
# account used to generate the credentials. See
# https://forum.snapcraft.io/t/revoking-exported-credentials/19031 for
# more info.
- job: publish_snap
pool:
vmImage: ubuntu-22.04
variables:
- group: certbot-common
strategy:
matrix:
amd64:
SNAP_ARCH: amd64
arm32v6:
SNAP_ARCH: armhf
arm64v8:
SNAP_ARCH: arm64
steps:
- bash: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends snapd
sudo snap install --classic snapcraft
displayName: Install dependencies
- task: DownloadPipelineArtifact@2
inputs:
artifact: snaps_$(SNAP_ARCH)
path: $(Build.SourcesDirectory)/snap
displayName: Retrieve Certbot snaps
- task: DownloadSecureFile@1
name: snapcraftCfg
inputs:
secureFile: snapcraft.cfg
- bash: |
set -e
export SNAPCRAFT_STORE_CREDENTIALS=$(cat "$(snapcraftCfg.secureFilePath)")
for SNAP_FILE in snap/*.snap; do
tools/retry.sh eval snapcraft upload --release=${{ parameters.snapReleaseChannel }} "${SNAP_FILE}"
done
displayName: Publish to Snap store
# 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:
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: docker_$(DOCKER_ARCH)
path: $(Build.SourcesDirectory)
displayName: Retrieve Docker images
- bash: set -e && docker load --input $(Build.SourcesDirectory)/images.tar
displayName: Load Docker images
- task: Docker@2
inputs:
command: login
containerRegistry: docker-hub
displayName: Login to Docker Hub
- 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

@@ -2,58 +2,51 @@ jobs:
- job: extended_test
variables:
- name: IMAGE_NAME
value: ubuntu-18.04
value: ubuntu-22.04
- name: PYTHON_VERSION
value: 3.10
value: 3.12
- group: certbot-common
strategy:
matrix:
linux-py38:
PYTHON_VERSION: 3.8
TOXENV: py38
linux-py39:
PYTHON_VERSION: 3.9
TOXENV: py39
linux-py37-nopin:
PYTHON_VERSION: 3.7
TOXENV: py37
CERTBOT_NO_PIN: 1
linux-external-mock:
TOXENV: external-mock
linux-boulder-v2-integration-certbot-oldest:
PYTHON_VERSION: 3.7
TOXENV: integration-certbot-oldest
ACME_SERVER: boulder-v2
linux-boulder-v2-integration-nginx-oldest:
PYTHON_VERSION: 3.7
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:
linux-py310:
PYTHON_VERSION: 3.10
TOXENV: py310
linux-py311:
PYTHON_VERSION: 3.11
TOXENV: py311
linux-isolated:
TOXENV: 'isolated-acme,isolated-certbot,isolated-apache,isolated-cloudflare,isolated-digitalocean,isolated-dnsimple,isolated-dnsmadeeasy,isolated-gehirn,isolated-google,isolated-linode,isolated-luadns,isolated-nsone,isolated-ovh,isolated-rfc2136,isolated-route53,isolated-sakuracloud,isolated-nginx'
linux-integration-certbot-oldest:
PYTHON_VERSION: 3.8
TOXENV: integration
ACME_SERVER: boulder-v2
linux-boulder-v2-py39-integration:
TOXENV: integration-certbot-oldest
linux-integration-nginx-oldest:
PYTHON_VERSION: 3.8
TOXENV: integration-nginx-oldest
# python 3.8 integration tests are not run here because they're run as
# part of the standard test suite
linux-py39-integration:
PYTHON_VERSION: 3.9
TOXENV: integration
ACME_SERVER: boulder-v2
linux-boulder-v2-py310-integration:
linux-py310-integration:
PYTHON_VERSION: 3.10
TOXENV: integration
ACME_SERVER: boulder-v2
linux-py311-integration:
PYTHON_VERSION: 3.11
TOXENV: integration
linux-py312-integration:
PYTHON_VERSION: 3.12
TOXENV: integration
nginx-compat:
TOXENV: nginx_compat
linux-integration-rfc2136:
IMAGE_NAME: ubuntu-18.04
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: integration-dns-rfc2136
docker-dev:
TOXENV: docker_dev
le-modification:
IMAGE_NAME: ubuntu-18.04
IMAGE_NAME: ubuntu-22.04
TOXENV: modification
farmtest-apache2:
PYTHON_VERSION: 3.8

View File

@@ -1,17 +1,15 @@
jobs:
- job: docker_build
pool:
vmImage: ubuntu-18.04
vmImage: ubuntu-22.04
strategy:
matrix:
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
amd64:
DOCKER_ARCH: amd64
# Do not run the heavy non-amd64 builds for test branches
${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}:
arm32v6:
DOCKER_ARCH: arm32v6
arm64v8:
DOCKER_ARCH: arm64v8
# The default timeout of 60 minutes is a little low for compiling
# cryptography on ARM architectures.
timeoutInMinutes: 180
@@ -34,99 +32,40 @@ jobs:
path: $(Build.ArtifactStagingDirectory)
artifact: docker_$(DOCKER_ARCH)
displayName: Store Docker artifact
- job: docker_run
- job: docker_test
dependsOn: docker_build
pool:
vmImage: ubuntu-18.04
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:
vmImage: windows-2019
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.9
architecture: x64
addToPath: true
- script: |
python -m venv venv
venv\Scripts\python tools\pipstrap.py
venv\Scripts\python tools\pip_install.py -e windows-installer
displayName: Prepare Windows installer build environment
- script: |
venv\Scripts\construct-windows-installer
displayName: Build Certbot installer
- task: CopyFiles@2
inputs:
sourceFolder: $(System.DefaultWorkingDirectory)/windows-installer/build/nsis
contents: '*.exe'
targetFolder: $(Build.ArtifactStagingDirectory)
- task: PublishPipelineArtifact@1
inputs:
path: $(Build.ArtifactStagingDirectory)
# If we change the artifact's name, it should also be changed in tools/create_github_release.py
artifact: windows-installer
displayName: Publish Windows installer
- job: installer_run
dependsOn: installer_build
strategy:
matrix:
win2019:
imageName: windows-2019
pool:
vmImage: $(imageName)
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.9
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:
artifact: windows-installer
path: $(Build.SourcesDirectory)/bin
displayName: Retrieve Windows installer
- script: |
python -m venv venv
venv\Scripts\python tools\pipstrap.py
venv\Scripts\python tools\pip_install.py -e certbot-ci
env:
PIP_NO_BUILD_ISOLATION: no
displayName: Prepare Certbot-CI
- script: |
set PATH=%ProgramFiles%\Certbot\bin;%PATH%
venv\Scripts\python -m pytest certbot-ci\windows_installer_integration_tests --allow-persistent-changes --installer-path $(Build.SourcesDirectory)\bin\certbot-beta-installer-win_amd64.exe
displayName: Run windows installer integration tests
- script: |
set PATH=%ProgramFiles%\Certbot\bin;%PATH%
venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4
displayName: Run certbot integration tests
- job: snaps_build
pool:
vmImage: ubuntu-18.04
vmImage: ubuntu-22.04
strategy:
matrix:
amd64:
SNAP_ARCH: amd64
# Do not run the heavy non-amd64 builds for test branches
${{ if not(startsWith(variables['Build.SourceBranchName'], 'test-')) }}:
armhf:
SNAP_ARCH: armhf
arm64:
SNAP_ARCH: arm64
armhf:
SNAP_ARCH: armhf
arm64:
SNAP_ARCH: arm64
timeoutInMinutes: 0
steps:
- script: |
@@ -137,7 +76,7 @@ jobs:
displayName: Install dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
versionSpec: 3.12
addToPath: true
- task: DownloadSecureFile@1
name: credentials
@@ -149,7 +88,7 @@ jobs:
git config --global user.name "$(Build.RequestedFor)"
mkdir -p ~/.local/share/snapcraft/provider/launchpad
cp $(credentials.secureFilePath) ~/.local/share/snapcraft/provider/launchpad/credentials
python3 tools/snap/build_remote.py ALL --archs ${SNAP_ARCH} --timeout 19800
python3 tools/snap/build_remote.py ALL --archs ${SNAP_ARCH} --timeout $(snapBuildTimeout)
displayName: Build snaps
- script: |
set -e
@@ -164,18 +103,17 @@ jobs:
- job: snap_run
dependsOn: snaps_build
pool:
vmImage: ubuntu-18.04
vmImage: ubuntu-22.04
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
versionSpec: 3.12
addToPath: true
- script: |
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends nginx-light snapd
python3 -m venv venv
venv/bin/python tools/pipstrap.py
venv/bin/python tools/pip_install.py -U tox
displayName: Install dependencies
- task: DownloadPipelineArtifact@2
@@ -189,12 +127,12 @@ jobs:
displayName: Install Certbot snap
- script: |
set -e
venv/bin/python -m tox -e integration-external,apacheconftest-external-with-pebble
venv/bin/python -m tox run -e integration-external,apacheconftest-external-with-pebble
displayName: Run tox
- job: snap_dns_run
dependsOn: snaps_build
pool:
vmImage: ubuntu-18.04
vmImage: ubuntu-22.04
steps:
- script: |
set -e
@@ -203,7 +141,7 @@ jobs:
displayName: Install dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
versionSpec: 3.12
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:
@@ -213,7 +151,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

@@ -1,66 +1,50 @@
jobs:
- job: test
variables:
PYTHON_VERSION: 3.10
PYTHON_VERSION: 3.12
strategy:
matrix:
macos-py37-cover:
IMAGE_NAME: macOS-10.15
PYTHON_VERSION: 3.7
TOXENV: py37-cover
macos-py310-cover:
IMAGE_NAME: macOS-10.15
PYTHON_VERSION: 3.10
TOXENV: py310-cover
windows-py37:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.7
TOXENV: py37-win
windows-py39-cover:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.9
TOXENV: py39-cover-win
windows-integration-certbot:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.9
TOXENV: integration-certbot
linux-oldest-tests-1:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.7
TOXENV: '{acme,apache,apache-v2,certbot}-oldest'
linux-oldest-tests-2:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.7
TOXENV: '{dns,nginx}-oldest'
linux-py37:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.7
TOXENV: py37
linux-py310-cover:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.10
TOXENV: py310-cover
linux-py310-lint:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.10
macos-py38-cover:
IMAGE_NAME: macOS-12
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-13
TOXENV: cover
# See explanation under macos-py38-cover.
PIP_USE_PEP517: "true"
linux-oldest:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: oldest
linux-py38:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: py38
linux-cover:
IMAGE_NAME: ubuntu-22.04
TOXENV: cover
linux-lint:
IMAGE_NAME: ubuntu-22.04
TOXENV: lint-posix
linux-py310-mypy:
IMAGE_NAME: ubuntu-18.04
PYTHON_VERSION: 3.10
TOXENV: mypy-posix
linux-mypy:
IMAGE_NAME: ubuntu-22.04
TOXENV: mypy
linux-integration:
IMAGE_NAME: ubuntu-18.04
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: integration
ACME_SERVER: pebble
apache-compat:
IMAGE_NAME: ubuntu-18.04
IMAGE_NAME: ubuntu-22.04
TOXENV: apache_compat
apacheconftest:
IMAGE_NAME: ubuntu-18.04
IMAGE_NAME: ubuntu-22.04
TOXENV: apacheconftest-with-pebble
nginxroundtrip:
IMAGE_NAME: ubuntu-18.04
IMAGE_NAME: ubuntu-22.04
TOXENV: nginxroundtrip
pool:
vmImage: $(IMAGE_NAME)

View File

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

View File

@@ -0,0 +1,6 @@
stages:
- stage: Deploy
jobs:
- template: ../jobs/common-deploy-jobs.yml
parameters:
snapReleaseChannel: edge

View File

@@ -0,0 +1,38 @@
stages:
- stage: Deploy
jobs:
- template: ../jobs/common-deploy-jobs.yml
parameters:
snapReleaseChannel: beta
- job: create_github_release
pool:
vmImage: ubuntu-22.04
steps:
- task: DownloadPipelineArtifact@2
inputs:
artifact: changelog
path: '$(Pipeline.Workspace)'
- task: GitHubRelease@1
inputs:
# this "github-releases" credential is what azure pipelines calls a
# "service connection". it was created using the instructions at
# https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#github-service-connection
# with a fine-grained personal access token from github to limit
# the permissions given to azure pipelines. the connection on azure
# needs permissions for the "release" pipeline (and maybe the
# "full-test-suite" pipeline to simplify testing it). information
# on how to set up these permissions can be found at
# https://learn.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&tabs=yaml#secure-a-service-connection.
# the github token that is used needs "contents:write" and
# "workflows:write" permissions for the certbot repo
#
# as of writing this, the current token will expire on 3/15/2025.
# when recreating it, you may also want to create it using the
# shared "certbotbot" github account so the credentials aren't tied
# to any one dev's github account and their access to the certbot
# repo
gitHubConnection: github-releases
title: ${{ format('Certbot {0}', replace(variables['Build.SourceBranchName'], 'v', '')) }}
releaseNotesFilePath: '$(Pipeline.Workspace)/release_notes.md'
assets: '$(Build.SourcesDirectory)/packages/{*.tar.gz,SHA256SUMS*}'
addChangeLog: false

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,32 +20,19 @@ steps:
set -e
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
python-dev \
gcc \
libaugeas0 \
libssl-dev \
libffi-dev \
ca-certificates \
nginx-light \
openssl
nginx-light
sudo systemctl stop nginx
sudo sysctl net.ipv4.ip_unprivileged_port_start=0
condition: startswith(variables['IMAGE_NAME'], 'ubuntu')
displayName: Install Linux dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: $(PYTHON_VERSION)
addToPath: true
# tools/pip_install.py is used to pin packages to a known working version
# except in tests where the environment variable CERTBOT_NO_PIN is set.
# virtualenv is listed here explicitly to make sure it is upgraded when
# CERTBOT_NO_PIN is set to work around failures we've seen when using an older
# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also
# set, pip updates dependencies it thinks are already satisfied to avoid some
# problems with its lack of real dependency resolution.
- bash: |
set -e
python tools/pipstrap.py
python tools/pip_install.py -I tox virtualenv
python3 tools/pip_install.py tox
displayName: Install runtime dependencies
- task: DownloadSecureFile@1
name: testFarmPem
@@ -49,9 +44,34 @@ steps:
export TARGET_BRANCH="`echo "${BUILD_SOURCEBRANCH}" | sed -E 's!refs/(heads|tags)/!!g'`"
[ -z "${SYSTEM_PULLREQUEST_TARGETBRANCH}" ] || export TARGET_BRANCH="${SYSTEM_PULLREQUEST_TARGETBRANCH}"
env
python -m tox
python3 -m tox run
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)
AWS_EC2_PEM_FILE: $(testFarmPem.secureFilePath)
displayName: Run tox
# For now, let's omit `set -e` and avoid the script exiting with a nonzero
# status code to prevent problems here from causing build failures. If
# this turns out to work well, we can change this.
- bash: |
python3 tools/pip_install.py -I coverage
case "$AGENT_OS" in
Darwin)
CODECOV_URL="https://uploader.codecov.io/latest/macos/codecov"
;;
Linux)
CODECOV_URL="https://uploader.codecov.io/latest/linux/codecov"
;;
Windows_NT)
CODECOV_URL="https://uploader.codecov.io/latest/windows/codecov.exe"
;;
*)
echo "Unexpected OS"
exit 0
esac
curl --retry 3 -o codecov "$CODECOV_URL"
chmod +x codecov
coverage xml
./codecov || echo "Uploading coverage data failed"
condition: and(eq(variables['uploadCoverage'], true), or(startsWith(variables['TOXENV'], 'cover'), startsWith(variables['TOXENV'], 'integration')))
displayName: Upload coverage data

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

7
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,7 @@
# This disables all reporting from codecov. Let's just set it up to collect
# data for now and then we can play with the settings here.
comment: false
coverage:
status:
project: off
patch: off

View File

@@ -1,5 +1,6 @@
## Pull Request Checklist
- [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `master` section of `certbot/CHANGELOG.md` to include a description of the change being made.
- [ ] The Certbot team has recently expressed interest in reviewing a PR for this. If not, this PR may be closed due our limited resources and need to prioritize how we spend them.
- [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `main` section of `certbot/CHANGELOG.md` to include a description of the change being made.
- [ ] Add or update any documentation as needed to support the changes in this PR.
- [ ] Include your name in `AUTHORS.md` if you like.

35
.github/stale.yml vendored
View File

@@ -1,35 +0,0 @@
# Configuration for https://github.com/marketplace/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 365
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
# When changing this value, be sure to also update markComment below.
daysUntilClose: 30
# Ignore issues with an assignee (defaults to false)
exemptAssignees: true
# Label to use when marking as stale
staleLabel: needs-update
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
We've made a lot of changes to Certbot since this issue was opened. If you
still have this issue with an up-to-date version of Certbot, can you please
add a comment letting us know? This helps us to better see what issues are
still affecting our users. If there is no activity in the next 30 days, this
issue will be automatically closed.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue has been closed due to lack of activity, but if you think it
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
limitPerRun: 1
# Don't mark pull requests as stale.
only: issues

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

@@ -0,0 +1,21 @@
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:
- uses: mattermost/action-mattermost-notify@main
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_MERGE_WEBHOOK }}
TEXT: >
[${{ github.repository }}] |
[${{ github.event.pull_request.title }}
#${{ github.event.number }}](https://github.com/${{ github.repository }}/pull/${{ github.event.number }})
was merged into main by ${{ github.actor }}

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

@@ -0,0 +1,27 @@
name: Weekly Github Update
on:
schedule:
# Every week on Thursday @ 13:00
- cron: "0 13 * * 4"
workflow_dispatch:
jobs:
send-mattermost-message:
runs-on: ubuntu-latest
steps:
- name: Create Mattermost Message
run: |
DATE=$(date --date="7 days ago" +"%Y-%m-%d")
echo "MERGED_URL=https://github.com/pulls?q=merged%3A%3E${DATE}+org%3Acertbot" >> $GITHUB_ENV
echo "UPDATED_URL=https://github.com/pulls?q=updated%3A%3E${DATE}+org%3Acertbot" >> $GITHUB_ENV
- uses: mattermost/action-mattermost-notify@main
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
MATTERMOST_CHANNEL: private-certbot
TEXT: |
## Updates Across Certbot Repos
- Certbot team members SHOULD look at: [link](${{ env.MERGED_URL }})
- Certbot team members MAY also want to look at: [link](${{ env.UPDATED_URL }})
- 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.
- 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).

48
.github/workflows/stale.yml vendored Normal file
View File

@@ -0,0 +1,48 @@
name: Update Stale Issues
on:
schedule:
# Run 1:24AM every night
- cron: '24 1 * * *'
workflow_dispatch:
permissions:
issues: write
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v6
with:
# 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
# Label to use when marking as stale
stale-issue-label: needs-update
stale-issue-message: >
We've made a lot of changes to Certbot since this issue was opened. If you
still have this issue with an up-to-date version of Certbot, can you please
add a comment letting us know? This helps us to better see what issues are
still affecting our users. If there is no activity in the next 30 days, this
issue will be automatically closed.
close-issue-message: >
This issue has been closed due to lack of activity, but if you think it
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 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

2
.gitignore vendored
View File

@@ -13,6 +13,7 @@ poetry.lock
# coverage
.coverage
.coverage.*
/htmlcov/
/.vagrant
@@ -58,3 +59,4 @@ certbot-dns*/certbot-dns*_arm*.txt
/certbot_amd64*.txt
/certbot_arm*.txt
certbot-dns*/snap
snapcraft.cfg

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

715
.pylintrc
View File

@@ -1,54 +1,373 @@
[MASTER]
[MAIN]
# use as many jobs as there are cores
jobs=0
# Analyse import fallback blocks. This can be used to support both Python 2 and
# 3 compatible code, which means that the block might have code that exists
# only in one or another interpreter, leading to false positives when analysed.
analyse-fallback-blocks=no
# Specify a configuration file.
#rcfile=
# Load and enable all available extensions. Use --list-extensions to see a list
# all available extensions.
#enable-all-extensions=
# In error mode, messages with a category besides ERROR or FATAL are
# suppressed, and no reports are done by default. Error mode is compatible with
# disabling specific errors.
#errors-only=
# Always return a 0 (non-error) status code, even if lint errors are found.
# This is primarily useful in continuous integration scripts.
#exit-zero=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-allow-list=
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code. (This is an alternative name to extension-pkg-allow-list
# for backward compatibility.)
extension-pkg-whitelist=pywintypes,win32api,win32file,win32security
# Return non-zero exit code if any of these messages/categories are detected,
# even if score is above --fail-under value. Syntax same as enable. Messages
# specified are enabled, while categories only check already-enabled messages.
fail-on=
# Specify a score threshold under which the program will exit with error.
fail-under=10
# Interpret the stdin as a python script, whose filename needs to be passed as
# the module_or_package argument.
#from-stdin=
# Files or directories to be skipped. They should be base names, not paths.
ignore=CVS
# Add files or directories matching the regular expressions patterns to the
# 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.
# 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
# Emacs file locks
ignore-patterns=^\.#
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis). It
# supports qualified module names, as well as Unix pattern matching.
ignored-modules=
# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
# CERTBOT COMMENT
# This is needed for pylint to import linter_plugin.py since
# https://github.com/PyCQA/pylint/pull/3396.
init-hook="import pylint.config, os, sys; sys.path.append(os.path.dirname(pylint.config.PYLINTRC))"
init-hook="import pylint.config, os, sys; sys.path.append(os.path.dirname(next(pylint.config.find_default_config_files())))"
# Profiled execution.
profile=no
# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the
# number of processors available to use, and will cap the count on Windows to
# avoid hangs.
jobs=0
# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=CVS
# Control the amount of potential inferred values when inferring a single
# object. This can help the performance when dealing with large functions or
# complex, nested conditions.
limit-inference-results=100
# List of plugins (as comma separated values of python module names) to load,
# usually to register additional checkers.
load-plugins=linter_plugin
# Pickle collected data for later comparisons.
persistent=yes
# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=linter_plugin
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.10
# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code.
extension-pkg-whitelist=pywintypes,win32api,win32file,win32security
# Discover python modules and packages in the file system subtree.
recursive=no
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no
# In verbose mode, extra non-checker-related info will be displayed.
#verbose=
[BASIC]
# Naming style matching correct argument names.
argument-naming-style=snake_case
# Regular expression matching correct argument names. Overrides argument-
# naming-style. If left empty, argument names will be checked with the set
# naming style.
#argument-rgx=
# Naming style matching correct attribute names.
attr-naming-style=snake_case
# Regular expression matching correct attribute names. Overrides attr-naming-
# style. If left empty, attribute names will be checked with the set naming
# style.
#attr-rgx=
# Bad variable names which should always be refused, separated by a comma.
bad-names=foo,
bar,
baz,
toto,
tutu,
tata
# Bad variable names regexes, separated by a comma. If names match any regex,
# they will always be refused
bad-names-rgxs=
# Naming style matching correct class attribute names.
class-attribute-naming-style=any
# Regular expression matching correct class attribute names. Overrides class-
# attribute-naming-style. If left empty, class attribute names will be checked
# with the set naming style.
#class-attribute-rgx=
# Naming style matching correct class constant names.
class-const-naming-style=UPPER_CASE
# Regular expression matching correct class constant names. Overrides class-
# const-naming-style. If left empty, class constant names will be checked with
# the set naming style.
#class-const-rgx=
# Naming style matching correct class names.
class-naming-style=PascalCase
# Regular expression matching correct class names. Overrides class-naming-
# style. If left empty, class names will be checked with the set naming style.
#class-rgx=
# Naming style matching correct constant names.
const-naming-style=UPPER_CASE
# Regular expression matching correct constant names. Overrides const-naming-
# style. If left empty, constant names will be checked with the set naming
# style.
#const-rgx=
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# Naming style matching correct function names.
function-naming-style=snake_case
# Regular expression matching correct function names. Overrides function-
# naming-style. If left empty, function names will be checked with the set
# naming style.
function-rgx=[a-z_][a-z0-9_]{2,40}$
# Good variable names which should always be accepted, separated by a comma.
good-names=i,
j,
k,
ex,
Run,
_,
fd,
logger
# Good variable names regexes, separated by a comma. If names match any regex,
# they will always be accepted
good-names-rgxs=
# Include a hint for the correct naming format with invalid-name.
include-naming-hint=no
# Naming style matching correct inline iteration names.
inlinevar-naming-style=any
# Regular expression matching correct inline iteration names. Overrides
# inlinevar-naming-style. If left empty, inline iteration names will be checked
# with the set naming style.
#inlinevar-rgx=
# Naming style matching correct method names.
method-naming-style=snake_case
# Regular expression matching correct method names. Overrides method-naming-
# style. If left empty, method names will be checked with the set naming style.
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Naming style matching correct module names.
module-naming-style=snake_case
# Regular expression matching correct module names. Overrides module-naming-
# style. If left empty, module names will be checked with the set naming style.
#module-rgx=
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__)|(test_[A-Za-z0-9_]*)|(_.*)|(.*Test$)
# List of decorators that produce properties, such as abc.abstractproperty. Add
# to this list to register other decorators that produce valid properties.
# These decorators are taken in consideration only for invalid-name.
property-classes=abc.abstractproperty
# Regular expression matching correct type variable names. If left empty, type
# variable names will be checked with the set naming style.
#typevar-rgx=
# Naming style matching correct variable names.
variable-naming-style=snake_case
# Regular expression matching correct variable names. Overrides variable-
# naming-style. If left empty, variable names will be checked with the set
# naming style.
variable-rgx=[a-z_][a-z0-9_]{1,30}$
[CLASSES]
# Warn about protected attribute access inside special methods
check-protected-access-in-special-methods=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,
__new__,
setUp,
__post_init__
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=cls
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=builtins.BaseException,
builtins.Exception
[FORMAT]
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Number of spaces of indent required inside a hanging or continued line.
# git history told me that "This does something silly/broken..."
#indent-after-paren=4
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Maximum number of characters on a single line.
max-line-length=100
# Maximum number of lines in a module.
max-module-lines=1250
# Allow the body of a class to be on the same line as the declaration if body
# contains single statement.
single-line-class-stmt=no
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
[IMPORTS]
# List of modules that can be imported at any level, not just the top level
# one.
allow-any-import-level=
# Allow wildcard imports from modules that define __all__.
allow-wildcard-with-all=no
# Deprecated modules which should not be used, separated by a comma.
deprecated-modules=
# Output a graph (.gv or any supported image format) of external dependencies
# to the given file (report RP0402 must not be disabled).
ext-import-graph=
# Output a graph (.gv or any supported image format) of all (i.e. internal and
# external) dependencies to the given file (report RP0402 must not be
# disabled).
import-graph=
# Output a graph (.gv or any supported image format) of internal dependencies
# to the given file (report RP0402 must not be disabled).
int-import-graph=
# Force import order to recognize a module as part of the standard
# compatibility libraries.
known-standard-library=
# Force import order to recognize a module as part of a third party library.
known-third-party=enchant
# Couples of modules and preferred modules, separated by a comma.
preferred-modules=
[LOGGING]
# The type of string formatting that logging methods do. `old` means using %
# formatting, `new` is for `{}` formatting.
logging-format-style=old
# Logging modules to check that the string format arguments are in logging
# function parameter format.
logging-modules=logging,logger
[MESSAGES CONTROL]
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=
# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE,
# UNDEFINED.
confidence=HIGH,
CONTROL_FLOW,
INFERENCE,
INFERENCE_FAILURE,
UNDEFINED
# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# file where it should appear only once). You can also use "--disable=all" to
# disable everything first and then re-enable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
# no Warning level messages displayed, use "--disable=all --enable=classes
# --disable=W".
# CERTBOT COMMENT
# 1) Once certbot codebase is claimed to be compatible exclusively with Python 3,
# the useless-object-inheritance check can be enabled again, and code fixed accordingly.
@@ -74,261 +393,185 @@ extension-pkg-whitelist=pywintypes,win32api,win32file,win32security
# not need to enforce encoding on files so we disable this check.
# 7) consider-using-f-string is "suggesting" to move to f-string when possible with an error. This
# clearly relates to code design and not to potential defects in the code, let's just ignore that.
disable=fixme,locally-disabled,locally-enabled,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design,import-outside-toplevel,useless-object-inheritance,unsubscriptable-object,no-value-for-parameter,no-else-return,no-else-raise,no-else-break,no-else-continue,raise-missing-from,wrong-import-order,unspecified-encoding,consider-using-f-string
disable=fixme,locally-disabled,invalid-name,cyclic-import,duplicate-code,design,import-outside-toplevel,useless-object-inheritance,unsubscriptable-object,no-value-for-parameter,no-else-return,no-else-raise,no-else-break,no-else-continue,raise-missing-from,wrong-import-order,unspecified-encoding,consider-using-f-string,raw-checker-failed,bad-inline-option,file-ignored,suppressed-message,useless-suppression,deprecated-pragma,use-symbolic-message-instead
[REPORTS]
# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text
# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no
# Tells whether to display a full report or only the messages
reports=yes
# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details
#msg-template=
# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time (only on the command line, not in the configuration file where
# it should appear only once). See also the "--disable" option for examples.
enable=c-extension-no-member
[BASIC]
[METHOD_ARGS]
# Required attributes for module, separated by a comma
required-attributes=
# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter,apply,input,file
# Good variable names which should always be accepted, separated by a comma
good-names=f,i,j,k,ex,Run,_,fd,logger
# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata
# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=
# Include a hint for the correct naming format with invalid-name
include-naming-hint=no
# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,40}$
# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,40}$
# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{1,30}$
# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$
# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$
# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$
# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$
# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$
# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$
# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$
# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,50}$
# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,50}$
# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=(__.*__)|(test_[A-Za-z0-9_]*)|(_.*)|(.*Test$)
# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1
# List of qualified names (i.e., library.method) which require a timeout
# parameter e.g. 'requests.api.get,requests.api.post'
timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request
[MISCELLANEOUS]
# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
notes=FIXME,
XXX,
TODO
# Regular expression of note tags to take in consideration.
notes-rgx=
[LOGGING]
[REFACTORING]
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging,logger
# Maximum number of nested blocks for function / method body
max-nested-blocks=5
# Complete name of functions that never returns. When checking for
# inconsistent-return-statements if a never returning function is called then
# it will be considered as an explicit return statement and no message will be
# printed.
never-returning-functions=sys.exit,argparse.parse_error
[VARIABLES]
[REPORTS]
# Tells whether we should check for unused import in __init__ files.
init-import=no
# Python expression which should return a score less than or equal to 10. You
# have access to the variables 'fatal', 'error', 'warning', 'refactor',
# 'convention', and 'info' which contain the number of messages in each
# category, as well as 'statement' which is the total number of statements
# analyzed. This score is used by the global evaluation report (RP0004).
evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10))
# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=(unused)?_.*|dummy
# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
msg-template=
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=
# Set the output format. Available formats are text, parseable, colorized, json
# and msvs (visual studio). You can also give a reporter class, e.g.
# mypackage.mymodule.MyReporterClass.
#output-format=
# Tells whether to display a full report or only the messages.
reports=no
# Activate the evaluation score.
score=yes
[SIMILARITIES]
# Comments are removed from the similarity computation
ignore-comments=yes
# Docstrings are removed from the similarity computation
ignore-docstrings=yes
# Imports are removed from the similarity computation
ignore-imports=yes
# Signatures are removed from the similarity computation
ignore-signatures=yes
# Minimum lines number of a similarity.
min-similarity-lines=6
# Ignore comments when computing similarities.
ignore-comments=yes
# Ignore docstrings when computing similarities.
ignore-docstrings=yes
[STRING]
# Ignore imports when computing similarities.
ignore-imports=yes
# This flag controls whether inconsistent-quotes generates a warning when the
# character used as a quote delimiter is used inconsistently within a module.
check-quote-consistency=no
[FORMAT]
# Maximum number of characters on a single line.
max-line-length=100
# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$
# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no
# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma
# Maximum number of lines in a module
max-module-lines=1250
# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string=' '
# Number of spaces of indent required inside a hanging or continued line.
# This does something silly/broken...
#indent-after-paren=4
# This flag controls whether the implicit-str-concat should generate a warning
# on implicit string concatenation in sequences defined over several lines.
check-str-concat-over-line-jumps=no
[TYPECHECK]
# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes
# List of decorators that produce context managers, such as
# contextlib.contextmanager. Add to this list to register other decorators that
# produce valid context managers.
contextmanager-decorators=contextlib.contextmanager
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E1101 when accessed. Python regular
# expressions are accepted.
generated-members=
# Tells whether to warn about missing members when the owner of the attribute
# is inferred to be None.
ignore-none=yes
# This flag controls whether pylint should warn about no-member and similar
# checks whenever an opaque object is returned when inferring. The inference
# can return multiple potential results while evaluating a Python object, but
# some branches might not be evaluated, which results in partial inference. In
# that case, it might be useful to still emit no-member and other checks for
# the rest of the inferred objects.
ignore-on-opaque-inference=yes
# List of symbolic message names to ignore for Mixin members.
ignored-checks-for-mixins=no-member,
not-async-context-manager,
not-context-manager,
attribute-defined-outside-init
# List of class names for which member attributes should not be checked (useful
# for classes with dynamically set attributes). This supports the use of
# qualified names.
ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace,Field,Header,JWS,closing
# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=pkg_resources,confargparse,argparse
# import errors ignored only in 1.4.4
# https://bitbucket.org/logilab/pylint/commits/cd000904c9e2
ignored-modules=confargparse,argparse
# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=Field,Header,JWS,closing
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.
missing-member-hint=yes
# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=yes
# The minimum edit distance a name should have in order to be considered a
# similar match for a missing member name.
missing-member-hint-distance=1
# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent
# The total number of similar names that should be taken in consideration when
# showing a hint for a missing member.
missing-member-max-choices=1
# Regex pattern to define which classes are considered mixins.
mixin-class-rgx=.*[Mm]ixin
# List of decorators that change the signature of a decorated function.
signature-mutators=
[IMPORTS]
[VARIABLES]
# Deprecated modules which should not be used, separated by a comma
deprecated-modules=regsub,TERMIOS,Bastion,rexec
# List of additional names supposed to be defined in builtins. Remember that
# you should avoid defining new builtins when possible.
additional-builtins=
# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=
# Tells whether unused global variables should be treated as a violation.
allow-global-unused-variables=yes
# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=
# List of names allowed to shadow builtins
allowed-redefined-builtins=
# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=
# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,
_cb
# A regular expression matching the name of dummy variables (i.e. expected to
# not be used).
dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_
[CLASSES]
# Argument names that match this expression will be ignored.
ignored-argument-names=_.*|^ignored_|^unused_
# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defined in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by,implementedBy,providedBy
# Tells whether we should check for unused import in __init__ files.
init-import=no
# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp
# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls
# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs
[EXCEPTIONS]
# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception
# List of qualified module names which can have objects that can redefine
# builtins.
redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io

View File

@@ -17,9 +17,13 @@ Authors
* [Alex Halderman](https://github.com/jhalderm)
* [Alex Jordan](https://github.com/strugee)
* [Alex Zorin](https://github.com/alexzorin)
* [Alexis Hancock](https://github.com/zoracon)
* [Amir Omidi](https://github.com/aaomidi)
* [Amjad Mashaal](https://github.com/TheNavigat)
* [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)
@@ -65,6 +69,7 @@ Authors
* [Daniel Convissor](https://github.com/convissor)
* [Daniel "Drex" Drexler](https://github.com/aeturnum)
* [Daniel Huang](https://github.com/dhuang)
* [Daniel McMahon] (https://github.com/igloodan)
* [Dave Guarino](https://github.com/daguar)
* [David cz](https://github.com/dave-cz)
* [David Dworken](https://github.com/ddworken)
@@ -89,6 +94,7 @@ Authors
* [Felix Yan](https://github.com/felixonmars)
* [Filip Ochnik](https://github.com/filipochnik)
* [Florian Klink](https://github.com/flokli)
* [Francesco Colista](https://github.com/fcolista)
* [Francois Marier](https://github.com/fmarier)
* [Frank](https://github.com/Frankkkkk)
* [Frederic BLANC](https://github.com/fblanc)
@@ -115,8 +121,10 @@ Authors
* [Jacob Sachs](https://github.com/jsachs)
* [Jairo Llopis](https://github.com/Yajo)
* [Jakub Warmuz](https://github.com/kuba)
* [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)
@@ -147,6 +155,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)
@@ -157,6 +166,7 @@ Authors
* [Luca Ebach](https://github.com/lucebac)
* [Luca Olivetti](https://github.com/olivluca)
* [Luke Rogers](https://github.com/lukeroge)
* [Lukhnos Liu](https://github.com/lukhnos)
* [Maarten](https://github.com/mrtndwrd)
* [Mads Jensen](https://github.com/atombrella)
* [Maikel Martens](https://github.com/krukas)
@@ -174,6 +184,7 @@ Authors
* [Mathieu Leduc-Hamel](https://github.com/mlhamel)
* [Matt Bostock](https://github.com/mattbostock)
* [Matthew Ames](https://github.com/SuperMatt)
* [Matthew W. Thomas](https://github.com/mwt)
* [Michael Schumacher](https://github.com/schumaml)
* [Michael Strache](https://github.com/Jarodiv)
* [Michael Sverdlin](https://github.com/sveder)
@@ -198,24 +209,30 @@ Authors
* [osirisinferi](https://github.com/osirisinferi)
* Patrick Figel
* [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)
* [Peter Eckersley](https://github.com/pde)
* [Peter Mosmans](https://github.com/PeterMosmans)
* [Phil Martin](https://github.com/frillip)
* [Philippe Langlois](https://github.com/langloisjp)
* [Philipp Spitzer](https://github.com/spitza)
* [Piero Steinger](https://github.com/Jadaw1n)
* [Pierre Jaury](https://github.com/kaiyou)
* [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)
@@ -273,6 +290,7 @@ Authors
* [Wilfried Teiken](https://github.com/wteiken)
* [Willem Fibbe](https://github.com/fibbers)
* [William Budington](https://github.com/Hainish)
* [Will Greenberg](https://github.com/wgreenberg)
* [Will Newby](https://github.com/willnewby)
* [Will Oller](https://github.com/willoller)
* [Yan](https://github.com/diracdeltas)
@@ -283,3 +301,4 @@ Authors
* [Yuseong Cho](https://github.com/g6123)
* [Zach Shepherd](https://github.com/zjs)
* [陈三](https://github.com/chenxsan)
* [Shahar Naveh](https://github.com/ShaharNaveh)

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: acme/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: acme/readthedocs.org.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

@@ -2,7 +2,7 @@
This module is an implementation of the `ACME protocol`_.
.. _`ACME protocol`: https://ietf-wg-acme.github.io/acme
.. _`ACME protocol`: https://datatracker.ietf.org/doc/html/rfc8555
"""
import sys

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,12 +92,11 @@ class DNS01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.msg.to_partial_json())
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
@@ -107,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):
@@ -127,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
@@ -163,13 +161,11 @@ class HTTP01ResponseTest(unittest.TestCase):
self.response = self.chall.response(KEY)
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.msg.to_partial_json())
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
@@ -183,15 +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()))
mock_get.assert_called_once_with(self.chall.uri("local"), verify=False)
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):
@@ -199,23 +196,34 @@ 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()))
mock_get.assert_called_once_with(self.chall.uri("local"), verify=False)
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):
self.response.simple_verify(self.chall, "local", KEY.public_key())
mock_get.assert_called_once_with(self.chall.uri("local"), verify=False,
timeout=30)
mock_get.reset_mock()
self.response.simple_verify(self.chall, "local", KEY.public_key(), timeout=1234)
mock_get.assert_called_once_with(self.chall.uri("local"), verify=False,
timeout=1234)
class HTTP01Test(unittest.TestCase):
@@ -231,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):
@@ -274,12 +280,11 @@ class TLSALPN01ResponseTest(unittest.TestCase):
}
def test_to_partial_json(self):
self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'},
self.response.to_partial_json())
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
@@ -288,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'))
@@ -313,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)
@@ -328,18 +332,18 @@ class TLSALPN01ResponseTest(unittest.TestCase):
mock_gethostbyname.assert_called_once_with('foo.com')
mock_probe_sni.assert_called_once_with(
host=b'127.0.0.1', port=self.response.PORT, name=b'foo.com',
alpn_protocols=['acme-tls/1'])
alpn_protocols=[b'acme-tls/1'])
self.response.probe_cert('foo.com', host='8.8.8.8')
mock_probe_sni.assert_called_with(
host=b'8.8.8.8', port=mock.ANY, name=b'foo.com',
alpn_protocols=['acme-tls/1'])
alpn_protocols=[b'acme-tls/1'])
@mock.patch('acme.challenges.TLSALPN01Response.probe_cert')
def test_simple_verify_false_on_probe_error(self, mock_probe_cert):
mock_probe_cert.side_effect = errors.Error
self.assertFalse(self.response.simple_verify(
self.chall, self.domain, KEY.public_key()))
assert not self.response.simple_verify(
self.chall, self.domain, KEY.public_key())
class TLSALPN01Test(unittest.TestCase):
@@ -354,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
@@ -367,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)
@@ -391,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
@@ -405,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(
@@ -419,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):
@@ -461,8 +464,6 @@ class DNSResponseTest(unittest.TestCase):
from acme.challenges import DNSResponse
self.msg = DNSResponse(validation=self.validation)
self.jmsg_to = {
'resource': 'challenge',
'type': 'dns',
'validation': self.validation,
}
self.jmsg_from = {
@@ -472,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):
@@ -492,12 +493,11 @@ class JWSPayloadRFC8555Compliant(unittest.TestCase):
from acme.challenges import HTTP01Response
challenge_body = HTTP01Response()
challenge_body.le_acme_version = 2
jobj = challenge_body.json_dumps(indent=2).encode()
# RFC8555 states that challenge responses must have an empty payload.
self.assertEqual(jobj, b'{}')
assert jobj == b'{}'
if __name__ == '__main__':
unittest.main() # pragma: no cover
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover

View File

@@ -0,0 +1,794 @@
"""Tests for acme.client."""
# pylint: disable=too-many-lines
import copy
import datetime
import http.client as http_client
import json
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
CERT_SAN_PEM = test_util.load_vector('cert-san.pem')
CSR_MIXED_PEM = test_util.load_vector('csr-mixed.pem')
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
DIRECTORY_V2 = messages.Directory({
'newAccount': 'https://www.letsencrypt-demo.org/acme/new-account',
'newNonce': 'https://www.letsencrypt-demo.org/acme/new-nonce',
'newOrder': 'https://www.letsencrypt-demo.org/acme/new-order',
'revokeCert': 'https://www.letsencrypt-demo.org/acme/revoke-cert',
'meta': messages.Directory.Meta(),
})
class ClientV2Test(unittest.TestCase):
"""Tests for acme.client.ClientV2."""
def setUp(self):
self.response = mock.MagicMock(
ok=True, status_code=http_client.OK, headers={}, links={})
self.net = mock.MagicMock()
self.net.post.return_value = self.response
self.net.get.return_value = self.response
self.identifier = messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value='example.com')
# Registration
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
reg = messages.Registration(
contact=self.contact, key=KEY.public_key())
the_arg: Dict = dict(reg)
self.new_reg = messages.NewRegistration(**the_arg)
self.regr = messages.RegistrationResource(
body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1')
# Authorization
authzr_uri = 'https://www.letsencrypt-demo.org/acme/authz/1'
challb = messages.ChallengeBody(
uri=(authzr_uri + '/1'), status=messages.STATUS_VALID,
chall=challenges.DNS(token=jose.b64decode(
'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA')))
self.challr = messages.ChallengeResource(
body=challb, authzr_uri=authzr_uri)
self.authz = messages.Authorization(
identifier=messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value='example.com'),
challenges=(challb,))
self.authzr = messages.AuthorizationResource(
body=self.authz, uri=authzr_uri)
# Reason code for revocation
self.rsn = 1
self.directory = DIRECTORY_V2
self.client = ClientV2(self.directory, self.net)
self.new_reg = self.new_reg.update(terms_of_service_agreed=True)
self.authzr_uri2 = 'https://www.letsencrypt-demo.org/acme/authz/2'
self.authz2 = self.authz.update(identifier=messages.Identifier(
typ=messages.IDENTIFIER_FQDN, value='www.example.com'),
status=messages.STATUS_PENDING)
self.authzr2 = messages.AuthorizationResource(
body=self.authz2, uri=self.authzr_uri2)
self.order = messages.Order(
identifiers=(self.authz.identifier, self.authz2.identifier),
status=messages.STATUS_PENDING,
authorizations=(self.authzr.uri, self.authzr_uri2),
finalize='https://www.letsencrypt-demo.org/acme/acct/1/order/1/finalize')
self.orderr = messages.OrderResource(
body=self.order,
uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1',
authorizations=[self.authzr, self.authzr2], csr_pem=CSR_MIXED_PEM)
def test_new_account(self):
self.response.status_code = http_client.CREATED
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = self.regr.uri
assert self.regr == self.client.new_account(self.new_reg)
def test_new_account_tos_link(self):
self.response.status_code = http_client.CREATED
self.response.json.return_value = self.regr.body.to_json()
self.response.headers['Location'] = self.regr.uri
self.response.links.update({
'terms-of-service': {'url': '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
with pytest.raises(errors.ConflictError):
self.client.new_account(self.new_reg)
def test_deactivate_account(self):
deactivated_regr = self.regr.update(
body=self.regr.body.update(status='deactivated'))
self.response.json.return_value = deactivated_regr.body.to_json()
self.response.status_code = http_client.OK
self.response.headers['Location'] = self.regr.uri
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)
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)
order_response.status_code = http_client.CREATED
order_response.json.return_value = self.order.to_json()
order_response.headers['Location'] = self.orderr.uri
self.net.post.return_value = order_response
authz_response = copy.deepcopy(self.response)
authz_response.json.return_value = self.authz.to_json()
authz_response.headers['Location'] = self.authzr.uri
authz_response2 = self.response
authz_response2.json.return_value = self.authz2.to_json()
authz_response2.headers['Location'] = self.authzr2.uri
with mock.patch('acme.client.ClientV2._post_as_get') as mock_post_as_get:
mock_post_as_get.side_effect = (authz_response, authz_response2)
assert self.client.new_order(CSR_MIXED_PEM) == self.orderr
def test_answer_challege(self):
self.response.links['up'] = {'url': self.challr.authzr_uri}
self.response.json.return_value = self.challr.body.to_json()
chall_response = challenges.DNSResponse(validation=None)
self.client.answer_challenge(self.challr.body, 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):
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):
mock_datetime.datetime.now.return_value = datetime.datetime(2018, 2, 15)
mock_datetime.timedelta = datetime.timedelta
expected_deadline = mock_datetime.datetime.now() + datetime.timedelta(seconds=90)
self.client.poll_authorizations = mock.Mock(return_value=self.orderr)
self.client.finalize_order = mock.Mock(return_value=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)
@mock.patch('acme.client.datetime')
def test_poll_authorizations_timeout(self, mock_datetime):
now_side_effect = [datetime.datetime(2018, 2, 15),
datetime.datetime(2018, 2, 16),
datetime.datetime(2018, 2, 17)]
mock_datetime.datetime.now.side_effect = now_side_effect
self.response.json.side_effect = [
self.authz.to_json(), self.authz2.to_json(), self.authz2.to_json()]
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)
challb = self.challr.body.update(status=messages.STATUS_INVALID,
error=messages.Error.with_code('unauthorized'))
authz = self.authz.update(status=messages.STATUS_INVALID, challenges=(challb,))
self.response.json.return_value = authz.to_json()
with pytest.raises(errors.ValidationError):
self.client.poll_authorizations(self.orderr, deadline)
def test_poll_authorizations_success(self):
deadline = datetime.datetime(9999, 9, 9)
updated_authz2 = self.authz2.update(status=messages.STATUS_VALID)
updated_authzr2 = messages.AuthorizationResource(
body=updated_authz2, uri=self.authzr_uri2)
updated_orderr = self.orderr.update(authorizations=[self.authzr, updated_authzr2])
self.response.json.side_effect = (
self.authz.to_json(), self.authz2.to_json(), updated_authz2.to_json())
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()
with pytest.raises(errors.UnexpectedUpdate):
self.client.poll(self.authzr)
def test_finalize_order_success(self):
updated_order = self.order.update(
certificate='https://www.letsencrypt-demo.org/acme/cert/',
status=messages.STATUS_VALID)
updated_orderr = self.orderr.update(body=updated_order, fullchain_pem=CERT_SAN_PEM)
self.response.json.return_value = updated_order.to_json()
self.response.text = CERT_SAN_PEM
deadline = datetime.datetime(9999, 9, 9)
assert self.client.finalize_order(self.orderr, deadline) == updated_orderr
def test_finalize_order_error(self):
updated_order = self.order.update(
error=messages.Error.with_code('unauthorized'),
status=messages.STATUS_INVALID)
self.response.json.return_value = updated_order.to_json()
deadline = datetime.datetime(9999, 9, 9)
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 pytest.raises(errors.Error, match="The certificate order failed"):
self.client.finalize_order(self.orderr, datetime.datetime(9999, 9, 9))
def test_finalize_order_timeout(self):
deadline = datetime.datetime.now() - datetime.timedelta(seconds=60)
with pytest.raises(errors.TimeoutError):
self.client.finalize_order(self.orderr, deadline)
def test_finalize_order_alt_chains(self):
updated_order = self.order.update(
certificate='https://www.letsencrypt-demo.org/acme/cert/',
status=messages.STATUS_VALID
)
updated_orderr = self.orderr.update(body=updated_order,
fullchain_pem=CERT_SAN_PEM,
alternative_fullchains_pem=[CERT_SAN_PEM,
CERT_SAN_PEM])
self.response.json.return_value = updated_order.to_json()
self.response.text = CERT_SAN_PEM
self.response.headers['Link'] ='<https://example.com/acme/cert/1>;rel="alternate", ' + \
'<https://example.com/dir>;rel="index", ' + \
'<https://example.com/acme/cert/2>;title="foo";rel="alternate"'
deadline = datetime.datetime(9999, 9, 9)
resp = self.client.finalize_order(self.orderr, deadline, fetch_alternative_chains=True)
self.net.post.assert_any_call('https://example.com/acme/cert/1',
mock.ANY, new_nonce_url=mock.ANY)
self.net.post.assert_any_call('https://example.com/acme/cert/2',
mock.ANY, new_nonce_url=mock.ANY)
assert resp == updated_orderr
del self.response.headers['Link']
resp = self.client.finalize_order(self.orderr, deadline, fetch_alternative_chains=True)
assert resp == updated_orderr.update(alternative_fullchains_pem=[])
def test_revoke(self):
self.client.revoke(messages_test.CERT, self.rsn)
self.net.post.assert_called_once_with(
self.directory["revokeCert"], mock.ANY, new_nonce_url=DIRECTORY_V2['newNonce'])
def test_revoke_bad_status_raises_error(self):
self.response.status_code = http_client.METHOD_NOT_ALLOWED
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()
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()
def test_external_account_required_true(self):
self.client.directory = messages.Directory({
'meta': messages.Directory.Meta(external_account_required=True)
})
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)
})
assert not self.client.external_account_required()
def test_external_account_required_default(self):
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'
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:
mock_client.return_value = self.authzr2
self.client.poll(self.authzr2) # pylint: disable=protected-access
self.client.net.post.assert_called_once_with(
self.authzr2.uri, None,
new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce')
self.client.net.get.assert_not_called()
def test_retry_after_date(self):
self.response.headers['Retry-After'] = 'Fri, 31 Dec 1999 23:59:59 GMT'
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):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
self.response.headers['Retry-After'] = 'foooo'
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):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
dt_mock.datetime.side_effect = datetime.datetime
self.response.headers['Retry-After'] = "Tue, 116 Feb 2016 11:50:00 MST"
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):
dt_mock.datetime.now.return_value = datetime.datetime(2015, 3, 27)
dt_mock.timedelta = datetime.timedelta
self.response.headers['Retry-After'] = '50'
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
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()
assert DIRECTORY_V2.to_partial_json() == \
ClientV2.get_directory('https://example.com/dir', self.net).to_partial_json()
class MockJSONDeSerializable(jose.JSONDeSerializable):
# pylint: disable=missing-docstring
def __init__(self, value):
self.value = value
def to_partial_json(self):
return {'foo': self.value}
@classmethod
def from_json(cls, jobj):
pass # pragma: no cover
class ClientNetworkTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork."""
def setUp(self):
self.verify_ssl = mock.MagicMock()
self.wrap_in_jws = mock.MagicMock(return_value=mock.sentinel.wrapped)
self.net = ClientNetwork(
key=KEY, alg=jose.RS256, verify_ssl=self.verify_ssl,
user_agent='acme-python-test')
self.response = mock.MagicMock(ok=True, status_code=http_client.OK)
self.response.headers = {}
self.response.links = {}
def test_init(self):
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)
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'}
# 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)
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
self.response.json.return_value = {}
with mock.patch('acme.client.messages.Error.from_json') as from_json:
from_json.side_effect = jose.DeserializationError
# pylint: disable=protected-access
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
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
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
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):
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
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
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',
'application/json; charset=utf-8'
)
except AssertionError:
return
raise AssertionError('Expected Content-Type warning ' #pragma: no cover
'to not have been logged')
@mock.patch('acme.client.logger')
def test_check_response_ok_bad_ct(self, mock_logger):
self.response.json.return_value = {}
self.response.headers['Content-Type'] = 'text/plain'
# pylint: disable=protected-access
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'
)
def test_check_response_conflict(self):
self.response.ok = False
self.response.status_code = 409
# pylint: disable=protected-access
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
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
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')
@mock.patch('acme.client.logger')
def test_send_request_get_der(self, mock_logger):
self.net.session = mock.MagicMock()
self.net.session.request.return_value = mock.MagicMock(
ok=True, status_code=http_client.OK,
content=b"hi")
# pylint: disable=protected-access
self.net._send_request('HEAD', 'http://example.com/', 'foo',
timeout=mock.ANY, bar='baz', headers={'Accept': 'application/pkix-cert'})
mock_logger.debug.assert_called_with(
'Received response:\nHTTP %d\n%s\n\n%s', 200,
'', b'aGk=')
def test_send_request_post(self):
self.net.session = mock.MagicMock()
self.net.session.request.return_value = self.response
# pylint: disable=protected-access
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')
def test_send_request_verify_ssl(self):
# pylint: disable=protected-access
for verify in True, False:
self.net.session = mock.MagicMock()
self.net.session.request.return_value = self.response
self.net.verify_ssl = verify
# pylint: disable=protected-access
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)
def test_send_request_user_agent(self):
self.net.session = mock.MagicMock()
# pylint: disable=protected-access
self.net._send_request('GET', 'http://example.com/',
headers={'bar': 'baz'})
self.net.session.request.assert_called_once_with(
'GET', 'http://example.com/', verify=mock.ANY,
timeout=mock.ANY,
headers={'User-Agent': 'acme-python-test', 'bar': 'baz'})
self.net._send_request('GET', 'http://example.com/',
headers={'User-Agent': 'foo2'})
self.net.session.request.assert_called_with(
'GET', 'http://example.com/',
verify=mock.ANY, timeout=mock.ANY, headers={'User-Agent': 'foo2'})
def test_send_request_timeout(self):
self.net.session = mock.MagicMock()
# pylint: disable=protected-access
self.net._send_request('GET', 'http://example.com/',
headers={'bar': 'baz'})
self.net.session.request.assert_called_once_with(
mock.ANY, mock.ANY, verify=mock.ANY, headers=mock.ANY,
timeout=45)
def test_del(self, close_exception=None):
sess = mock.MagicMock()
if close_exception is not None:
sess.close.side_effect = close_exception
self.net.session = sess
del self.net
sess.close.assert_called_once_with()
def test_del_error(self):
self.test_del(ReferenceError)
@mock.patch('acme.client.requests')
def test_requests_error_passthrough(self, mock_requests):
mock_requests.exceptions = requests.exceptions
mock_requests.request.side_effect = requests.exceptions.RequestException
# pylint: disable=protected-access
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
try:
# pylint: disable=protected-access
self.net._send_request('GET', "http://localhost:19123/nonexistent.txt")
# Value Error Generated Exceptions
except ValueError as y:
assert "Requesting localhost/nonexistent: " \
"Connection refused" == str(y)
# Requests Library Exceptions
except requests.exceptions.ConnectionError as z: #pragma: no cover
assert "'Connection aborted.'" in str(z) or "[WinError 10061]" in str(z)
class ClientNetworkWithMockedResponseTest(unittest.TestCase):
"""Tests for acme.client.ClientNetwork which mock out response."""
def setUp(self):
self.net = ClientNetwork(key=None, alg=None)
self.response = mock.MagicMock(ok=True, status_code=http_client.OK)
self.response.headers = {}
self.response.links = {}
self.response.checked = False
self.acmev1_nonce_response = mock.MagicMock(
ok=False, status_code=http_client.METHOD_NOT_ALLOWED)
self.acmev1_nonce_response.headers = {}
self.obj = mock.MagicMock()
self.wrapped_obj = mock.MagicMock()
self.content_type = mock.sentinel.content_type
self.all_nonces = [
jose.b64encode(b'Nonce'),
jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')]
self.available_nonces = self.all_nonces[:]
def send_request(*args, **kwargs):
# pylint: disable=unused-argument,missing-docstring
assert "new_nonce_url" not in kwargs
method = args[0]
uri = args[1]
if method == 'HEAD' and uri != "new_nonce_uri":
response = self.acmev1_nonce_response
else:
response = self.response
if self.available_nonces:
response.headers = {
self.net.REPLAY_NONCE_HEADER:
self.available_nonces.pop().decode()}
else:
response.headers = {}
return response
# pylint: disable=protected-access
self.net._send_request = self.send_request = mock.MagicMock(
side_effect=send_request)
self.net._check_response = self.check_response
self.net._wrap_in_jws = mock.MagicMock(return_value=self.wrapped_obj)
def check_response(self, response, content_type):
# pylint: disable=missing-docstring
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):
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):
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):
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
assert self.response == self.net.post('uri', self.obj)
assert self.response.checked
def test_post(self):
# pylint: disable=protected-access
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 = []
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')]
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']
with pytest.raises(errors.BadNonce):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_failed_retry(self):
check_response = mock.MagicMock()
check_response.side_effect = messages.Error.with_code('badNonce')
# pylint: disable=protected-access
self.net._check_response = check_response
with pytest.raises(messages.Error):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_not_retried(self):
check_response = mock.MagicMock()
check_response.side_effect = [messages.Error.with_code('malformed'),
self.response]
# pylint: disable=protected-access
self.net._check_response = check_response
with pytest.raises(messages.Error):
self.net.post('uri',
self.obj, content_type=self.content_type)
def test_post_successful_retry(self):
post_once = mock.MagicMock()
post_once.side_effect = [messages.Error.with_code('badNonce'),
self.response]
# pylint: disable=protected-access
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:
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
# regression test for https://github.com/certbot/certbot/issues/6092
bad_response = mock.MagicMock(ok=False, status_code=http_client.SERVICE_UNAVAILABLE)
self.net._send_request = mock.MagicMock()
self.net._send_request.return_value = bad_response
self.content_type = None
check_response = mock.MagicMock()
self.net._check_response = check_response
with pytest.raises(errors.ClientError):
self.net.post('uri',
self.obj, content_type=self.content_type,
new_nonce_url='new_nonce_uri')
assert check_response.call_count == 1
def test_new_nonce_uri_removed(self):
self.content_type = None
self.net.post('uri', self.obj, content_type=None, new_nonce_url='new_nonce_uri')
if __name__ == '__main__':
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,17 +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)
assert csr.get_version() == 0, \
"Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4"
class DumpPyopensslChainTest(unittest.TestCase):
@@ -330,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']
@@ -339,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

@@ -0,0 +1,60 @@
"""Tests for acme.fields."""
import datetime
import sys
import unittest
import warnings
import josepy as jose
import pytest
import pytz
class FixedTest(unittest.TestCase):
"""Tests for acme.fields.Fixed."""
def setUp(self):
from acme.fields import fixed
self.field = fixed('name', 'x')
def test_decode(self):
assert 'x' == self.field.decode('x')
def test_decode_bad(self):
with pytest.raises(jose.DeserializationError):
self.field.decode('y')
def test_encode(self):
assert 'x' == self.field.encode('x')
def test_encode_override(self):
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.encoded = '2015-03-27T00:00:00Z'
def test_default_encoder(self):
from acme.fields import RFC3339Field
assert self.encoded == RFC3339Field.default_encoder(self.decoded)
def test_default_encoder_naive_fails(self):
from acme.fields import RFC3339Field
with pytest.raises(ValueError):
RFC3339Field.default_encoder(datetime.datetime.now())
def test_default_decoder(self):
from acme.fields import RFC3339Field
assert self.decoded == RFC3339Field.default_decoder(self.encoded)
def test_default_decoder_raises_deserialization_error(self):
from acme.fields import RFC3339Field
with pytest.raises(jose.DeserializationError):
RFC3339Field.default_decoder('')
if __name__ == '__main__':
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,12 +1,16 @@
"""Tests for acme.messages."""
import contextlib
import sys
from typing import Dict
import unittest
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')
@@ -17,7 +21,10 @@ class ErrorTest(unittest.TestCase):
"""Tests for acme.messages.Error."""
def setUp(self):
from acme.messages import Error, ERROR_PREFIX
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',
@@ -25,54 +32,84 @@ class ErrorTest(unittest.TestCase):
'type': ERROR_PREFIX + 'malformed',
}
self.error_custom = Error(typ='custom', detail='bar')
self.identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com')
self.subproblem = Error.with_code('caa', detail='bar', title='title', identifier=self.identifier)
self.error_with_subproblems = Error.with_code('malformed', detail='foo', title='title', subproblems=[self.subproblem])
self.empty_error = Error()
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
hash(Error.from_json(self.error.to_json()))
def test_from_json_with_subproblems(self):
from acme.messages import Error
parsed_error = Error.from_json(self.error_with_subproblems.to_json())
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))
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)
# 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):
assert self.error_custom.__traceback__ is None
try:
1/0
except ZeroDivisionError as e:
self.error_custom.__traceback__ = e.__traceback__
assert self.error_custom.__traceback__ is not None
class ConstantTest(unittest.TestCase):
@@ -89,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):
@@ -119,8 +156,8 @@ class DirectoryTest(unittest.TestCase):
def setUp(self):
from acme.messages import Directory
self.dir = Directory({
'new-reg': 'reg',
mock.MagicMock(resource_type='new-cert'): 'cert',
'newReg': 'reg',
'newCert': 'cert',
'meta': Directory.Meta(
terms_of_service='https://example.com/acme/terms',
website='https://www.example.com/',
@@ -133,30 +170,29 @@ class DirectoryTest(unittest.TestCase):
Directory({'foo': 'bar'})
def test_getitem(self):
self.assertEqual('reg', self.dir['new-reg'])
from acme.messages import NewRegistration
self.assertEqual('reg', self.dir[NewRegistration])
self.assertEqual('reg', self.dir[NewRegistration()])
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.new_reg)
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(), {
'new-reg': 'reg',
'new-cert': 'cert',
assert self.dir.to_json() == {
'newReg': 'reg',
'newCert': 'cert',
'meta': {
'terms-of-service': 'https://example.com/acme/terms',
'termsOfService': 'https://example.com/acme/terms',
'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
@@ -167,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):
@@ -184,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):
@@ -214,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"
@@ -229,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
@@ -257,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):
@@ -272,9 +310,8 @@ class UpdateRegistrationTest(unittest.TestCase):
def test_empty(self):
from acme.messages import UpdateRegistration
jstring = '{"resource": "reg"}'
self.assertEqual(jstring, UpdateRegistration().json_dumps())
self.assertEqual(
UpdateRegistration(), UpdateRegistration.json_loads(jstring))
assert '{}' == UpdateRegistration().json_dumps()
assert UpdateRegistration() == UpdateRegistration.json_loads(jstring)
class RegistrationResourceTest(unittest.TestCase):
@@ -287,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):
@@ -299,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):
@@ -320,7 +357,7 @@ class ChallengeBodyTest(unittest.TestCase):
error=error)
self.jobj_to = {
'uri': 'http://challb',
'url': 'http://challb',
'status': self.status,
'type': 'dns',
'token': 'evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA',
@@ -334,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):
@@ -367,20 +404,17 @@ class AuthorizationTest(unittest.TestCase):
chall=challenges.DNS(
token=b'DGyRejmCefe7v4NfDGDKfA')),
)
combinations = ((0,), (1,))
from acme.messages import Authorization
from acme.messages import Identifier
from acme.messages import IDENTIFIER_FQDN
identifier = Identifier(typ=IDENTIFIER_FQDN, value='example.com')
self.authz = Authorization(
identifier=identifier, combinations=combinations,
challenges=self.challbs)
identifier=identifier, challenges=self.challbs)
self.jobj_from = {
'identifier': identifier.to_json(),
'challenges': [challb.to_json() for challb in self.challbs],
'combinations': combinations,
}
def test_from_json(self):
@@ -391,12 +425,6 @@ class AuthorizationTest(unittest.TestCase):
from acme.messages import Authorization
hash(Authorization.from_json(self.jobj_from))
def test_resolved_combinations(self):
self.assertEqual(self.authz.resolved_combinations, (
(self.challbs[0],),
(self.challbs[1],),
))
class AuthorizationResourceTest(unittest.TestCase):
"""Tests for acme.messages.AuthorizationResource."""
@@ -406,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):
@@ -417,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):
@@ -433,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):
@@ -460,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."""
@@ -476,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):
@@ -487,12 +543,11 @@ class JWSPayloadRFC8555Compliant(unittest.TestCase):
from acme.messages import NewAuthorization
new_order = NewAuthorization()
new_order.le_acme_version = 2
jobj = new_order.json_dumps(indent=2).encode()
# RFC8555 states that JWS bodies must not have a resource field.
self.assertEqual(jobj, b'{}')
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
@@ -23,9 +23,6 @@ import requests
from acme import crypto_util
from acme import errors
from acme import fields
from acme.mixins import ResourceMixin
from acme.mixins import TypeMixin
logger = logging.getLogger(__name__)
@@ -47,12 +44,17 @@ class Challenge(jose.TypedJSONObjectWithFields):
return UnrecognizedChallenge.from_json(jobj)
class ChallengeResponse(ResourceMixin, TypeMixin, jose.TypedJSONObjectWithFields):
class ChallengeResponse(jose.TypedJSONObjectWithFields):
# _fields_to_partial_json
"""ACME challenge response."""
TYPES: Dict[str, Type['ChallengeResponse']] = {}
resource_type = 'challenge'
resource: str = fields.resource(resource_type)
def to_partial_json(self) -> Dict[str, Any]:
# Removes the `type` field which is inserted by TypedJSONObjectWithFields.to_partial_json.
# This field breaks RFC8555 compliance.
jobj = super().to_partial_json()
jobj.pop(self.type_field_name, None)
return jobj
class UnrecognizedChallenge(Challenge):
@@ -299,7 +301,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
"""Whitespace characters which should be ignored at the end of the body."""
def simple_verify(self, chall: 'HTTP01', domain: str, account_public_key: jose.JWK,
port: Optional[int] = None) -> bool:
port: Optional[int] = None, timeout: int = 30) -> bool:
"""Simple verify.
:param challenges.SimpleHTTP chall: Corresponding challenge.
@@ -307,6 +309,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
:param JWK account_public_key: Public key for the key pair
being authorized.
:param int port: Port used in the validation.
:param int timeout: Timeout in seconds.
:returns: ``True`` iff validation with the files currently served by the
HTTP server is successful.
@@ -328,7 +331,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse):
uri = chall.uri(domain)
logger.debug("Verifying %s at %s...", chall.typ, uri)
try:
http_response = requests.get(uri, verify=False)
http_response = requests.get(uri, verify=False, timeout=timeout)
except requests.exceptions.RequestException as error:
logger.error("Unable to reach %s: %s", uri, error)
return False
@@ -408,7 +411,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
"""
ID_PE_ACME_IDENTIFIER_V1 = b"1.3.6.1.5.5.7.1.30.1"
ACME_TLS_1_PROTOCOL = "acme-tls/1"
ACME_TLS_1_PROTOCOL = b"acme-tls/1"
@property
def h(self) -> bytes:

File diff suppressed because it is too large Load Diff

View File

@@ -11,6 +11,7 @@ from typing import Callable
from typing import List
from typing import Mapping
from typing import Optional
from typing import Sequence
from typing import Set
from typing import Tuple
from typing import Union
@@ -39,7 +40,9 @@ class _DefaultCertSelection:
def __call__(self, connection: SSL.Connection) -> Optional[Tuple[crypto.PKey, crypto.X509]]:
server_name = connection.get_servername()
return self.certs.get(server_name, None)
if server_name:
return self.certs.get(server_name, None)
return None # pragma: no cover
class SSLSocket: # pylint: disable=too-few-public-methods
@@ -60,7 +63,8 @@ class SSLSocket: # pylint: disable=too-few-public-methods
method: int = _DEFAULT_SSL_METHOD,
alpn_selection: Optional[Callable[[SSL.Connection, List[bytes]], bytes]] = None,
cert_selection: Optional[Callable[[SSL.Connection],
Tuple[crypto.PKey, crypto.X509]]] = None
Optional[Tuple[crypto.PKey,
crypto.X509]]]] = None
) -> None:
self.sock = sock
self.alpn_selection = alpn_selection
@@ -71,8 +75,8 @@ class SSLSocket: # pylint: disable=too-few-public-methods
raise ValueError("Both cert_selection and certs specified.")
actual_cert_selection: Union[_DefaultCertSelection,
Optional[Callable[[SSL.Connection],
Tuple[crypto.PKey,
crypto.X509]]]] = cert_selection
Optional[Tuple[crypto.PKey,
crypto.X509]]]]] = cert_selection
if actual_cert_selection is None:
actual_cert_selection = _DefaultCertSelection(certs if certs else {})
self.cert_selection = actual_cert_selection
@@ -120,35 +124,50 @@ class SSLSocket: # pylint: disable=too-few-public-methods
def shutdown(self, *unused_args: Any) -> bool:
# OpenSSL.SSL.Connection.shutdown doesn't accept any args
return self._wrapped.shutdown()
try:
return self._wrapped.shutdown()
except SSL.Error as error:
# We wrap the error so we raise the same error type as sockets
# in the standard library. This is useful when this object is
# used by code which expects a standard socket such as
# socketserver in the standard library.
raise socket.error(error)
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()
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
method: int = _DEFAULT_SSL_METHOD, source_address: Tuple[str, int] = ('', 0),
alpn_protocols: Optional[List[str]] = None) -> crypto.X509:
alpn_protocols: Optional[Sequence[bytes]] = None) -> crypto.X509:
"""Probe SNI server for SSL certificate.
:param bytes name: Byte string to send as the server name in the
@@ -161,7 +180,7 @@ def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, #
of source interface). See `socket.creation_connection` for more
info. Available only in Python 2.7+.
:param alpn_protocols: Protocols to request using ALPN.
:type alpn_protocols: `list` of `str`
:type alpn_protocols: `Sequence` of `bytes`
:raises acme.errors.Error: In case of any problems.
@@ -198,7 +217,9 @@ def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, #
client_ssl.shutdown()
except SSL.Error as error:
raise errors.Error(error)
return client_ssl.get_peer_certificate()
cert = client_ssl.get_peer_certificate()
assert cert # Appease mypy. We would have crashed out by now if there was no certificate.
return cert
def make_csr(private_key_pem: bytes, domains: Optional[Union[Set[str], List[str]]] = None,
@@ -249,7 +270,8 @@ def make_csr(private_key_pem: bytes, domains: Optional[Union[Set[str], List[str]
value=b"DER:30:03:02:01:05"))
csr.add_extensions(extensions)
csr.set_pubkey(private_key)
csr.set_version(2)
# RFC 2986 Section 4.1 only defines version 0
csr.set_version(0)
csr.sign(private_key, 'sha256')
return crypto.dump_certificate_request(
crypto.FILETYPE_PEM, csr)

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)``).
"""
@@ -51,22 +50,6 @@ class RFC3339Field(jose.Field):
raise jose.DeserializationError(error)
class Resource(jose.Field):
"""Resource MITM field."""
def __init__(self, resource_type: str, *args: Any, **kwargs: Any) -> None:
self.resource_type = resource_type
kwargs['default'] = resource_type
super().__init__('resource', *args, **kwargs)
def decode(self, value: Any) -> Any:
if value != self.resource_type:
raise jose.DeserializationError(
'Wrong resource type: {0} instead of {1}'.format(
value, self.resource_type))
return value
def fixed(json_name: str, value: Any) -> Any:
"""Generates a type-friendly Fixed field."""
return Fixed(json_name, value)
@@ -75,8 +58,3 @@ def fixed(json_name: str, value: Any) -> Any:
def rfc3339(json_name: str, omitempty: bool = False) -> Any:
"""Generates a type-friendly RFC3339 field."""
return RFC3339Field(json_name, omitempty=omitempty)
def resource(resource_type: str) -> Any:
"""Generates a type-friendly Resource field."""
return Resource(resource_type)

View File

@@ -29,7 +29,7 @@ class Header(jose.Header):
class Signature(jose.Signature):
"""ACME-specific Signature. Uses ACME-specific Header for customer fields."""
__slots__ = jose.Signature._orig_slots # type: ignore[attr-defined] # pylint: disable=protected-access,no-member
__slots__ = jose.Signature._orig_slots # pylint: disable=protected-access,no-member
# TODO: decoder/encoder should accept cls? Otherwise, subclassing
# JSONObjectWithFields is tricky...
@@ -44,7 +44,7 @@ class Signature(jose.Signature):
class JWS(jose.JWS):
"""ACME-specific JWS. Includes none, url, and kid in protected header."""
signature_cls = Signature
__slots__ = jose.JWS._orig_slots # type: ignore[attr-defined] # pylint: disable=protected-access
__slots__ = jose.JWS._orig_slots # pylint: disable=protected-access
@classmethod
# type: ignore[override] # pylint: disable=arguments-differ

View File

@@ -1,18 +0,0 @@
"""Simple shim around the typing module.
This was useful when this code supported Python 2 and typing wasn't always
available. This code is being kept for now for backwards compatibility.
"""
import warnings
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
from typing import Any
warnings.warn("acme.magic_typing is deprecated and will be removed in a future release.",
DeprecationWarning)
class TypingClass:
"""Ignore import errors by getting anything"""
def __getattr__(self, name: str) -> Any:
return None # pragma: no cover

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
@@ -11,9 +11,7 @@ from typing import MutableMapping
from typing import Optional
from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import josepy as jose
@@ -22,14 +20,7 @@ from acme import errors
from acme import fields
from acme import jws
from acme import util
from acme.mixins import ResourceMixin
if TYPE_CHECKING:
from typing_extensions import Protocol # pragma: no cover
else:
Protocol = object
OLD_ERROR_PREFIX = "urn:acme:error:"
ERROR_PREFIX = "urn:ietf:params:acme:error:"
ERROR_CODES = {
@@ -67,31 +58,93 @@ ERROR_CODES = {
ERROR_TYPE_DESCRIPTIONS = {**{
ERROR_PREFIX + name: desc for name, desc in ERROR_CODES.items()
}, **{ # add errors with old prefix, deprecate me
OLD_ERROR_PREFIX + name: desc for name, desc in ERROR_CODES.items()
}}
def is_acme_error(err: BaseException) -> bool:
"""Check if argument is an ACME error."""
if isinstance(err, Error) and (err.typ is not None):
return (ERROR_PREFIX in err.typ) or (OLD_ERROR_PREFIX in err.typ)
return ERROR_PREFIX in err.typ
return False
class _Constant(jose.JSONDeSerializable, Hashable):
"""ACME constant."""
__slots__ = ('name',)
POSSIBLE_NAMES: Dict[str, '_Constant'] = NotImplemented
def __init__(self, name: str) -> None:
super().__init__()
self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation
self.name = name
def to_partial_json(self) -> str:
return self.name
@classmethod
def from_json(cls, jobj: str) -> '_Constant':
if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test
raise jose.DeserializationError(f'{cls.__name__} not recognized')
return cls.POSSIBLE_NAMES[jobj]
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.name})'
def __eq__(self, other: Any) -> bool:
return isinstance(other, type(self)) and other.name == self.name
def __hash__(self) -> int:
return hash((self.__class__, self.name))
class IdentifierType(_Constant):
"""ACME identifier type."""
POSSIBLE_NAMES: Dict[str, _Constant] = {}
IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder
IDENTIFIER_IP = IdentifierType('ip') # IdentifierIP in pebble - not in Boulder yet
class Identifier(jose.JSONObjectWithFields):
"""ACME identifier.
:ivar IdentifierType typ:
:ivar str value:
"""
typ: IdentifierType = jose.field('type', decoder=IdentifierType.from_json)
value: str = jose.field('value')
class Error(jose.JSONObjectWithFields, errors.Error):
"""ACME error.
https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
https://datatracker.ietf.org/doc/html/rfc7807
Note: Although Error inherits from JSONObjectWithFields, which is immutable,
we add mutability for Error to comply with the Python exception API.
:ivar str typ:
:ivar str title:
:ivar str detail:
:ivar Identifier identifier:
:ivar tuple subproblems: An array of ACME Errors which may be present when the CA
returns multiple errors related to the same request, `tuple` of `Error`.
"""
typ: str = jose.field('type', omitempty=True, default='about:blank')
title: str = jose.field('title', omitempty=True)
detail: str = jose.field('detail', omitempty=True)
identifier: Optional['Identifier'] = jose.field(
'identifier', decoder=Identifier.from_json, omitempty=True)
subproblems: Optional[Tuple['Error', ...]] = jose.field('subproblems', omitempty=True)
# Mypy does not understand the josepy magic happening here, and falsely claims
# that subproblems is redefined. Let's ignore the type check here.
@subproblems.decoder # type: ignore
def subproblems(value: List[Dict[str, Any]]) -> Tuple['Error', ...]: # pylint: disable=no-self-argument,missing-function-docstring
return tuple(Error.from_json(subproblem) for subproblem in value)
@classmethod
def with_code(cls, code: str, **kwargs: Any) -> 'Error':
@@ -134,40 +187,21 @@ class Error(jose.JSONObjectWithFields, errors.Error):
return code
return None
# Hack to allow mutability on Errors (see GH #9539)
def __setattr__(self, name: str, value: Any) -> None:
return object.__setattr__(self, name, value)
def __str__(self) -> str:
return b' :: '.join(
result = b' :: '.join(
part.encode('ascii', 'backslashreplace') for part in
(self.typ, self.description, self.detail, self.title)
if part is not None).decode()
class _Constant(jose.JSONDeSerializable, Hashable):
"""ACME constant."""
__slots__ = ('name',)
POSSIBLE_NAMES: Dict[str, '_Constant'] = NotImplemented
def __init__(self, name: str) -> None:
super().__init__()
self.POSSIBLE_NAMES[name] = self # pylint: disable=unsupported-assignment-operation
self.name = name
def to_partial_json(self) -> str:
return self.name
@classmethod
def from_json(cls, jobj: str) -> '_Constant':
if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test
raise jose.DeserializationError(f'{cls.__name__} not recognized')
return cls.POSSIBLE_NAMES[jobj]
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self.name})'
def __eq__(self, other: Any) -> bool:
return isinstance(other, type(self)) and other.name == self.name
def __hash__(self) -> int:
return hash((self.__class__, self.name))
if self.identifier:
result = f'Problem for {self.identifier.value}: ' + result # pylint: disable=no-member
if self.subproblems and len(self.subproblems) > 0:
for subproblem in self.subproblems:
result += f'\n{subproblem}'
return result
class Status(_Constant):
@@ -185,45 +219,15 @@ STATUS_READY = Status('ready')
STATUS_DEACTIVATED = Status('deactivated')
class IdentifierType(_Constant):
"""ACME identifier type."""
POSSIBLE_NAMES: Dict[str, _Constant] = {}
IDENTIFIER_FQDN = IdentifierType('dns') # IdentifierDNS in Boulder
IDENTIFIER_IP = IdentifierType('ip') # IdentifierIP in pebble - not in Boulder yet
class Identifier(jose.JSONObjectWithFields):
"""ACME identifier.
:ivar IdentifierType typ:
:ivar str value:
"""
typ: IdentifierType = jose.field('type', decoder=IdentifierType.from_json)
value: str = jose.field('value')
class HasResourceType(Protocol):
"""
Represents a class with a resource_type class parameter of type string.
"""
resource_type: str = NotImplemented
GenericHasResourceType = TypeVar("GenericHasResourceType", bound=HasResourceType)
class Directory(jose.JSONDeSerializable):
"""Directory."""
"""Directory.
_REGISTERED_TYPES: Dict[str, Type[HasResourceType]] = {}
Directory resources must be accessed by the exact field name in RFC8555 (section 9.7.5).
"""
class Meta(jose.JSONObjectWithFields):
"""Directory Meta."""
_terms_of_service: str = jose.field('terms-of-service', omitempty=True)
_terms_of_service_v2: str = jose.field('termsOfService', omitempty=True)
_terms_of_service: str = jose.field('termsOfService', omitempty=True)
website: str = jose.field('website', omitempty=True)
caa_identities: List[str] = jose.field('caaIdentities', omitempty=True)
external_account_required: bool = jose.field('externalAccountRequired', omitempty=True)
@@ -235,7 +239,7 @@ class Directory(jose.JSONDeSerializable):
@property
def terms_of_service(self) -> str:
"""URL for the CA TOS"""
return self._terms_of_service or self._terms_of_service_v2
return self._terms_of_service
def __iter__(self) -> Iterator[str]:
# When iterating over fields, use the external name 'terms_of_service' instead of
@@ -246,41 +250,23 @@ class Directory(jose.JSONDeSerializable):
def _internal_name(self, name: str) -> str:
return '_' + name if name == 'terms_of_service' else name
@classmethod
def _canon_key(cls, key: Union[str, HasResourceType, Type[HasResourceType]]) -> str:
if isinstance(key, str):
return key
return key.resource_type
@classmethod
def register(cls,
resource_body_cls: Type[GenericHasResourceType]) -> Type[GenericHasResourceType]:
"""Register resource."""
resource_type = resource_body_cls.resource_type
assert resource_type not in cls._REGISTERED_TYPES
cls._REGISTERED_TYPES[resource_type] = resource_body_cls
return resource_body_cls
def __init__(self, jobj: Mapping[str, Any]) -> None:
canon_jobj = util.map_keys(jobj, self._canon_key)
# TODO: check that everything is an absolute URL; acme-spec is
# not clear on that
self._jobj = canon_jobj
self._jobj = jobj
def __getattr__(self, name: str) -> Any:
try:
return self[name.replace('_', '-')]
return self[name]
except KeyError as error:
raise AttributeError(str(error))
def __getitem__(self, name: Union[str, HasResourceType, Type[HasResourceType]]) -> Any:
def __getitem__(self, name: str) -> Any:
try:
return self._jobj[self._canon_key(name)]
return self._jobj[name]
except KeyError:
raise KeyError('Directory field "' + self._canon_key(name) + '" not found')
raise KeyError(f'Directory field "{name}" not found')
def to_partial_json(self) -> Dict[str, Any]:
return self._jobj
return util.map_keys(self._jobj, lambda k: k)
@classmethod
def from_json(cls, jobj: MutableMapping[str, Any]) -> 'Directory':
@@ -441,17 +427,12 @@ class Registration(ResourceBody):
return self._filter_contact(self.email_prefix)
@Directory.register
class NewRegistration(ResourceMixin, Registration):
class NewRegistration(Registration):
"""New registration."""
resource_type = 'new-reg'
resource: str = fields.resource(resource_type)
class UpdateRegistration(ResourceMixin, Registration):
class UpdateRegistration(Registration):
"""Update registration."""
resource_type = 'reg'
resource: str = fields.resource(resource_type)
class RegistrationResource(ResourceWithURI):
@@ -489,7 +470,6 @@ class ChallengeBody(ResourceBody):
# challenge object supports either one, but should be accessed through the
# name "uri". In Client.answer_challenge, whichever one is set will be
# used.
_uri: str = jose.field('uri', omitempty=True, default=None)
_url: str = jose.field('url', omitempty=True, default=None)
status: Status = jose.field('status', decoder=Status.from_json,
omitempty=True, default=STATUS_PENDING)
@@ -518,7 +498,7 @@ class ChallengeBody(ResourceBody):
@property
def uri(self) -> str:
"""The URL of this challenge."""
return self._url or self._uri
return self._url
def __getattr__(self, name: str) -> Any:
return getattr(self.chall, name)
@@ -527,10 +507,10 @@ class ChallengeBody(ResourceBody):
# When iterating over fields, use the external name 'uri' instead of
# the internal '_uri'.
for name in super().__iter__():
yield name[1:] if name == '_uri' else name
yield 'uri' if name == '_url' else name
def _internal_name(self, name: str) -> str:
return '_' + name if name == 'uri' else name
return '_url' if name == 'uri' else name
class ChallengeResource(Resource):
@@ -554,15 +534,12 @@ class Authorization(ResourceBody):
:ivar acme.messages.Identifier identifier:
:ivar list challenges: `list` of `.ChallengeBody`
:ivar tuple combinations: Challenge combinations (`tuple` of `tuple`
of `int`, as opposed to `list` of `list` from the spec).
:ivar acme.messages.Status status:
:ivar datetime.datetime expires:
"""
identifier: Identifier = jose.field('identifier', decoder=Identifier.from_json, omitempty=True)
challenges: List[ChallengeBody] = jose.field('challenges', omitempty=True)
combinations: Tuple[Tuple[int, ...], ...] = jose.field('combinations', omitempty=True)
status: Status = jose.field('status', omitempty=True, decoder=Status.from_json)
# TODO: 'expires' is allowed for Authorization Resources in
@@ -575,27 +552,16 @@ class Authorization(ResourceBody):
# Mypy does not understand the josepy magic happening here, and falsely claims
# that challenge is redefined. Let's ignore the type check here.
@challenges.decoder # type: ignore
def challenges(value: List[Dict[str, Any]]) -> Tuple[ChallengeBody, ...]: # type: ignore[misc] # pylint: disable=no-self-argument,missing-function-docstring
def challenges(value: List[Dict[str, Any]]) -> Tuple[ChallengeBody, ...]: # pylint: disable=no-self-argument,missing-function-docstring
return tuple(ChallengeBody.from_json(chall) for chall in value)
@property
def resolved_combinations(self) -> Tuple[Tuple[ChallengeBody, ...], ...]:
"""Combinations with challenges instead of indices."""
return tuple(tuple(self.challenges[idx] for idx in combo)
for combo in self.combinations) # pylint: disable=not-an-iterable
@Directory.register
class NewAuthorization(ResourceMixin, Authorization):
class NewAuthorization(Authorization):
"""New authorization."""
resource_type = 'new-authz'
resource: str = fields.resource(resource_type)
class UpdateAuthorization(ResourceMixin, Authorization):
class UpdateAuthorization(Authorization):
"""Update authorization."""
resource_type = 'authz'
resource: str = fields.resource(resource_type)
class AuthorizationResource(ResourceWithURI):
@@ -609,16 +575,13 @@ class AuthorizationResource(ResourceWithURI):
new_cert_uri: str = jose.field('new_cert_uri', omitempty=True)
@Directory.register
class CertificateRequest(ResourceMixin, jose.JSONObjectWithFields):
"""ACME new-cert request.
class CertificateRequest(jose.JSONObjectWithFields):
"""ACME newOrder request.
:ivar jose.ComparableX509 csr:
`OpenSSL.crypto.X509Req` wrapped in `.ComparableX509`
"""
resource_type = 'new-cert'
resource: str = fields.resource(resource_type)
csr: jose.ComparableX509 = jose.field('csr', decoder=jose.decode_csr, encoder=jose.encode_csr)
@@ -635,16 +598,13 @@ class CertificateResource(ResourceWithURI):
authzrs: Tuple[AuthorizationResource, ...] = jose.field('authzrs')
@Directory.register
class Revocation(ResourceMixin, jose.JSONObjectWithFields):
class Revocation(jose.JSONObjectWithFields):
"""Revocation message.
:ivar jose.ComparableX509 certificate: `OpenSSL.crypto.X509` wrapped in
`jose.ComparableX509`
"""
resource_type = 'revoke-cert'
resource: str = fields.resource(resource_type)
certificate: jose.ComparableX509 = jose.field(
'certificate', decoder=jose.decode_cert, encoder=jose.encode_cert)
reason: int = jose.field('reason')
@@ -675,7 +635,7 @@ class Order(ResourceBody):
# Mypy does not understand the josepy magic happening here, and falsely claims
# that identifiers is redefined. Let's ignore the type check here.
@identifiers.decoder # type: ignore
def identifiers(value: List[Dict[str, Any]]) -> Tuple[Identifier, ...]: # type: ignore[misc] # pylint: disable=no-self-argument,missing-function-docstring
def identifiers(value: List[Dict[str, Any]]) -> Tuple[Identifier, ...]: # pylint: disable=no-self-argument,missing-function-docstring
return tuple(Identifier.from_json(identifier) for identifier in value)
@@ -694,14 +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)
@Directory.register
class NewOrder(Order):
"""New order."""
resource_type = 'new-order'

View File

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

View File

@@ -46,10 +46,12 @@ class TLSServer(socketserver.TCPServer):
method=self.method))
def _cert_selection(self, connection: SSL.Connection
) -> Tuple[crypto.PKey, crypto.X509]: # pragma: no cover
) -> Optional[Tuple[crypto.PKey, crypto.X509]]: # pragma: no cover
"""Callback selecting certificate for connection."""
server_name = connection.get_servername()
return self.certs.get(server_name, None)
if server_name:
return self.certs.get(server_name, None)
return None
def server_bind(self) -> None:
self._wrap_sock()
@@ -151,14 +153,18 @@ class TLSALPN01Server(TLSServer, ACMEServerMixin):
def __init__(self, server_address: Tuple[str, int],
certs: List[Tuple[crypto.PKey, crypto.X509]],
challenge_certs: Mapping[str, Tuple[crypto.PKey, crypto.X509]],
challenge_certs: Mapping[bytes, Tuple[crypto.PKey, crypto.X509]],
ipv6: bool = False) -> None:
# We don't need to implement a request handler here because the work
# (including logging) is being done by wrapped socket set up in the
# parent TLSServer class.
TLSServer.__init__(
self, server_address, _BaseRequestHandlerWithLogging, certs=certs,
self, server_address, socketserver.BaseRequestHandler, certs=certs,
ipv6=ipv6)
self.challenge_certs = challenge_certs
def _cert_selection(self, connection: SSL.Connection) -> Tuple[crypto.PKey, crypto.X509]:
def _cert_selection(self, connection: SSL.Connection) -> Optional[Tuple[crypto.PKey,
crypto.X509]]:
# TODO: We would like to serve challenge cert only if asked for it via
# ALPN. To do this, we need to retrieve the list of protos from client
# hello, but this is currently impossible with openssl [0], and ALPN
@@ -167,8 +173,10 @@ class TLSALPN01Server(TLSServer, ACMEServerMixin):
# handshake in alpn_selection() if ALPN protos are not what we expect.
# [0] https://github.com/openssl/openssl/issues/4952
server_name = connection.get_servername()
logger.debug("Serving challenge cert for server name %s", server_name)
return self.challenge_certs[server_name]
if server_name:
logger.debug("Serving challenge cert for server name %s", server_name)
return self.challenge_certs[server_name]
return None # pragma: no cover
def _alpn_selection(self, _connection: SSL.Connection, alpn_protos: List[bytes]) -> bytes:
"""Callback to select alpn protocol."""
@@ -303,16 +311,3 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
return functools.partial(
cls, simple_http_resources=simple_http_resources,
timeout=timeout)
class _BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler):
"""BaseRequestHandler with logging."""
def log_message(self, format: str, *args: Any) -> None: # pylint: disable=redefined-builtin
"""Log arbitrary message."""
logger.debug("%s - - %s", self.client_address[0], format % args)
def handle(self) -> None:
"""Handle request."""
self.log_message("Incoming request")
socketserver.BaseRequestHandler.handle(self)

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

@@ -163,7 +163,7 @@ def example_http():
# Register account and accept TOS
net = client.ClientNetwork(acc_key, user_agent=USER_AGENT)
directory = messages.Directory.from_json(net.get(DIRECTORY_URL).json())
directory = client.ClientV2.get_directory(DIRECTORY_URL, net)
client_acme = client.ClientV2(directory, net=net)
# Terms of Service URL is in client_acme.directory.meta.terms_of_service
@@ -215,8 +215,7 @@ def example_http():
try:
regr = client_acme.query_registration(regr)
except errors.Error as err:
if err.typ == messages.OLD_ERROR_PREFIX + 'unauthorized' \
or err.typ == messages.ERROR_PREFIX + 'unauthorized':
if err.typ == messages.ERROR_PREFIX + 'unauthorized':
# Status is deactivated.
pass
raise

View File

@@ -3,16 +3,18 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.26.0.dev0'
version = '2.12.0.dev0'
install_requires = [
'cryptography>=2.5.0',
'josepy>=1.13.0',
'PyOpenSSL>=17.3.0',
'cryptography>=3.2.1',
# Josepy 2+ may introduce backward incompatible changes by droping usage of
# deprecated PyOpenSSL APIs.
'josepy>=1.13.0, <2',
# 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',
'requests-toolbelt>=0.3.0',
'setuptools>=41.6.0',
]
@@ -22,6 +24,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',
@@ -31,21 +42,22 @@ 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',
'Programming Language :: Python :: 3.11',
'Programming Language :: Python :: 3.12',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],

File diff suppressed because it is too large Load Diff

View File

@@ -1,72 +0,0 @@
"""Tests for acme.fields."""
import datetime
import unittest
import josepy as jose
import pytz
class FixedTest(unittest.TestCase):
"""Tests for acme.fields.Fixed."""
def setUp(self):
from acme.fields import fixed
self.field = fixed('name', 'x')
def test_decode(self):
self.assertEqual('x', self.field.decode('x'))
def test_decode_bad(self):
self.assertRaises(jose.DeserializationError, self.field.decode, 'y')
def test_encode(self):
self.assertEqual('x', self.field.encode('x'))
def test_encode_override(self):
self.assertEqual('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.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))
def test_default_encoder_naive_fails(self):
from acme.fields import RFC3339Field
self.assertRaises(
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))
def test_default_decoder_raises_deserialization_error(self):
from acme.fields import RFC3339Field
self.assertRaises(
jose.DeserializationError, RFC3339Field.default_decoder, '')
class ResourceTest(unittest.TestCase):
"""Tests for acme.fields.Resource."""
def setUp(self):
from acme.fields import Resource
self.field = Resource('x')
def test_decode_good(self):
self.assertEqual('x', self.field.decode('x'))
def test_decode_wrong(self):
self.assertRaises(jose.DeserializationError, self.field.decode, 'y')
if __name__ == '__main__':
unittest.main() # pragma: no cover

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,30 +0,0 @@
"""Tests for acme.magic_typing."""
import sys
import unittest
import warnings
from unittest import mock
class MagicTypingTest(unittest.TestCase):
"""Tests for acme.magic_typing."""
def test_import_success(self):
try:
import typing as temp_typing
except ImportError: # pragma: no cover
temp_typing = None # pragma: no cover
typing_class_mock = mock.MagicMock()
text_mock = mock.MagicMock()
typing_class_mock.Text = text_mock
sys.modules['typing'] = typing_class_mock
if 'acme.magic_typing' in sys.modules:
del sys.modules['acme.magic_typing'] # pragma: no cover
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=DeprecationWarning)
from acme.magic_typing import Text
self.assertEqual(Text, text_mock)
del sys.modules['acme.magic_typing']
sys.modules['typing'] = temp_typing
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__)
@@ -136,20 +143,18 @@ def included_in_paths(filepath: str, paths: Iterable[str]) -> bool:
return any(fnmatch.fnmatch(filepath, path) for path in paths)
def parse_defines(apachectl: str) -> Dict[str, str]:
def parse_defines(define_cmd: List[str]) -> Dict[str, str]:
"""
Gets Defines from httpd process and returns a dictionary of
the defined variables.
:param str apachectl: Path to apachectl executable
:param list define_cmd: httpd command to dump defines
:returns: dictionary of defined variables
:rtype: dict
"""
variables: Dict[str, str] = {}
define_cmd = [apachectl, "-t", "-D",
"DUMP_RUN_CFG"]
matches = parse_from_subprocess(define_cmd, r"Define: ([^ \n]*)")
try:
matches.remove("DUMP_RUN_CFG")
@@ -165,33 +170,31 @@ def parse_defines(apachectl: str) -> Dict[str, str]:
return variables
def parse_includes(apachectl: str) -> List[str]:
def parse_includes(inc_cmd: List[str]) -> List[str]:
"""
Gets Include directives from httpd process and returns a list of
their values.
:param str apachectl: Path to apachectl executable
:param list inc_cmd: httpd command to dump includes
:returns: list of found Include directive values
:rtype: list of str
"""
inc_cmd: List[str] = [apachectl, "-t", "-D", "DUMP_INCLUDES"]
return parse_from_subprocess(inc_cmd, r"\(.*\) (.*)")
def parse_modules(apachectl: str) -> List[str]:
def parse_modules(mod_cmd: List[str]) -> List[str]:
"""
Get loaded modules from httpd process, and return the list
of loaded module names.
:param str apachectl: Path to apachectl executable
:param list mod_cmd: httpd command to dump loaded modules
:returns: list of found LoadModule module names
:rtype: list of str
"""
mod_cmd = [apachectl, "-t", "-D", "DUMP_MODULES"]
return parse_from_subprocess(mod_cmd, r"(.*)_module")
@@ -252,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")
.joinpath("tls_configs").joinpath("{0}-options-ssl-apache.conf".format(prefix)))
return str(file_manager.enter_context(importlib_resources.as_file(ref)))

View File

@@ -118,7 +118,8 @@ class ApacheBlockNode(ApacheDirectiveNode):
# pylint: disable=unused-argument
def add_child_directive(self, name: str, parameters: Optional[List[str]] = None,
position: int = None) -> ApacheDirectiveNode: # pragma: no cover
position: Optional[int] = None
) -> ApacheDirectiveNode: # pragma: no cover
"""Adds a new DirectiveNode to the sequence of children"""
new_dir = ApacheDirectiveNode(name=assertions.PASS,
parameters=assertions.PASS,

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
@@ -85,6 +84,10 @@ class OsOptions:
self.restart_cmd = ['apache2ctl', 'graceful'] if not restart_cmd else restart_cmd
self.restart_cmd_alt = restart_cmd_alt
self.conftest_cmd = ['apache2ctl', 'configtest'] if not conftest_cmd else conftest_cmd
syntax_tests_cmd_base = [ctl, '-t', '-D']
self.get_defines_cmd = syntax_tests_cmd_base + ['DUMP_RUN_CFG']
self.get_includes_cmd = syntax_tests_cmd_base + ['DUMP_INCLUDES']
self.get_modules_cmd = syntax_tests_cmd_base + ['DUMP_MODULES']
self.enmod = enmod
self.dismod = dismod
self.le_vhost_ext = le_vhost_ext
@@ -166,6 +169,17 @@ class ApacheConfigurator(common.Configurator):
return apache_util.find_ssl_apache_conf("old")
return apache_util.find_ssl_apache_conf("current")
def _override_cmds(self) -> None:
"""
Set our various command binaries to whatever the user has overridden for apachectl
"""
self.options.version_cmd[0] = self.options.ctl
self.options.restart_cmd[0] = self.options.ctl
self.options.conftest_cmd[0] = self.options.ctl
self.options.get_modules_cmd[0] = self.options.ctl
self.options.get_includes_cmd[0] = self.options.ctl
self.options.get_defines_cmd[0] = self.options.ctl
def _prepare_options(self) -> None:
"""
Set the values possibly changed by command line parameters to
@@ -181,10 +195,7 @@ class ApacheConfigurator(common.Configurator):
else:
setattr(self.options, o, getattr(self.OS_DEFAULTS, o))
# Special cases
self.options.version_cmd[0] = self.options.ctl
self.options.restart_cmd[0] = self.options.ctl
self.options.conftest_cmd[0] = self.options.ctl
self._override_cmds()
@classmethod
def add_parser_arguments(cls, add: Callable[..., None]) -> None:
@@ -354,12 +365,9 @@ class ApacheConfigurator(common.Configurator):
self.version = self.get_version()
logger.debug('Apache version is %s',
'.'.join(str(i) for i in self.version))
if self.version < (2, 2):
if self.version < (2, 4):
raise errors.NotSupportedError(
"Apache Version {0} not supported.".format(str(self.version)))
elif self.version < (2, 4):
logger.warning('Support for Apache 2.2 is deprecated and will be removed in a '
'future release.')
# Recover from previous crash before Augeas initialization to have the
# correct parse tree from the get go.
@@ -479,9 +487,9 @@ class ApacheConfigurator(common.Configurator):
if HAS_APACHECONFIG:
apache_vars = {
"defines": apache_util.parse_defines(self.options.ctl),
"includes": apache_util.parse_includes(self.options.ctl),
"modules": apache_util.parse_modules(self.options.ctl),
"defines": apache_util.parse_defines(self.options.get_defines_cmd),
"includes": apache_util.parse_includes(self.options.get_includes_cmd),
"modules": apache_util.parse_modules(self.options.get_modules_cmd),
}
metadata["apache_vars"] = apache_vars
@@ -803,7 +811,7 @@ class ApacheConfigurator(common.Configurator):
return self._find_best_vhost(target, filtered_vhosts, filter_defaults)
def _find_best_vhost(
self, target_name: str, vhosts: List[obj.VirtualHost] = None,
self, target_name: str, vhosts: Optional[List[obj.VirtualHost]] = None,
filter_defaults: bool = True
) -> Optional[obj.VirtualHost]:
"""Finds the best vhost for a target_name.
@@ -1176,46 +1184,6 @@ class ApacheConfigurator(common.Configurator):
vhost.aliases.add(serveralias)
vhost.name = servername
def is_name_vhost(self, target_addr: obj.Addr) -> bool:
"""Returns if vhost is a name based vhost
NameVirtualHost was deprecated in Apache 2.4 as all VirtualHosts are
now NameVirtualHosts. If version is earlier than 2.4, check if addr
has a NameVirtualHost directive in the Apache config
:param certbot_apache._internal.obj.Addr target_addr: vhost address
:returns: Success
:rtype: bool
"""
# Mixed and matched wildcard NameVirtualHost with VirtualHost
# behavior is undefined. Make sure that an exact match exists
# search for NameVirtualHost directive for ip_addr
# note ip_addr can be FQDN although Apache does not recommend it
return (self.version >= (2, 4) or
bool(self.parser.find_dir("NameVirtualHost", str(target_addr))))
def add_name_vhost(self, addr: obj.Addr) -> None:
"""Adds NameVirtualHost directive for given address.
:param addr: Address that will be added as NameVirtualHost directive
:type addr: :class:`~certbot_apache._internal.obj.Addr`
"""
loc = parser.get_aug_path(self.parser.loc["name"])
if addr.get_port() == "443":
self.parser.add_dir_to_ifmodssl(
loc, "NameVirtualHost", [str(addr)])
else:
self.parser.add_dir(loc, "NameVirtualHost", [str(addr)])
msg = "Setting {0} to be NameBasedVirtualHost\n".format(addr)
logger.debug(msg)
self.save_notes += msg
def prepare_server_https(self, port: str, temp: bool = False) -> None:
"""Prepare the server for HTTPS.
@@ -1363,8 +1331,7 @@ class ApacheConfigurator(common.Configurator):
"""
if self.options.handle_modules:
if self.version >= (2, 4) and ("socache_shmcb_module" not in
self.parser.modules):
if "socache_shmcb_module" not in self.parser.modules:
self.enable_mod("socache_shmcb", temp=temp)
if "ssl_module" not in self.parser.modules:
self.enable_mod("ssl", temp=temp)
@@ -1451,10 +1418,6 @@ class ApacheConfigurator(common.Configurator):
# for the new directives; For these reasons... this is tacked
# on after fully creating the new vhost
# Now check if addresses need to be added as NameBasedVhost addrs
# This is for compliance with versions of Apache < 2.4
self._add_name_vhost_if_necessary(ssl_vhost)
return ssl_vhost
def _get_new_vh_path(self, orig_matches: List[str], new_matches: List[str]) -> Optional[str]:
@@ -1753,40 +1716,6 @@ class ApacheConfigurator(common.Configurator):
aliases = (self.parser.aug.get(match) for match in matches)
return self.domain_in_names(aliases, target_name)
def _add_name_vhost_if_necessary(self, vhost: obj.VirtualHost) -> None:
"""Add NameVirtualHost Directives if necessary for new vhost.
NameVirtualHosts was a directive in Apache < 2.4
https://httpd.apache.org/docs/2.2/mod/core.html#namevirtualhost
:param vhost: New virtual host that was recently created.
:type vhost: :class:`~certbot_apache._internal.obj.VirtualHost`
"""
need_to_save: bool = False
# See if the exact address appears in any other vhost
# Remember 1.1.1.1:* == 1.1.1.1 -> hence any()
for addr in vhost.addrs:
# In Apache 2.2, when a NameVirtualHost directive is not
# set, "*" and "_default_" will conflict when sharing a port
addrs = {addr,}
if addr.get_addr() in ("*", "_default_"):
addrs.update(obj.Addr((a, addr.get_port(),))
for a in ("*", "_default_"))
for test_vh in self.vhosts:
if (vhost.filep != test_vh.filep and
any(test_addr in addrs for
test_addr in test_vh.addrs) and not self.is_name_vhost(addr)):
self.add_name_vhost(addr)
logger.info("Enabling NameVirtualHosts on %s", addr)
need_to_save = True
break
if need_to_save:
self.save()
def find_vhost_by_id(self, id_str: str) -> obj.VirtualHost:
"""
Searches through VirtualHosts and tries to match the id in a comment
@@ -2002,12 +1931,6 @@ class ApacheConfigurator(common.Configurator):
:param unused_options: Not currently used
:type unused_options: Not Available
"""
min_apache_ver = (2, 3, 3)
if self.get_version() < min_apache_ver:
raise errors.PluginError(
"Unable to set OCSP directives.\n"
"Apache version is below 2.3.3.")
if "socache_shmcb_module" not in self.parser.modules:
self.enable_mod("socache_shmcb")
@@ -2188,10 +2111,7 @@ class ApacheConfigurator(common.Configurator):
general_vh.filep, ssl_vhost.filep)
def _set_https_redirection_rewrite_rule(self, vhost: obj.VirtualHost) -> None:
if self.get_version() >= (2, 3, 9):
self.parser.add_dir(vhost.path, "RewriteRule", constants.REWRITE_HTTPS_ARGS_WITH_END)
else:
self.parser.add_dir(vhost.path, "RewriteRule", constants.REWRITE_HTTPS_ARGS)
self.parser.add_dir(vhost.path, "RewriteRule", constants.REWRITE_HTTPS_ARGS)
def _verify_no_certbot_redirect(self, vhost: obj.VirtualHost) -> None:
"""Checks to see if a redirect was already installed by certbot.
@@ -2223,9 +2143,6 @@ class ApacheConfigurator(common.Configurator):
rewrite_args_dict[dir_path].append(match)
if rewrite_args_dict:
redirect_args = [constants.REWRITE_HTTPS_ARGS,
constants.REWRITE_HTTPS_ARGS_WITH_END]
for dir_path, args_paths in rewrite_args_dict.items():
arg_vals = [self.parser.aug.get(x) for x in args_paths]
@@ -2237,7 +2154,7 @@ class ApacheConfigurator(common.Configurator):
raise errors.PluginEnhancementAlreadyPresent(
"Certbot has already enabled redirection")
if arg_vals in redirect_args:
if arg_vals == constants.REWRITE_HTTPS_ARGS:
raise errors.PluginEnhancementAlreadyPresent(
"Certbot has already enabled redirection")
@@ -2306,12 +2223,6 @@ class ApacheConfigurator(common.Configurator):
if ssl_vhost.aliases:
serveralias = "ServerAlias " + " ".join(ssl_vhost.aliases)
rewrite_rule_args: List[str]
if self.get_version() >= (2, 3, 9):
rewrite_rule_args = constants.REWRITE_HTTPS_ARGS_WITH_END
else:
rewrite_rule_args = constants.REWRITE_HTTPS_ARGS
return (
f"<VirtualHost {' '.join(str(addr) for addr in self._get_proposed_addrs(ssl_vhost))}>\n"
f"{servername} \n"
@@ -2319,7 +2230,7 @@ class ApacheConfigurator(common.Configurator):
f"ServerSignature Off\n"
f"\n"
f"RewriteEngine On\n"
f"RewriteRule {' '.join(rewrite_rule_args)}\n"
f"RewriteRule {' '.join(constants.REWRITE_HTTPS_ARGS)}\n"
"\n"
f"ErrorLog {self.options.logs_root}/redirect.error.log\n"
f"LogLevel warn\n"

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
@@ -32,26 +36,31 @@ ALL_SSL_OPTIONS_HASHES: List[str] = [
'5cc003edd93fb9cd03d40c7686495f8f058f485f75b5e764b789245a386e6daf',
'007cd497a56a3bb8b6a2c1aeb4997789e7e38992f74e44cc5d13a625a738ac73',
'34783b9e2210f5c4a23bced2dfd7ec289834716673354ed7c7abf69fe30192a3',
'61466bc2f98a623c02be8a5ee916ead1655b0ce883bdc936692076ea499ff5ce',
'3fd812e3e87fe5c645d3682a511b2a06c8286f19594f28e280f17cd6af1301b5',
]
"""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] = [
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,NE,R=permanent]"]
"""Apache version<2.3.9 rewrite rule arguments used for redirections to
https vhost"""
REWRITE_HTTPS_ARGS_WITH_END: List[str] = [
"^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,NE,R=permanent]"]
"""Apache version >= 2.3.9 rewrite rule arguments used for redirections to
https vhost"""
OLD_REWRITE_HTTPS_ARGS: List[List[str]] = [
["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,QSA,R=permanent]"],
["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"]]
["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[END,QSA,R=permanent]"],
["^", "https://%{SERVER_NAME}%{REQUEST_URI}", "[L,NE,R=permanent]"]]
HSTS_ARGS: List[str] = ["always", "set", "Strict-Transport-Security",
"\"max-age=31536000\""]

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,7 +2,9 @@
from typing import Dict
from typing import Type
from certbot import util
from certbot_apache._internal import configurator
from certbot_apache._internal import override_alpine
from certbot_apache._internal import override_arch
from certbot_apache._internal import override_centos
from certbot_apache._internal import override_darwin
@@ -12,9 +14,8 @@ 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]] = {
"alpine": override_alpine.AlpineConfigurator,
"arch": override_arch.ArchConfigurator,
"cloudlinux": override_centos.CentOSConfigurator,
"darwin": override_darwin.DarwinConfigurator,

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
@@ -24,22 +23,6 @@ logger = logging.getLogger(__name__)
class ApacheHttp01(common.ChallengePerformer):
"""Class that performs HTTP-01 challenges within the Apache configurator."""
CONFIG_TEMPLATE22_PRE = """\
RewriteEngine on
RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [L]
"""
CONFIG_TEMPLATE22_POST = """\
<Directory {0}>
Order Allow,Deny
Allow from all
</Directory>
<Location /.well-known/acme-challenge>
Order Allow,Deny
Allow from all
</Location>
"""
CONFIG_TEMPLATE24_PRE = """\
RewriteEngine on
RewriteRule ^/\\.well-known/acme-challenge/([A-Za-z0-9-_=]+)$ {0}/$1 [END]
@@ -90,11 +73,7 @@ class ApacheHttp01(common.ChallengePerformer):
"""Make sure that we have the needed modules available for http01"""
if self.configurator.conf("handle-modules"):
needed_modules = ["rewrite"]
if self.configurator.version < (2, 4):
needed_modules.append("authz_host")
else:
needed_modules.append("authz_core")
needed_modules = ["rewrite", "authz_core"]
for mod in needed_modules:
if mod + "_module" not in self.configurator.parser.modules:
self.configurator.enable_mod(mod, temp=True)
@@ -131,15 +110,8 @@ class ApacheHttp01(common.ChallengePerformer):
self.configurator.reverter.register_file_creation(
True, self.challenge_conf_post)
if self.configurator.version < (2, 4):
config_template_pre = self.CONFIG_TEMPLATE22_PRE
config_template_post = self.CONFIG_TEMPLATE22_POST
else:
config_template_pre = self.CONFIG_TEMPLATE24_PRE
config_template_post = self.CONFIG_TEMPLATE24_POST
config_text_pre = config_template_pre.format(self.challenge_dir)
config_text_post = config_template_post.format(self.challenge_dir)
config_text_pre = self.CONFIG_TEMPLATE24_PRE.format(self.challenge_dir)
config_text_post = self.CONFIG_TEMPLATE24_POST.format(self.challenge_dir)
logger.debug("writing a pre config file with text:\n %s", config_text_pre)
with open(self.challenge_conf_pre, "w") as new_conf:
@@ -184,15 +156,13 @@ class ApacheHttp01(common.ChallengePerformer):
def _set_up_challenges(self) -> List[KeyAuthorizationChallengeResponse]:
if not os.path.isdir(self.challenge_dir):
old_umask = filesystem.umask(0o022)
try:
filesystem.makedirs(self.challenge_dir, 0o755)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for http-01 challenge")
finally:
filesystem.umask(old_umask)
with filesystem.temp_umask(0o022):
try:
filesystem.makedirs(self.challenge_dir, 0o755)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for http-01 challenge")
responses = []
for achall in self.achalls:

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