Compare commits

...

206 Commits

Author SHA1 Message Date
Erica Portnoy
fe1b963398 undo fix 2025-01-15 09:45:25 -08:00
Erica Portnoy
e85d3b6a83 set authenticator manually each call to override --standalone 2025-01-15 09:34:20 -08:00
Erica Portnoy
39cff40554 define key type outside conditional to make mypy happy 2025-01-14 18:24:19 -08:00
Erica Portnoy
cb9b435f06 add integration test 2025-01-14 18:09:47 -08:00
Erica Portnoy
aa55b78883 Add regression test 2025-01-14 17:42:35 -08:00
Erica Portnoy
de3d510f12 Pass the optionally-existing key back into obtain_certificates on retry so that the existing key can be used for the new certificate 2025-01-14 17:39:13 -08:00
Alexis
86694397a6 Update notify_weekly.yaml (#10118)
Making the weekly message a little more useful.

---------

Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
2025-01-13 07:46:12 -08:00
ohemorange
b18c074088 Allow non-breaking spaces in nginx config files (#10126)
Fixes @josevavia's issue in #9942.
2025-01-10 15:25:05 -08:00
Brad Warren
f59a639ec4 improve repin experience on macOS (#10128)
this hopefully at least helps the problem hit at
https://github.com/certbot/certbot/pull/10126#discussion_r1909714276

i took this approach because in my experience, linux specific shell
commands have crept into our scripts repeatedly over the years so i
think just having macOS devs use the linux versions is much more
reliable. it's what i've personally been doing for years now
2025-01-10 12:54:54 -08:00
Brad Warren
5411e4c86a silence poetry warning (#10127)
when reviewing https://github.com/certbot/certbot/pull/10126 and running
`tools/pinning/oldest/repin.sh` using a freshly created dev environment,
i was repeatedly given the message

> The "poetry.dev-dependencies" section is deprecated and will be
removed in a future version. Use "poetry.group.dev.dependencies"
instead.

i believe this section was generated automatically by poetry's tooling
when it created the initial boilerplate file for us, but we don't use
it, so i just deleted the section which makes the warnings disappear
2025-01-10 12:52:24 -08:00
Brad Warren
0425b87b78 Merge pull request #10123 from certbot/candidate-3.1.0
Release Certbot 3.1.0
2025-01-07 15:53:09 -08:00
Erica Portnoy
1de966d637 Bump version to 3.2.0 2025-01-07 12:54:01 -08:00
Erica Portnoy
ba2e4aecb7 Add contents to certbot/CHANGELOG.md for next version 2025-01-07 12:54:01 -08:00
Erica Portnoy
7d2b1996d9 Remove built packages from git 2025-01-07 12:54:01 -08:00
Erica Portnoy
dcd52b0711 Release 3.1.0 2025-01-07 12:54:00 -08:00
Erica Portnoy
8074858620 Update changelog for 3.1.0 release 2025-01-07 12:53:36 -08:00
Brad Warren
d3d293299a minor acme doc & comment fixes (#10122)
this fixes two tiny things i noticed when reviewing
https://github.com/certbot/certbot/pull/10120

1. not all of our `acme` modules were generating API documentation
2. the deleted commend about a "type ignore" should have been deleted in
https://github.com/certbot/certbot/pull/9197 but will and i missed it
2025-01-07 18:17:00 +00:00
Alex Gaynor
9148acd332 Migrate verify_cert to take cryptography certificates (#10120) 2025-01-07 17:46:31 +00:00
Brad Warren
9f9a1df85e upgrade pylint (#10121)
we need this for https://github.com/certbot/certbot/issues/10045
2025-01-07 09:43:14 -08:00
ohemorange
985457e57b Add docstring for acme.crypto_util.get_names_from_subject_and_extensions (#10115)
It was my oversight to not request this when this function was made
public in https://github.com/certbot/certbot/pull/10111.
2025-01-06 14:37:29 -08:00
Alex Gaynor
4004589cbf Migrate certbot-compatibility-test to cryptography (as much as possible (#10117)
Also fixed a typing error.
2025-01-06 13:39:16 -08:00
Alex Gaynor
8f7c3756b3 Migrate get_serial_from_cert and valid_privkey to cryptography (#10116) 2025-01-06 13:34:57 -08:00
Alex Gaynor
6ea5da51e0 Simplify typing for a local variable (#10113)
`_DefaultCertSelection` _is_ a `Callable` of the appropriate signature.

Also fixed a mypy error I see locally, `TOKEN_SIZE` should be an
integer.
2025-01-06 13:18:28 -08:00
Alex Gaynor
1ac05ae891 Remove _pyopenssl_cert_or_req_san_ip which is unused, and migrate _pyopenssl_cert_or_req_all_names to cryptography (#10112)
Unfortunately the other helpers from this family are directly called by
(historic) versions of certbot, and so cannot be easily removed.
2025-01-06 12:46:23 -08:00
Manuel Baldassarri
a441debdaa Add Nginx Unit plugin to documentation (#10110)
## Pull Request Checklist

- [ ] 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.
- [x] Add or update any documentation as needed to support the changes
in this PR.
- [ ] Include your name in `AUTHORS.md` if you like.
2025-01-06 12:37:31 -08:00
Alex Gaynor
5dd898f56b Move _get_names_from_subject_and_extensions to acme's crypto_utils (#10111)
Make use of it in more places
2025-01-03 16:21:31 -08:00
Alex Gaynor
a1fce6b398 Convert notBefore and notAfter to use cryptography's APIs (#10103) 2025-01-03 13:50:33 -08:00
Will Greenberg
635d9c3ec3 Merge pull request #10090 from alex/san-cryptography
Convert several SAN handling functions to use cryptography's APIs
2025-01-02 11:42:49 -08:00
Alex Gaynor
0f36d0c1ba Convert several SAN handling functions to use cryptography's APIs 2025-01-02 14:25:17 -05:00
Alex Gaynor
619da0432a Introduce a Format enum to help us migrate away from pyOpenSSL's constants
Begin using it in `dump_pyopenssl_chain`
2024-12-21 11:06:43 -05:00
Alex Gaynor
314838eb81 Convert some certbot-ci utilities to use cryptography's APIs (#10102) 2024-12-19 19:37:09 +00:00
Will Greenberg
25a1933e01 snap: disable FIPS detection (#10067)
This is needed because the Python + OpenSSL bundled in core24 don't
include an OpenSSL FIPS provider, which causes crashes on host systems
with OpenSSL 1.1.1f (e.g. Ubuntu Pro 20.04). For some reason, core24's
OpenSSL also looks in a non-standard location for the provider, which
also causes crashes on systems with OpenSSL 3.x (e.g. RHEL 9). If you
need FIPS functionality in certbot, install via pip.
2024-12-19 10:55:53 -08:00
Alex Gaynor
0f500e8010 Convert crypto_util_test.py to use cryptography's APIs (#10100) 2024-12-19 10:24:16 -08:00
Alex Gaynor
1afae838bb Convert validate_key_csr to use cryptography's APIs (#10099) 2024-12-19 07:11:47 -08:00
Alex Gaynor
724be8848a Convert http01_example.py to use cryptography's APIs (#10098)
Co-authored-by: ohemorange <ebportnoy@gmail.com>
2024-12-18 23:54:44 +00:00
Alex Gaynor
06ea141ca9 Convert make_key to use cryptography's APIs (#10091) 2024-12-18 15:10:20 -08:00
Mads Jensen
23245c07b2 Replace assert False with pytest.fail (#10094)
This seems to be better style. The assert False statements are
automatically removed by Python when running in the optimized mode,
which could hide test failures.

## Pull Request Checklist

- [ ] 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.

Co-authored-by: Mads Jensen <atombrella@users.noreply.github.com>
2024-12-18 14:44:05 -08:00
Will Greenberg
2d1d1cd534 Merge pull request #10089 from jvanasco/fix-migrate_to_cryptography
switch `cert_and_chain_from_fullchain` to cryptography APIs
2024-12-17 14:37:02 -08:00
Will Greenberg
5240e3cbf2 Merge pull request #10085 from atombrella/pyupgrade/up020_open_alias
Replace io.open with the built-in.
2024-12-17 14:35:08 -08:00
Will Greenberg
5fca4a14ab Merge pull request #10084 from atombrella/pyupgrade/up024_oserror
Replace aliased OSError.
2024-12-17 14:34:10 -08:00
Alex Gaynor
9be070414f Convert valid_csr and csr_matches_pubkey to use cryptography's APIs (#10088) 2024-12-17 09:22:22 -08:00
jonathan vanasco
761c268934 missed import level in port 2024-12-16 15:42:33 -05:00
jonathan vanasco
1fa110c9d7 added to authors 2024-12-16 15:30:42 -05:00
jonathan vanasco
9d1fccf53a switch cert_and_chain_from_fullchain to cryptography 2024-12-16 15:24:38 -05:00
Alex Gaynor
b16c64a05b Convert make_csr to use cryptography instead of pyOpenSSL (#10086)
These pyOpenSSL APIs are deprecated and we'd like to remove them.
2024-12-16 11:00:52 -08:00
Mads Jensen
88932da859 lint 2024-12-14 11:33:59 +01:00
Mads Jensen
8a69b2f1d9 Replace io.open with the built-in.
As of Python 3, io.open is an alias for the built-in open function.
2024-12-14 11:29:40 +01:00
Mads Jensen
57b5942fc3 Replace aliased OSError.
As of Python 3.3, various errors were merged into OSError.
https://docs.python.org/3/library/exceptions.html#OSError
2024-12-14 11:15:26 +01:00
Brad Warren
0f0000298b improve repinning (#10082)
this PR hopefully improves two things that i hit while working on #10035

1) i found that repinning our dependencies took ~6 minutes!

digging into it a bit, the biggest culprit i found was the inclusion of
`--no-cache-dir` here which seemed to cause poetry to redownload the
same packages over and over in a single run. this comes from
https://github.com/certbot/certbot/pull/9453 which fixed a problem i
(but not alex) was having with a major performance penalty. i removed
the flag here and instead included instructions on clearing poetry's
caches in case anyone ever hits this in the future. with this change,
the script now takes about 40 seconds on my laptop

2) every run of this script ended with the output:

    ```
Warning: poetry-plugin-export will not be installed by default in a
future version of Poetry.
In order to avoid a breaking change and make your automation
forward-compatible, please install poetry-plugin-export.
explicitly. See https://python-poetry.org/docs/plugins/#using-plugins
for details on how to install a plugin.
To disable this warning run 'poetry config warnings.export false'.
    ```

setting `POETRY_WARNINGS_EXPORT=false` fixes this which i believe is
safe to do because of
2c8609464c/certbot/setup.py (L53-L56)
2024-12-12 12:00:11 -08:00
Will Greenberg
c39fbe388c Merge pull request #10081 from certbot/no-windows-installer
remove the windows installer
2024-12-12 10:40:44 -08:00
Brad Warren
fc07f5f935 update pinnings 2024-12-12 08:57:10 -08:00
Brad Warren
9c8cdd05da remove the windows installer 2024-12-12 08:57:10 -08:00
Brad Warren
2c8609464c fix upgrading pyopenssl (#10080)
i hit this when working on https://github.com/certbot/certbot/pull/10076
where i found that updating all our dependencies no longer worked
because of new deprecations in pyopenssl. this pr fixes that
2024-12-11 15:15:55 -08:00
Brad Warren
7a48c235a9 remove importlib_resources (#10076)
this is part of my work on
https://github.com/certbot/certbot/issues/10035 based on erica's comment
at
https://github.com/certbot/certbot/issues/10035#issuecomment-2452212686
2024-12-06 12:37:17 -08:00
Mads Jensen
3f9387bd15 Fix F541 and E711 (#10071)
There are a quite a lot of imports that are unused.

F541 is Unnecessary f-interpolation without placeholders
E711 is incorrect use of == for boolean and None comparisons

## Pull Request Checklist

- [x] 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.
- [x] Include your name in `AUTHORS.md` if you like.

---------

Co-authored-by: Mads Jensen <atombrella@users.noreply.github.com>
2024-12-05 11:33:09 -08:00
Brad Warren
087cb4d1f4 remove python 3.8 support (#10077)
fixes https://github.com/certbot/certbot/issues/10035. you can compare
this to the PR that did this for python 3.7 at
https://github.com/certbot/certbot/pull/9792

i agree with erica's comment at
https://github.com/certbot/certbot/issues/10035#issuecomment-2452212686,
but felt this PR was already getting pretty large so i did that in a
second PR at https://github.com/certbot/certbot/pull/10076
2024-12-04 14:55:20 -08:00
Brad Warren
bcbc3dd484 Merge pull request #10075 from certbot/test-no-setuptools
remove setuptools dependency
2024-12-03 13:58:03 -08:00
Brad Warren
89737718c1 update documentation and pinnings 2024-12-03 11:25:02 -08:00
Harlan Lieberman-Berg
b0e389aad7 Drop setuptools as a runtime dependency
Because of the change from using setuptools.pkg_resources to using
importlib, we no longer need a runtime dependency on setuptools. It is
still required, however, for running setup.py.
2024-12-03 11:17:27 -08:00
Brad Warren
9f5451d16b update intersphinx mapping (#10074)
this hopefully fixes our nightly failures

readthedocs seems to redirect users to its .io site so
https://acme-python.readthedocs.org/en/latest/objects.inv is supposed to
redirect people to
https://acme-python.readthedocs.io/en/latest/objects.inv, but that
doesn't always seem to work and instead [sometimes serves a
403](https://dev.azure.com/certbot/certbot/_build/results?buildId=8237&view=logs&j=d74e04fe-9740-597d-e9fa-1d0400037dfd&t=dde413a4-f24c-59a0-9684-e33d79f9aa02&l=800)

removing the need for this redirect seems to fix things based on some
quick testing and certainly shouldn't hurt
2024-12-03 11:16:13 -08:00
Will Greenberg
5ada20cb74 Merge pull request #10068 from certbot/test-include-ssl-provider
stage SSL lib and set OPENSSL_MODULES
2024-11-22 13:22:09 -08:00
Brad Warren
ba256adcdb add changelog entry 2024-11-22 11:23:00 -08:00
Brad Warren
94adff7247 stage ssl lib and set OPENSSL_MODULES 2024-11-22 11:00:56 -08:00
Will Greenberg
06d6231d6d Merge pull request #10060 from certbot/candidate-3.0.1
update main from 3.0.1 release
2024-11-18 13:38:23 -08:00
Brad Warren
61da18cc47 Merge branch 'main' into candidate-3.0.1 2024-11-18 13:25:09 -08:00
Brad Warren
7ab421233e remove old python macos cover tests (#10063)
Co-authored-by: Will Greenberg <willg@eff.org>
2024-11-18 19:18:46 +00:00
Brad Warren
59f32c9d11 update docker image (#10057) 2024-11-14 13:37:08 -08:00
Brad Warren
2b57c5f03c Bump version to 3.1.0 2024-11-14 09:22:05 -08:00
Brad Warren
8f75af1e84 Add contents to certbot/CHANGELOG.md for next version 2024-11-14 09:22:05 -08:00
Brad Warren
3c9b936168 Remove built packages from git 2024-11-14 09:22:04 -08:00
Brad Warren
93294fc989 Release 3.0.1 2024-11-14 09:22:03 -08:00
Brad Warren
58a07ddd79 Update changelog for 3.0.1 release 2024-11-14 09:21:24 -08:00
Will Greenberg
933c1703b6 Remove built packages from git
(cherry picked from commit 990352e371)
2024-11-14 09:21:04 -08:00
Brad Warren
a44d739dd7 silence warning and add test (#10054) (#10058)
fixes https://github.com/certbot/certbot/issues/9967

actually fixing the underlying issue is being tracked by https://github.com/certbot/certbot/issues/10053

in addition to the unit test, i tested this manually. if you obtain any cert and run `certbot renew` in an up-to-date dev environment, there's 3 warnings when on `main` and none on this branch

(cherry picked from commit aa6ea3b513)
2024-11-14 09:07:06 -08:00
Brad Warren
58374867c8 Merge pull request #10056 from certbot/prep-for-3.0.1
prep for 3.0.1
2024-11-13 20:40:33 -08:00
Brad Warren
aa6ea3b513 silence warning and add test (#10054)
fixes https://github.com/certbot/certbot/issues/9967

actually fixing the underlying issue is being tracked by https://github.com/certbot/certbot/issues/10053

in addition to the unit test, i tested this manually. if you obtain any cert and run `certbot renew` in an up-to-date dev environment, there's 3 warnings when on `main` and none on this branch
2024-11-13 20:39:50 -08:00
ohemorange
a25ef72c4f escape backslashes in format string in finish_release.py (#10043)
(cherry picked from commit 38fc7fcc48)
2024-11-13 10:34:32 -08:00
ohemorange
396b6cce02 Fix release script main replacement (#10042)
* restore incorrect regex changes to CHANGELOG.md

* Update _release.sh regex to switch only first instance of main in changelog

(cherry picked from commit 0e225dcba2)
2024-11-13 10:34:26 -08:00
ohemorange
38fc7fcc48 escape backslashes in format string in finish_release.py (#10043) 2024-11-05 23:42:59 +00:00
ohemorange
0e225dcba2 Fix release script main replacement (#10042)
* restore incorrect regex changes to CHANGELOG.md

* Update _release.sh regex to switch only first instance of main in changelog
2024-11-05 14:55:23 -08:00
Brad Warren
4ff5719a65 Merge pull request #10039 from certbot/candidate-3.0.0
Candidate 3.0.0
2024-11-05 12:52:47 -08:00
Will Greenberg
798a61622c Bump version to 3.1.0 2024-11-05 10:55:20 -08:00
Will Greenberg
b20d01e032 Add contents to certbot/CHANGELOG.md for next version 2024-11-05 10:55:20 -08:00
Will Greenberg
990352e371 Remove built packages from git 2024-11-05 10:55:20 -08:00
Will Greenberg
c5a5d6f9a1 Release 3.0.0 2024-11-05 10:55:19 -08:00
Will Greenberg
d4850399c5 Update changelog for 3.0.0 release 2024-11-05 10:54:15 -08:00
Brad Warren
c4be440853 update dependencies (#10036)
this fixes the current [dependabot alert](https://github.com/certbot/certbot/security/dependabot)
2024-11-01 10:04:10 -07:00
Will Greenberg
165c3e32b0 snap: fix generated postrefreshhook script (#9994)
Fixes #9990

If the python oneliner to check certbot's version succeeded, exit_code
would never be set, which would cause our exit_code check to fail. Use
a check that handles unset exit_code
2024-11-01 08:03:57 -07:00
Will Greenberg
2660a2017b Certbot 3.0 outdated plugin warning (#10031)
* Print an error if outdated snap plugins detected

With Certbot 3.0 comes a bump to Python 3.12, so if any snap plugins
are still located in a python3.8 directory, print an error informing
the user.

* tox nitpicks

* personal nitpick

* review fixups

* Update certbot/certbot/_internal/snap_config.py

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

* Use LOGGER.warn instead of error

* warn-->warning

* warn-->warning

---------

Co-authored-by: ohemorange <ebportnoy@gmail.com>
2024-11-01 07:52:48 -07:00
ohemorange
6a6544fd90 Update azure standard tests to use macOS-15 and python3.12 (#10032)
macOS-12 is [being deprecated](https://github.com/actions/runner-images/issues/10721) on Azure, so update to the latest available version.

* Upgrade macOS azure tests to use macOS-15

* switch standard azure tests to using python 3.12

* restore mac and linux cover tests to oldest and newest version style, and add explanation that that's what we're doing.
2024-11-01 07:34:16 -07:00
Brad Warren
320cf92944 depecate py38 support (#10034) 2024-10-31 15:48:57 -07:00
Brad Warren
3078c2f3db remove reference to "good first issue" label (#10018) 2024-10-25 11:43:44 -07:00
Brad Warren
c54f99e35b mattermost/action-mattermost-notify still uses master (#10021) 2024-10-04 14:08:25 -07:00
Brad Warren
c81dbb2582 Make Docker builds more verbose (#10022)
* use consistent casing to fix warnings

* don't truncate docker build logs

* make docker build output verbose
2024-10-04 13:54:56 -07:00
Will Greenberg
742f97e11a docs: fix logo url (#10019) 2024-09-26 15:10:06 -07:00
Will Greenberg
84c8dbc52a 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:48: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
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
219 changed files with 2241 additions and 2800 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

@@ -1,9 +1,9 @@
# We run the test suite on commits to master so codecov gets coverage data
# about the master branch and can use it to track coverage changes.
# 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:
- master
- main
pr:
- master
- main
- '*.x'
variables:

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,7 +6,7 @@ schedules:
displayName: Nightly build
branches:
include:
- master
- main
always: true
variables:
@@ -15,5 +15,5 @@ variables:
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

@@ -13,7 +13,5 @@ variables:
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

@@ -72,3 +72,57 @@ jobs:
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

@@ -4,7 +4,7 @@ jobs:
- name: IMAGE_NAME
value: ubuntu-22.04
- name: PYTHON_VERSION
value: 3.11
value: 3.12
- group: certbot-common
strategy:
matrix:
@@ -14,43 +14,39 @@ jobs:
linux-py310:
PYTHON_VERSION: 3.10
TOXENV: py310
linux-py311:
PYTHON_VERSION: 3.11
TOXENV: py311
linux-isolated:
TOXENV: 'isolated-{acme,certbot,apache,cloudflare,digitalocean,dnsimple,dnsmadeeasy,gehirn,google,linode,luadns,nsone,ovh,rfc2136,route53,sakuracloud,nginx}'
linux-boulder-v2-integration-certbot-oldest:
PYTHON_VERSION: 3.8
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.9
TOXENV: integration-certbot-oldest
ACME_SERVER: boulder-v2
linux-boulder-v2-integration-nginx-oldest:
PYTHON_VERSION: 3.8
linux-integration-nginx-oldest:
PYTHON_VERSION: 3.9
TOXENV: integration-nginx-oldest
ACME_SERVER: boulder-v2
linux-boulder-v2-py38-integration:
PYTHON_VERSION: 3.8
TOXENV: integration
ACME_SERVER: boulder-v2
linux-boulder-v2-py39-integration:
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-boulder-v2-py311-integration:
linux-py311-integration:
PYTHON_VERSION: 3.11
TOXENV: integration
ACME_SERVER: boulder-v2
# python 3.12 integration tests are not run here because they're run as
# part of the standard test suite
nginx-compat:
TOXENV: nginx_compat
linux-integration-rfc2136:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
PYTHON_VERSION: 3.12
TOXENV: integration-dns-rfc2136
le-modification:
IMAGE_NAME: ubuntu-22.04
TOXENV: modification
farmtest-apache2:
PYTHON_VERSION: 3.8
PYTHON_VERSION: 3.12
TOXENV: test-farm-apache2
pool:
vmImage: $(IMAGE_NAME)

View File

@@ -55,65 +55,6 @@ jobs:
- bash: |
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\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\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-22.04
@@ -135,7 +76,7 @@ jobs:
displayName: Install dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
versionSpec: 3.12
addToPath: true
- task: DownloadSecureFile@1
name: credentials
@@ -166,7 +107,7 @@ jobs:
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
versionSpec: 3.12
addToPath: true
- script: |
set -e
@@ -186,7 +127,7 @@ 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
@@ -200,7 +141,7 @@ jobs:
displayName: Install dependencies
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
versionSpec: 3.12
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:

View File

@@ -1,42 +1,26 @@
jobs:
- job: test
variables:
PYTHON_VERSION: 3.11
PYTHON_VERSION: 3.12
strategy:
matrix:
macos-py38-cover:
IMAGE_NAME: macOS-12
PYTHON_VERSION: 3.8
macos-cover:
IMAGE_NAME: macOS-15
TOXENV: cover
# As of pip 23.1.0, builds started failing on macOS unless this flag was set.
# See https://github.com/certbot/certbot/pull/9717#issuecomment-1610861794.
PIP_USE_PEP517: "true"
macos-cover:
IMAGE_NAME: macOS-12
TOXENV: cover
# See explanation under macos-py38-cover.
PIP_USE_PEP517: "true"
windows-py38:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.8
TOXENV: py-win
windows-py39-cover:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.9
TOXENV: cover-win
windows-integration-certbot:
IMAGE_NAME: windows-2019
PYTHON_VERSION: 3.9
TOXENV: integration-certbot
linux-oldest:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
PYTHON_VERSION: 3.9
TOXENV: oldest
linux-py38:
linux-py39:
# linux unit tests with the oldest python we support
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: py38
PYTHON_VERSION: 3.9
TOXENV: py39
linux-cover:
# linux unit+cover tests with the newest python we support
IMAGE_NAME: ubuntu-22.04
TOXENV: cover
linux-lint:
@@ -47,9 +31,7 @@ jobs:
TOXENV: mypy
linux-integration:
IMAGE_NAME: ubuntu-22.04
PYTHON_VERSION: 3.8
TOXENV: integration
ACME_SERVER: pebble
apache-compat:
IMAGE_NAME: ubuntu-22.04
TOXENV: apache_compat
@@ -65,6 +47,6 @@ jobs:
- template: ../steps/tox-steps.yml
- job: test_sphinx_builds
pool:
vmImage: ubuntu-20.04
vmImage: ubuntu-22.04
steps:
- template: ../steps/sphinx-steps.yml

View File

@@ -1,67 +0,0 @@
parameters:
# We do not define acceptable values for this parameter here as it is passed
# through to ../jobs/snap-deploy-job.yml which does its own sanity checking.
- name: snapReleaseChannel
type: string
default: edge
stages:
- stage: Deploy
jobs:
- template: ../jobs/snap-deploy-job.yml
parameters:
snapReleaseChannel: ${{ parameters.snapReleaseChannel }}
# 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

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

@@ -44,7 +44,7 @@ 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
python3 -m tox
python3 -m tox run
env:
AWS_ACCESS_KEY_ID: $(AWS_ACCESS_KEY_ID)
AWS_SECRET_ACCESS_KEY: $(AWS_SECRET_ACCESS_KEY)

View File

@@ -1,6 +1,6 @@
## Pull Request Checklist
- [ ] 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 `master` section of `certbot/CHANGELOG.md` to include a description of the change being made.
- [ ] 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.

View File

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

View File

@@ -2,8 +2,9 @@ name: Weekly Github Update
on:
schedule:
# Every week on Thursday @ 13:00
- cron: "0 13 * * 4"
# Every week on Thursday @ 10:00
- cron: "0 10 * * 4"
workflow_dispatch:
jobs:
send-mattermost-message:
runs-on: ubuntu-latest
@@ -11,15 +12,14 @@ jobs:
steps:
- name: Create Mattermost Message
run: |
DATE=$(date --date="7 days ago" +"%Y-%m-%d")
MERGED_URL="https://github.com/pulls?q=merged%3A%3E${DATE}+org%3Acertbot"
UPDATED_URL="https://github.com/pulls?q=updated%3A%3E${DATE}+org%3Acertbot"
echo "{\"text\":\"## Updates Across Certbot Repos\n\n
- Certbot team members SHOULD look at: [link]($MERGED_URL)\n\n
- Certbot team members MAY also want to look at: [link]($UPDATED_URL)\n\n
- Want to Discuss something today? Place it [here](https://docs.google.com/document/d/17YMUbtC1yg6MfiTMwT8zVm9LmO-cuGVBom0qFn8XJBM/edit?usp=sharing) and we can meet today on Zoom.\n\n
- The key words SHOULD and MAY in this message are to be interpreted as described in [RFC 8147](https://www.rfc-editor.org/rfc/rfc8174). \"
}" > mattermost.json
DATE=$(date --date="7 days ago" +"%Y-%m-%d")
echo "ASSIGNED_PRS=https://github.com/pulls?q=is%3Apr+is%3Aopen+updated%3A%3E%3D${DATE}+assignee%3A*+user%3Acertbot" >> $GITHUB_ENV
echo "UPDATED_URL=https://github.com/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-desc+updated%3A%3E%3D${DATE}+user%3Acertbot" >> $GITHUB_ENV
- uses: mattermost/action-mattermost-notify@master
env:
with:
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK_URL }}
MATTERMOST_CHANNEL: private-certbot
TEXT: |
## Updates In the Past Week
- Most commented in the last week: [link](${{ env.UPDATED_URL }})
- Updated (assigned) PRs in the last week: [link](${{ env.ASSIGNED_PRS }})

View File

@@ -3,6 +3,7 @@ on:
schedule:
# Run 1:24AM every night
- cron: '24 1 * * *'
workflow_dispatch:
permissions:
issues: write
jobs:

View File

@@ -69,7 +69,7 @@ ignored-modules=
# 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())))"
# 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
@@ -266,8 +266,8 @@ valid-metaclass-classmethod-first-arg=cls
[EXCEPTIONS]
# Exceptions that will emit a warning when caught.
overgeneral-exceptions=BaseException,
Exception
overgeneral-exceptions=builtins.BaseException,
builtins.Exception
[FORMAT]
@@ -524,7 +524,7 @@ ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace,
# 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
ignored-modules=confargparse,argparse
# Show a hint with possible names when a member name was not found. The aspect
# of finding the hint is based on edit distance.

View File

@@ -94,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)
@@ -138,6 +139,7 @@ Authors
* [John Reed](https://github.com/leerspace)
* [Jonas Berlin](https://github.com/xkr47)
* [Jonathan Herlin](https://github.com/Jonher937)
* [Jonathan Vanasco](https://github.com/jvanasco)
* [Jon Walsh](https://github.com/code-tree)
* [Joona Hoikkala](https://github.com/joohoi)
* [Josh McCullough](https://github.com/JoshMcCullough)
@@ -165,6 +167,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)

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: acme/readthedocs.org.requirements.txt

View File

@@ -24,6 +24,7 @@ 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')
CSR_NO_SANS_PEM = test_util.load_vector('csr-nosans.pem')
KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem'))
DIRECTORY_V2 = messages.Directory({
@@ -97,6 +98,10 @@ class ClientV2Test(unittest.TestCase):
body=self.order,
uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1',
authorizations=[self.authzr, self.authzr2], csr_pem=CSR_MIXED_PEM)
self.orderr2 = messages.OrderResource(
body=self.order,
uri='https://www.letsencrypt-demo.org/acme/acct/1/order/1',
authorizations=[self.authzr, self.authzr2], csr_pem=CSR_NO_SANS_PEM)
def test_new_account(self):
self.response.status_code = http_client.CREATED
@@ -158,6 +163,10 @@ class ClientV2Test(unittest.TestCase):
mock_post_as_get.side_effect = (authz_response, authz_response2)
assert self.client.new_order(CSR_MIXED_PEM) == self.orderr
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_NO_SANS_PEM) == self.orderr2
def test_answer_challege(self):
self.response.links['up'] = {'url': self.challr.authzr_uri}
self.response.json.return_value = self.challr.body.to_json()

View File

@@ -12,11 +12,21 @@ import unittest
import josepy as jose
import OpenSSL
import pytest
from cryptography import x509
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa, x25519
from acme import errors
from acme._internal.tests import test_util
class FormatTest(unittest.TestCase):
def test_to_cryptography_encoding(self):
from acme.crypto_util import Format
assert Format.DER.to_cryptography_encoding() == serialization.Encoding.DER
assert Format.PEM.to_cryptography_encoding() == serialization.Encoding.PEM
class SSLSocketAndProbeSNITest(unittest.TestCase):
"""Tests for acme.crypto_util.SSLSocket/probe_sni."""
@@ -177,48 +187,6 @@ class PyOpenSSLCertOrReqSANTest(unittest.TestCase):
['chicago-cubs.venafi.example', 'cubs.venafi.example']
class PyOpenSSLCertOrReqSANIPTest(unittest.TestCase):
"""Test for acme.crypto_util._pyopenssl_cert_or_req_san_ip."""
@classmethod
def _call(cls, loader, name):
# pylint: disable=protected-access
from acme.crypto_util import _pyopenssl_cert_or_req_san_ip
return _pyopenssl_cert_or_req_san_ip(loader(name))
def _call_cert(self, name):
return self._call(test_util.load_cert, name)
def _call_csr(self, name):
return self._call(test_util.load_csr, name)
def test_cert_no_sans(self):
assert self._call_cert('cert.pem') == []
def test_csr_no_sans(self):
assert self._call_csr('csr-nosans.pem') == []
def test_cert_domain_sans(self):
assert self._call_cert('cert-san.pem') == []
def test_csr_domain_sans(self):
assert self._call_csr('csr-san.pem') == []
def test_cert_ip_two_sans(self):
assert self._call_cert('cert-ipsans.pem') == ['192.0.2.145', '203.0.113.1']
def test_csr_ip_two_sans(self):
assert self._call_csr('csr-ipsans.pem') == ['192.0.2.145', '203.0.113.1']
def test_csr_ipv6_sans(self):
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):
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):
"""Test for gen_ss_cert (generation of self-signed cert)."""
@@ -250,79 +218,74 @@ class MakeCSRTest(unittest.TestCase):
@classmethod
def _call_with_key(cls, *args, **kwargs):
privkey = OpenSSL.crypto.PKey()
privkey.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)
privkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, privkey)
privkey = rsa.generate_private_key(public_exponent=65537, key_size=2048)
privkey_pem = privkey.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption(),
)
from acme.crypto_util import make_csr
return make_csr(privkey_pem, *args, **kwargs)
def test_make_csr(self):
csr_pem = self._call_with_key(["a.example", "b.example"])
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'):
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()
assert b"--BEGIN CERTIFICATE REQUEST--" in csr_pem
assert b"--END CERTIFICATE REQUEST--" in csr_pem
csr = x509.load_pem_x509_csr(csr_pem)
assert len(csr.extensions) == 1
assert list(
csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
) == [
x509.DNSName("a.example"),
x509.DNSName("b.example"),
]
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')])
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'):
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()
# for IP san it's actually need to be octet-string,
# but somewhere downstream thankfully handle it for us
csr_pem = self._call_with_key(
["a.example"],
False,
[ipaddress.ip_address("127.0.0.1"), ipaddress.ip_address("::1")],
)
assert b"--BEGIN CERTIFICATE REQUEST--" in csr_pem
assert b"--END CERTIFICATE REQUEST--" in csr_pem
csr = x509.load_pem_x509_csr(csr_pem)
assert len(csr.extensions) == 1
assert list(
csr.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
) == [
x509.DNSName("a.example"),
x509.IPAddress(ipaddress.ip_address("127.0.0.1")),
x509.IPAddress(ipaddress.ip_address("::1")),
]
def test_make_csr_must_staple(self):
csr_pem = self._call_with_key(["a.example"], must_staple=True)
csr = OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr_pem)
csr = x509.load_pem_x509_csr(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'):
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"]
assert len(must_staple_exts) == 1, \
"Expected exactly one Must Staple extension"
assert len(csr.extensions) == 2
assert list(csr.extensions.get_extension_for_class(x509.TLSFeature).value) == [
x509.TLSFeatureType.status_request
]
def test_make_csr_without_hostname(self):
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)
def test_make_csr_invalid_key_type(self):
privkey = x25519.X25519PrivateKey.generate()
privkey_pem = privkey.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption(),
)
from acme.crypto_util import make_csr
assert csr.get_version() == 0, \
"Expected CSR version to be v1 (encoded as 0), per RFC 2986, section 4"
with pytest.raises(ValueError):
make_csr(privkey_pem, ["a.example"])
class DumpPyopensslChainTest(unittest.TestCase):

View File

@@ -21,7 +21,7 @@ class HeaderTest(unittest.TestCase):
except (ValueError, TypeError):
assert True
else:
assert False # pragma: no cover
pytest.fail("Exception from jose.b64decode wasn't raised") # pragma: no cover
def test_nonce_decoder(self):
from acme.jws import Header

View File

@@ -193,7 +193,7 @@ class BaseDualNetworkedServersTest(unittest.TestCase):
from acme.standalone import BaseDualNetworkedServers
mock_bind.side_effect = socket.error(EADDRINUSE, "Fake addr in use error")
mock_bind.side_effect = OSError(EADDRINUSE, "Fake addr in use error")
with pytest.raises(socket.error) as exc_info:
BaseDualNetworkedServers(

View File

@@ -3,6 +3,7 @@
.. warning:: This module is not part of the public API.
"""
import importlib.resources
import os
import sys
@@ -12,16 +13,11 @@ import josepy as jose
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
vector_ref = importlib_resources.files(__package__).joinpath('testdata', *names)
vector_ref = importlib.resources.files(__package__).joinpath('testdata', *names)
return vector_ref.read_bytes()

View File

@@ -1,16 +1,16 @@
-----BEGIN CERTIFICATE REQUEST-----
MIICdjCCAV4CAQIwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMXq
v1y8EIcCbaUIzCtOcLkLS0MJ35oS+6DmV5WB1A0cIk6YrjsHIsY2lwMm13BWIvmw
tY+Y6n0rr7eViNx5ZRGHpHEI/TL3Neb+VefTydL5CgvK3dd4ex2kSbTaed3fmpOx
qMajEduwNcZPCcmoEXPkfrCP8w2vKQUkQ+JRPcdX1nTuzticeRP5B7YCmJsmxkEh
Y0tzzZ+NIRDARoYNofefY86h3e5q66gtJxccNchmIM3YQahhg5n3Xoo8hGfM/TIc
R7ncCBCLO6vtqo0QFva/NQODrgOmOsmgvqPkUWQFdZfWM8yIaU826dktx0CPB78t
TudnJ1rBRvGsjHMsZikCAwEAAaAxMC8GCSqGSIb3DQEJDjEiMCAwHgYDVR0RBBcw
FYINYS5leGVtcGxlLmNvbYcEwAACbzANBgkqhkiG9w0BAQsFAAOCAQEAdGMcRCxq
1X09gn1TNdMt64XUv+wdJCKDaJ+AgyIJj7QvVw8H5k7dOnxS4I+a/yo4jE+LDl2/
AuHcBLFEI4ddewdJSMrTNZjuRYuOdr3KP7fL7MffICSBi45vw5EOXg0tnjJCEiKu
6gcJgbLSP5JMMd7Haf33Q/VWsmHofR3VwOMdrnakwAU3Ff5WTuXTNVhL1kT/uLFX
yW1ru6BF4unwNqSR2UeulljpNfRBsiN4zJK11W6n9KT0NkBr9zY5WCM4sW7i8k9V
TeypWGo3jBKzYAGeuxZsB97U77jZ2lrGdBLZKfbcjnTeRVqCvCRrui4El7UGYFmj
7s6OJyWx5DSV8w==
MIICdjCCAV4CAQAwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANoV
T1pdvRUUBOqvm7M2ebLEHV7higUH7qAGUZEkfP6W4YriYVY+IHrH1svNPSa+oPTK
7weDNmT11ehWnGyECIM9z2r2Hi9yVV0ycxh4hWQ4Nt8BAKZwCwaXpyWm7Gj6m2Ez
pSN5Dd67g5YAQBrUUh1+RRbFi9c0Ls/6ZOExMvfg8kqt4c2sXCgH1IFnxvvOjBYo
p7xh0x3L1Akyax0tw8qgQp/z5mkupmVDNJYPFmbzFPMNyDR61ed6QUTDg7P4UAuF
kejLLzFvz5YaO7vC+huaTuPhInAhpzqpr4yU97KIjos2/83Itu/Cv8U1RAeEeRTk
h0WjUfltoem/5f8bIdsCAwEAAaAxMC8GCSqGSIb3DQEJDjEiMCAwHgYDVR0RBBcw
FYINYS5leGVtcGxlLmNvbYcEwAACbzANBgkqhkiG9w0BAQsFAAOCAQEAQ7n/hYen
5INHlcslHPYCQ/BAbX6Ou+Y8hUu8puWNVpE2OM95L2C87jbWwTmCRnkFBwtyoNqo
j3DXVW2RYv8y/exq7V6Y5LtpHTgwfugINJ3XlcVzA4Vnf1xqOxv3kwejkq74RuXn
xd5N28srgiFqb0e4tOAWVI8Tw27bgBqjoXl0QDFPZpctqUia5bcDJ9WzNSM7VaO1
CBNGHBRz+zL8sqoqJA4HV58tjcgzl+1RtGM+iUHxXpnH+aCNKWIUINrAzIm4Sm00
93RJjhb1kdNR0BC7ikWVbAWaVviHdvATK/RfpmhWDqfEaNgBpvT91GnkhpzctSFD
ro0yCUUXXrIr0w==
-----END CERTIFICATE REQUEST-----

View File

@@ -15,6 +15,7 @@ from typing import Type
from typing import TypeVar
from typing import Union
from cryptography import x509
from cryptography.hazmat.primitives import hashes
import josepy as jose
from OpenSSL import crypto
@@ -89,7 +90,7 @@ class _TokenChallenge(Challenge):
:ivar bytes token:
"""
TOKEN_SIZE = 128 / 8 # Based on the entropy value from the spec
TOKEN_SIZE = 128 // 8 # Based on the entropy value from the spec
"""Minimum size of the :attr:`token` in bytes."""
# TODO: acme-spec doesn't specify token as base64-encoded value
@@ -460,34 +461,39 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse):
return crypto_util.probe_sni(host=host.encode(), port=port, name=domain.encode(),
alpn_protocols=[self.ACME_TLS_1_PROTOCOL])
def verify_cert(self, domain: str, cert: crypto.X509) -> bool:
def verify_cert(self, domain: str, cert: Union[x509.Certificate, crypto.X509]) -> bool:
"""Verify tls-alpn-01 challenge certificate.
:param str domain: Domain name being validated.
:param OpensSSL.crypto.X509 cert: Challenge certificate.
:param cert: Challenge certificate.
:type cert: `cryptography.x509.Certificate` or `OpenSSL.crypto.X509`
:returns: Whether the certificate was successfully verified.
:rtype: bool
"""
# pylint: disable=protected-access
names = crypto_util._pyopenssl_cert_or_req_all_names(cert)
# Type ignore needed due to
# https://github.com/pyca/pyopenssl/issues/730.
logger.debug('Certificate %s. SANs: %s',
cert.digest('sha256'), names)
if not isinstance(cert, x509.Certificate):
cert = cert.to_cryptography()
names = crypto_util.get_names_from_subject_and_extensions(
cert.subject, cert.extensions
)
logger.debug(
"Certificate %s. SANs: %s", cert.fingerprint(hashes.SHA256()), names
)
if len(names) != 1 or names[0].lower() != domain.lower():
return False
for i in range(cert.get_extension_count()):
ext = cert.get_extension(i)
# FIXME: assume this is the ACME extension. Currently there is no
# way to get full OID of an unknown extension from pyopenssl.
if ext.get_short_name() == b'UNDEF':
data = ext.get_data()
return data == self.h
try:
ext = cert.extensions.get_extension_for_oid(
x509.ObjectIdentifier(self.ID_PE_ACME_IDENTIFIER_V1.decode())
)
except x509.ExtensionNotFound:
return False
return False
# This is for the type checker.
assert isinstance(ext.value, x509.UnrecognizedExtension)
return ext.value.value == self.h
# pylint: disable=too-many-arguments
def simple_verify(self, chall: 'TLSALPN01', domain: str, account_public_key: jose.JWK,

View File

@@ -12,10 +12,11 @@ from typing import List
from typing import Mapping
from typing import Optional
from typing import Set
from typing import Text
from typing import Tuple
from typing import Union
from cryptography import x509
import josepy as jose
import OpenSSL
import requests
@@ -122,18 +123,21 @@ class ClientV2:
:returns: The newly created order.
:rtype: OrderResource
"""
csr = OpenSSL.crypto.load_certificate_request(OpenSSL.crypto.FILETYPE_PEM, csr_pem)
# pylint: disable=protected-access
dnsNames = crypto_util._pyopenssl_cert_or_req_all_names(csr)
ipNames = crypto_util._pyopenssl_cert_or_req_san_ip(csr)
# ipNames is now []string
csr = x509.load_pem_x509_csr(csr_pem)
dnsNames = crypto_util.get_names_from_subject_and_extensions(csr.subject, csr.extensions)
try:
san_ext = csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
except x509.ExtensionNotFound:
ipNames = []
else:
ipNames = san_ext.value.get_values_for_type(x509.IPAddress)
identifiers = []
for name in dnsNames:
identifiers.append(messages.Identifier(typ=messages.IDENTIFIER_FQDN,
value=name))
for ips in ipNames:
for ip in ipNames:
identifiers.append(messages.Identifier(typ=messages.IDENTIFIER_IP,
value=ips))
value=str(ip)))
order = messages.NewOrder(identifiers=identifiers)
response = self._post(self.directory['newOrder'], order)
body = messages.Order.from_json(response.json())
@@ -517,7 +521,7 @@ class ClientNetwork:
self.account = account
self.alg = alg
self.verify_ssl = verify_ssl
self._nonces: Set[Text] = set()
self._nonces: Set[str] = set()
self.user_agent = user_agent
self.session = requests.Session()
self._default_timeout = timeout

View File

@@ -1,11 +1,12 @@
"""Crypto utilities."""
import binascii
import contextlib
import enum
import ipaddress
import logging
import os
import re
import socket
import typing
from typing import Any
from typing import Callable
from typing import List
@@ -16,6 +17,9 @@ from typing import Set
from typing import Tuple
from typing import Union
from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import dsa, rsa, ec, ed25519, ed448
import josepy as jose
from OpenSSL import crypto
from OpenSSL import SSL
@@ -34,6 +38,24 @@ logger = logging.getLogger(__name__)
_DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD
class Format(enum.IntEnum):
"""File format to be used when parsing or serializing X.509 structures.
Backwards compatible with the `FILETYPE_ASN1` and `FILETYPE_PEM` constants
from pyOpenSSL.
"""
DER = crypto.FILETYPE_ASN1
PEM = crypto.FILETYPE_PEM
def to_cryptography_encoding(self) -> serialization.Encoding:
"""Converts the Format to the corresponding cryptography `Encoding`.
"""
if self == Format.DER:
return serialization.Encoding.DER
else:
return serialization.Encoding.PEM
class _DefaultCertSelection:
def __init__(self, certs: Mapping[bytes, Tuple[crypto.PKey, crypto.X509]]):
self.certs = certs
@@ -73,13 +95,9 @@ class SSLSocket: # pylint: disable=too-few-public-methods
raise ValueError("Neither cert_selection or certs specified.")
if cert_selection and certs:
raise ValueError("Both cert_selection and certs specified.")
actual_cert_selection: Union[_DefaultCertSelection,
Optional[Callable[[SSL.Connection],
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
if cert_selection is None:
cert_selection = _DefaultCertSelection(certs if certs else {})
self.cert_selection = cert_selection
def __getattr__(self, name: str) -> Any:
return getattr(self.sock, name)
@@ -131,7 +149,7 @@ class SSLSocket: # pylint: disable=too-few-public-methods
# 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)
raise OSError(error)
def accept(self) -> Tuple[FakeConnection, Any]: # pylint: disable=missing-function-docstring
sock, addr = self.sock.accept()
@@ -155,7 +173,7 @@ class SSLSocket: # pylint: disable=too-few-public-methods
except SSL.Error as error:
# _pick_certificate_cb might have returned without
# creating SSL context (wrong server name)
raise socket.error(error)
raise OSError(error)
return ssl_sock, addr
except:
@@ -203,7 +221,7 @@ def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, #
)
socket_tuple: Tuple[bytes, int] = (host, port)
sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore[arg-type]
except socket.error as error:
except OSError as error:
raise errors.Error(error)
with contextlib.closing(sock) as client:
@@ -222,77 +240,118 @@ def probe_sni(name: bytes, host: bytes, port: int = 443, timeout: int = 300, #
return cert
def make_csr(private_key_pem: bytes, domains: Optional[Union[Set[str], List[str]]] = None,
must_staple: bool = False,
ipaddrs: Optional[List[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]] = None
) -> bytes:
def make_csr(
private_key_pem: bytes,
domains: Optional[Union[Set[str], List[str]]] = None,
must_staple: bool = False,
ipaddrs: Optional[List[Union[ipaddress.IPv4Address, ipaddress.IPv6Address]]] = None,
) -> bytes:
"""Generate a CSR containing domains or IPs as subjectAltNames.
Parameters are ordered this way for backwards compatibility when called using positional
arguments.
:param buffer private_key_pem: Private key, in PEM PKCS#8 format.
:param list domains: List of DNS names to include in subjectAltNames of CSR.
:param bool must_staple: Whether to include the TLS Feature extension (aka
OCSP Must Staple: https://tools.ietf.org/html/rfc7633).
:param list ipaddrs: List of IPaddress(type ipaddress.IPv4Address or ipaddress.IPv6Address)
names to include in subbjectAltNames of CSR.
params ordered this way for backward competablity when called by positional argument.
names to include in subbjectAltNames of CSR.
:returns: buffer PEM-encoded Certificate Signing Request.
"""
private_key = crypto.load_privatekey(
crypto.FILETYPE_PEM, private_key_pem)
csr = crypto.X509Req()
sanlist = []
# if domain or ip list not supplied make it empty list so it's easier to iterate
private_key = serialization.load_pem_private_key(private_key_pem, password=None)
# There are a few things that aren't valid for x509 signing. mypy
# complains if we don't check.
if not isinstance(
private_key,
(
dsa.DSAPrivateKey,
rsa.RSAPrivateKey,
ec.EllipticCurvePrivateKey,
ed25519.Ed25519PrivateKey,
ed448.Ed448PrivateKey,
),
):
raise ValueError(f"Invalid private key type: {type(private_key)}")
if domains is None:
domains = []
if ipaddrs is None:
ipaddrs = []
if len(domains)+len(ipaddrs) == 0:
raise ValueError("At least one of domains or ipaddrs parameter need to be not empty")
for address in domains:
sanlist.append('DNS:' + address)
for ips in ipaddrs:
sanlist.append('IP:' + ips.exploded)
# make sure its ascii encoded
san_string = ', '.join(sanlist).encode('ascii')
# for IP san it's actually need to be octet-string,
# but somewhere downsteam thankfully handle it for us
extensions = [
crypto.X509Extension(
b'subjectAltName',
if len(domains) + len(ipaddrs) == 0:
raise ValueError(
"At least one of domains or ipaddrs parameter need to be not empty"
)
builder = (
x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([]))
.add_extension(
x509.SubjectAlternativeName(
[x509.DNSName(d) for d in domains]
+ [x509.IPAddress(i) for i in ipaddrs]
),
critical=False,
value=san_string
),
]
)
)
if must_staple:
extensions.append(crypto.X509Extension(
b"1.3.6.1.5.5.7.1.24",
builder = builder.add_extension(
# "status_request" is the feature commonly known as OCSP
# Must-Staple
x509.TLSFeature([x509.TLSFeatureType.status_request]),
critical=False,
value=b"DER:30:03:02:01:05"))
csr.add_extensions(extensions)
csr.set_pubkey(private_key)
# 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)
)
csr = builder.sign(private_key, hashes.SHA256())
return csr.public_bytes(serialization.Encoding.PEM)
def get_names_from_subject_and_extensions(
subject: x509.Name, exts: x509.Extensions
) -> List[str]:
"""Gets all DNS SAN names as well as the first Common Name from subject.
:param subject: Name of the x509 object, which may include Common Name
:type subject: `cryptography.x509.Name`
:param exts: Extensions of the x509 object, which may include SANs
:type exts: `cryptography.x509.Extensions`
:returns: List of DNS Subject Alternative Names and first Common Name
:rtype: `list` of `str`
"""
# We know these are always `str` because `bytes` is only possible for
# other OIDs.
cns = [
typing.cast(str, c.value)
for c in subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
]
try:
san_ext = exts.get_extension_for_class(x509.SubjectAlternativeName)
except x509.ExtensionNotFound:
dns_names = []
else:
dns_names = san_ext.value.get_values_for_type(x509.DNSName)
if not cns:
return dns_names
else:
# We only include the first CN, if there are multiple. This matches
# the behavior of the previously implementation using pyOpenSSL.
return [cns[0]] + [d for d in dns_names if d != cns[0]]
def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req: Union[crypto.X509, crypto.X509Req]
) -> List[str]:
# unlike its name this only outputs DNS names, other type of idents will ignored
common_name = loaded_cert_or_req.get_subject().CN
sans = _pyopenssl_cert_or_req_san(loaded_cert_or_req)
if common_name is None:
return sans
return [common_name] + [d for d in sans if d != common_name]
cert_or_req = loaded_cert_or_req.to_cryptography()
return get_names_from_subject_and_extensions(
cert_or_req.subject, cert_or_req.extensions
)
def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, crypto.X509Req]) -> List[str]:
"""Get Subject Alternative Names from certificate or CSR using pyOpenSSL.
.. todo:: Implement directly in PyOpenSSL!
.. note:: Although this is `acme` internal API, it is used by
`letsencrypt`.
@@ -303,67 +362,13 @@ def _pyopenssl_cert_or_req_san(cert_or_req: Union[crypto.X509, crypto.X509Req])
:rtype: `list` of `str`
"""
# This function finds SANs with dns name
exts = cert_or_req.to_cryptography().extensions
try:
san_ext = exts.get_extension_for_class(x509.SubjectAlternativeName)
except x509.ExtensionNotFound:
return []
# constants based on PyOpenSSL certificate/CSR text dump
part_separator = ":"
prefix = "DNS" + part_separator
sans_parts = _pyopenssl_extract_san_list_raw(cert_or_req)
return [part.split(part_separator)[1]
for part in sans_parts if part.startswith(prefix)]
def _pyopenssl_cert_or_req_san_ip(cert_or_req: Union[crypto.X509, crypto.X509Req]) -> List[str]:
"""Get Subject Alternative Names IPs from certificate or CSR using pyOpenSSL.
:param cert_or_req: Certificate or CSR.
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
:returns: A list of Subject Alternative Names that are IP Addresses.
:rtype: `list` of `str`. note that this returns as string, not IPaddress object
"""
# constants based on PyOpenSSL certificate/CSR text dump
part_separator = ":"
prefix = "IP Address" + part_separator
sans_parts = _pyopenssl_extract_san_list_raw(cert_or_req)
return [part[len(prefix):] for part in sans_parts if part.startswith(prefix)]
def _pyopenssl_extract_san_list_raw(cert_or_req: Union[crypto.X509, crypto.X509Req]) -> List[str]:
"""Get raw SAN string from cert or csr, parse it as UTF-8 and return.
:param cert_or_req: Certificate or CSR.
:type cert_or_req: `OpenSSL.crypto.X509` or `OpenSSL.crypto.X509Req`.
:returns: raw san strings, parsed byte as utf-8
:rtype: `list` of `str`
"""
# This function finds SANs by dumping the certificate/CSR to text and
# searching for "X509v3 Subject Alternative Name" in the text. This method
# is used to because in PyOpenSSL version <0.17 `_subjectAltNameString` methods are
# not able to Parse IP Addresses in subjectAltName string.
if isinstance(cert_or_req, crypto.X509):
# pylint: disable=line-too-long
text = crypto.dump_certificate(crypto.FILETYPE_TEXT, cert_or_req).decode('utf-8')
else:
text = crypto.dump_certificate_request(crypto.FILETYPE_TEXT, cert_or_req).decode('utf-8')
# WARNING: this function does not support multiple SANs extensions.
# Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
raw_san = re.search(r"X509v3 Subject Alternative Name:(?: critical)?\s*(.*)", text)
parts_separator = ", "
# WARNING: this function assumes that no SAN can include
# parts_separator, hence the split!
sans_parts = [] if raw_san is None else raw_san.group(1).split(parts_separator)
return sans_parts
return san_ext.value.get_values_for_type(x509.DNSName)
def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
@@ -433,7 +438,7 @@ def gen_ss_cert(key: crypto.PKey, domains: Optional[List[str]] = None,
def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X509]],
filetype: int = crypto.FILETYPE_PEM) -> bytes:
filetype: Union[Format, int] = Format.PEM) -> bytes:
"""Dump certificate chain into a bundle.
:param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
@@ -446,12 +451,14 @@ def dump_pyopenssl_chain(chain: Union[List[jose.ComparableX509], List[crypto.X50
# XXX: returns empty string when no chain is available, which
# shuts up RenewableCert, but might not be the best solution...
filetype = Format(filetype)
def _dump_cert(cert: Union[jose.ComparableX509, crypto.X509]) -> bytes:
if isinstance(cert, jose.ComparableX509):
if isinstance(cert.wrapped, crypto.X509Req):
raise errors.Error("Unexpected CSR provided.") # pragma: no cover
cert = cert.wrapped
return crypto.dump_certificate(filetype, cert)
return cert.to_cryptography().public_bytes(filetype.to_cryptography_encoding())
# assumes that OpenSSL.crypto.dump_certificate includes ending
# newline character

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

@@ -98,7 +98,7 @@ class BaseDualNetworkedServers:
logger.debug(
"Successfully bound to %s:%s using %s", new_address[0],
new_address[1], "IPv6" if ip_version else "IPv4")
except socket.error as e:
except OSError as e:
last_socket_err = e
if self.servers:
# Already bound using IPv6.
@@ -121,7 +121,7 @@ class BaseDualNetworkedServers:
if last_socket_err:
raise last_socket_err
else: # pragma: no cover
raise socket.error("Could not bind to IPv4 or IPv6.")
raise OSError("Could not bind to IPv4 or IPv6.")
def serve_forever(self) -> None:
"""Wraps socketserver.TCPServer.serve_forever"""

View File

@@ -0,0 +1,5 @@
Crypto_util
-----------
.. automodule:: acme.crypto_util
:members:

5
acme/docs/api/jws.rst Normal file
View File

@@ -0,0 +1,5 @@
JWS
---
.. automodule:: acme.jws
:members:

5
acme/docs/api/util.rst Normal file
View File

@@ -0,0 +1,5 @@
Util
----
.. automodule:: acme.util
:members:

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

@@ -28,6 +28,7 @@ Workflow:
from contextlib import contextmanager
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import josepy as jose
import OpenSSL
@@ -68,10 +69,9 @@ def new_csr_comp(domain_name, pkey_pem=None):
"""Create certificate signing request."""
if pkey_pem is None:
# Create private key.
pkey = OpenSSL.crypto.PKey()
pkey.generate_key(OpenSSL.crypto.TYPE_RSA, CERT_PKEY_BITS)
pkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM,
pkey)
pkey = rsa.generate_private_key(public_exponent=65537, key_size=CERT_PKEY_BITS)
pkey_pem = pkey.public_bytes(serialization.Encoding.PEM)
csr_pem = crypto_util.make_csr(pkey_pem, [domain_name])
return pkey_pem, csr_pem
@@ -201,8 +201,10 @@ def example_http():
# Revoke certificate
fullchain_com = jose.ComparableX509(
OpenSSL.crypto.load_certificate(
OpenSSL.crypto.FILETYPE_PEM, fullchain_pem))
OpenSSL.crypto.X509.from_cryptography(
x509.load_pem_x509_certificate(fullchain_pem)
)
)
try:
client_acme.revoke(fullchain_com, 0) # revocation reason = 0

View File

@@ -3,17 +3,18 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'cryptography>=3.2.1',
'josepy>=1.13.0',
# 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',
'setuptools>=41.6.0',
]
docs_extras = [
@@ -22,15 +23,6 @@ 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',
@@ -44,17 +36,17 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
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.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',
],

View File

@@ -2,10 +2,10 @@
import atexit
import binascii
import fnmatch
import importlib.resources
import logging
import re
import subprocess
import sys
from contextlib import ExitStack
from typing import Dict
from typing import Iterable
@@ -17,12 +17,6 @@ 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__)
@@ -257,6 +251,6 @@ def find_ssl_apache_conf(prefix: str) -> str:
"""
file_manager = ExitStack()
atexit.register(file_manager.close)
ref = importlib_resources.files("certbot_apache").joinpath(
"_internal", "tls_configs", "{0}-options-ssl-apache.conf".format(prefix))
return str(file_manager.enter_context(importlib_resources.as_file(ref)))
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

@@ -295,7 +295,7 @@ class ApacheConfigurator(common.Configurator):
try:
with open(ssl_module_location, mode="rb") as f:
contents = f.read()
except IOError as error:
except OSError as error:
logger.debug(str(error), exc_info=True)
return None
return contents
@@ -928,7 +928,7 @@ class ApacheConfigurator(common.Configurator):
try:
socket.inet_aton(addr.get_addr())
return socket.gethostbyaddr(addr.get_addr())[0]
except (socket.error, socket.herror, socket.timeout):
except (OSError, socket.herror, socket.timeout):
pass
return ""
@@ -1523,7 +1523,7 @@ class ApacheConfigurator(common.Configurator):
# activation (it's not included as default)
if not self.parser.parsed_in_current(ssl_fp):
self.parser.parse_file(ssl_fp)
except IOError:
except OSError:
logger.critical("Error writing/reading to file in make_vhost_ssl", exc_info=True)
raise errors.PluginError("Unable to write/read in make_vhost_ssl")

View File

@@ -1,14 +1,10 @@
"""Apache plugin constants."""
import atexit
import sys
import importlib.resources
from contextlib import ExitStack
from typing import Dict
from typing import List
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
@@ -46,8 +42,8 @@ def _generate_augeas_lens_dir_static() -> str:
# 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_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"""

View File

@@ -4,6 +4,7 @@ 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
@@ -14,6 +15,7 @@ from certbot_apache._internal import override_suse
from certbot_apache._internal import override_void
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

@@ -0,0 +1,19 @@
""" Distribution specific override class for Alpine Linux """
from certbot_apache._internal import configurator
from certbot_apache._internal.configurator import OsOptions
class AlpineConfigurator(configurator.ApacheConfigurator):
"""Alpine Linux specific ApacheConfigurator override class"""
OS_DEFAULTS = OsOptions(
server_root="/etc/apache2",
vhost_root="/etc/apache2/conf.d",
vhost_files="*.conf",
logs_root="/var/log/apache2",
ctl="apachectl",
version_cmd=['apachectl', '-v'],
restart_cmd=['apachectl', 'graceful'],
conftest_cmd=['apachectl', 'configtest'],
challenge_location="/etc/apache2/conf.d",
)

View File

@@ -153,7 +153,7 @@ class ApacheParser:
try:
# This is a noop save
self.aug.save()
except (RuntimeError, IOError):
except (OSError, RuntimeError):
self._log_save_errors(ex_errs)
# Erase Save Notes
self.configurator.save_notes = ""
@@ -198,7 +198,7 @@ class ApacheParser:
ex_errs = self.aug.match("/augeas//error")
try:
self.aug.save()
except IOError:
except OSError:
self._log_save_errors(ex_errs)
raise

View File

@@ -14,7 +14,7 @@ SCRIPT_DIRNAME = os.path.dirname(__file__)
def main() -> int:
args = sys.argv[1:]
with acme_server.ACMEServer('pebble', [], False) as acme_xdist:
with acme_server.ACMEServer([], False) as acme_xdist:
environ = os.environ.copy()
environ['SERVER'] = acme_xdist['directory_url']
command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')]

View File

@@ -174,9 +174,9 @@ class MultipleVhostsTest(util.ApacheTest):
assert "certbot.demo" in names
def test_get_bad_path(self):
assert apache_util.get_file_path(None) == None
assert apache_util.get_file_path("nonexistent") == None
assert self.config._create_vhost("nonexistent") == None # pylint: disable=protected-access
assert apache_util.get_file_path(None) is None
assert apache_util.get_file_path("nonexistent") is None
assert self.config._create_vhost("nonexistent") is None # pylint: disable=protected-access
def test_get_aug_internal_path(self):
from certbot_apache._internal.apache_util import get_internal_aug_path
@@ -303,7 +303,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
assert self.vh_truth[3] == self.config._find_best_vhost("certbot.demo")
assert self.vh_truth[0] == self.config._find_best_vhost("encryption-example.demo")
assert self.config._find_best_vhost("does-not-exist.com") == None
assert self.config._find_best_vhost("does-not-exist.com") is None
def test_find_best_vhost_variety(self):
# pylint: disable=protected-access
@@ -1417,7 +1417,7 @@ class AugeasVhostsTest(util.ApacheTest):
self.config.parser.aug.match.side_effect = RuntimeError
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
chosen_vhost = self.config._create_vhost(path)
assert None == chosen_vhost
assert chosen_vhost is None
def test_choosevhost_works(self):
path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf"
@@ -1518,16 +1518,11 @@ class MultiVhostsTest(util.ApacheTest):
with_index_1 = ["/path[1]/section[1]"]
without_index = ["/path/section"]
with_index_2 = ["/path[2]/section[2]"]
assert self.config._get_new_vh_path(without_index,
with_index_1) == \
None
assert self.config._get_new_vh_path(without_index,
with_index_2) == \
with_index_2[0]
assert self.config._get_new_vh_path(without_index, with_index_1) is None
assert self.config._get_new_vh_path(without_index, with_index_2) == with_index_2[0]
both = with_index_1 + with_index_2
assert self.config._get_new_vh_path(without_index, both) == \
with_index_2[0]
assert self.config._get_new_vh_path(without_index, both) == with_index_2[0]
@mock.patch("certbot_apache._internal.configurator.display_util.notify")
def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_notify):
@@ -1658,15 +1653,12 @@ class InstallSslOptionsConfTest(util.ApacheTest):
file has been manually edited by the user, and will refuse to update it.
This test ensures that all necessary hashes are present.
"""
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
import importlib.resources
from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES
ref = importlib_resources.files("certbot_apache") / "_internal" / "tls_configs"
with importlib_resources.as_file(ref) as tls_configs_dir:
ref = importlib.resources.files("certbot_apache") / "_internal" / "tls_configs"
with importlib.resources.as_file(ref) as tls_configs_dir:
all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir)
if name.endswith('options-ssl-apache.conf')]
assert len(all_files) >= 1
@@ -1723,14 +1715,14 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.config._openssl_version = None
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
assert self.config.openssl_version() == None
assert self.config.openssl_version() is None
assert "Could not find ssl_module" in mock_log.call_args[0][0]
# When no ssl_module is present at all
self.config._openssl_version = None
assert "ssl_module" not in self.config.parser.modules
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
assert self.config.openssl_version() == None
assert self.config.openssl_version() is None
assert "Could not find ssl_module" in mock_log.call_args[0][0]
# When ssl_module is statically linked but --apache-bin not provided
@@ -1738,13 +1730,13 @@ class InstallSslOptionsConfTest(util.ApacheTest):
self.config.options.bin = None
self.config.parser.modules['ssl_module'] = None
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
assert self.config.openssl_version() == None
assert self.config.openssl_version() is None
assert "ssl_module is statically linked but" in mock_log.call_args[0][0]
self.config.parser.modules['ssl_module'] = "/fake/path"
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
# Check that correct logger.warning was printed
assert self.config.openssl_version() == None
assert self.config.openssl_version() is None
assert "Unable to read" in mock_log.call_args[0][0]
contents_missing_openssl = b"these contents won't match the regex"
@@ -1753,7 +1745,7 @@ class InstallSslOptionsConfTest(util.ApacheTest):
mock_omf.return_value = contents_missing_openssl
with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log:
# Check that correct logger.warning was printed
assert self.config.openssl_version() == None
assert self.config.openssl_version() is None
assert "Could not find OpenSSL" in mock_log.call_args[0][0]
def test_open_module_file(self):

View File

@@ -1,7 +1,7 @@
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
# We specify the minimum acme and certbot version as the current plugin
@@ -9,9 +9,7 @@ install_requires = [
# https://github.com/certbot/certbot/issues/8761 for more info.
f'acme>={version}',
f'certbot>={version}',
'importlib_resources>=1.3.1; python_version < "3.9"',
'python-augeas',
'setuptools>=41.6.0',
]
dev_extras = [
@@ -30,7 +28,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -39,10 +37,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -1,4 +1,3 @@
recursive-include certbot_integration_tests/assets *
include certbot_integration_tests/py.typed
include snap_integration_tests/py.typed
include windows_installer_integration_tests/py.typed

View File

@@ -1,5 +1,4 @@
"""This module contains advanced assertions for the certbot integration tests."""
import io
import os
from typing import Optional
from typing import Type
@@ -60,7 +59,7 @@ def assert_hook_execution(probe_path: str, probe_content: str) -> None:
:param str probe_content: content expected when the hook is executed
"""
encoding = 'utf-8' if POSIX_MODE else 'utf-16'
with io.open(probe_path, 'rt', encoding=encoding) as file:
with open(probe_path, 'rt', encoding=encoding) as file:
data = file.read()
lines = [line.strip() for line in data.splitlines()]
@@ -76,7 +75,7 @@ def assert_saved_lineage_option(config_dir: str, lineage: str,
:param str option: the option key
:param value: if desired, the expected option value
"""
with open(os.path.join(config_dir, 'renewal', '{0}.conf'.format(lineage))) as file_h:
with open(os.path.join(config_dir, 'renewal', f'{lineage}.conf')) as file_h:
assert f"{option} = {value if value else ''}" in file_h.read()

View File

@@ -23,7 +23,6 @@ class IntegrationTestsContext:
self.worker_id = 'primary'
acme_xdist = request.config.acme_xdist # type: ignore[attr-defined]
self.acme_server = acme_xdist['acme_server']
self.directory_url = acme_xdist['directory_url']
self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id]
self.http_01_port = acme_xdist['http_port'][self.worker_id]
@@ -48,6 +47,14 @@ class IntegrationTestsContext:
"request.raise_for_status(); "
'"'
).format(sys.executable, self.challtestsrv_url)
self.manual_dns_auth_hook_allow_fail = (
'{0} -c "import os; import requests; import json; '
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN')),"
"'value':os.environ.get('CERTBOT_VALIDATION')}}; "
"request = requests.post('{1}/set-txt', data=json.dumps(data)); "
"request.raise_for_status(); "
'"'
).format(sys.executable, self.challtestsrv_url)
self.manual_dns_cleanup_hook = (
'{0} -c "import os; import requests; import json; '
"data = {{'host':'_acme-challenge.{{0}}.'.format(os.environ.get('CERTBOT_DOMAIN'))}}; "

View File

@@ -7,7 +7,6 @@ import shutil
import subprocess
import time
from typing import Generator
from typing import Iterable
from typing import Tuple
from typing import Type
@@ -82,11 +81,9 @@ def test_registration_override(context: IntegrationTestsContext) -> None:
context.certbot(['update_account', '--email', 'ex1@domain.org,ex2@domain.org'])
stdout2, _ = context.certbot(['show_account'])
# https://github.com/letsencrypt/boulder/issues/6144
if context.acme_server != 'boulder-v2':
assert 'example@domain.org' in stdout1, "New email should be present"
assert 'example@domain.org' not in stdout2, "Old email should not be present"
assert 'ex1@domain.org, ex2@domain.org' in stdout2, "New emails should be present"
assert 'example@domain.org' in stdout1, "New email should be present"
assert 'example@domain.org' not in stdout2, "Old email should not be present"
assert 'ex1@domain.org, ex2@domain.org' in stdout2, "New emails should be present"
def test_prepare_plugins(context: IntegrationTestsContext) -> None:
@@ -471,6 +468,44 @@ def test_reuse_key(context: IntegrationTestsContext) -> None:
assert len({cert1, cert2, cert3}) == 3
def test_reuse_key_allow_subset_of_names(context: IntegrationTestsContext) -> None:
"""Test that key is reused when allow_subset_of_names is set."""
certname = context.get_domain('dns1')
domains = ','.join([certname, context.get_domain('fail-dns1')])
context.certbot([
'-a', 'manual', '-d', domains,
'--allow-subset-of-names',
'--preferred-challenges', 'dns',
'--manual-auth-hook', context.manual_dns_auth_hook_allow_fail,
'--manual-cleanup-hook', context.manual_dns_cleanup_hook,
'--reuse-key'
])
stdout, _ = context.certbot(['certificates'])
assert context.get_domain('fail-dns1') in stdout
certname = context.get_domain('dns1')
with open(join(context.config_dir, 'archive/{0}/privkey1.pem').format(certname), 'r') as file:
privkey1 = file.read()
# manual_dns_auth_hook from misc is designed to fail if the domain contains 'fail-*'.
context.certbot([
'reconfigure',
'--cert-name', certname,
'--manual-auth-hook', context.manual_dns_auth_hook,
'-a', 'manual' # needed to override --standalone passed automatically
])
context.certbot(['renew', '--cert-name', certname, '--force-renewal', '-a', 'manual'])
stdout, _ = context.certbot(['certificates'])
assert context.get_domain('fail-dns1') not in stdout
with open(join(context.config_dir, 'archive/{0}/privkey1.pem').format(certname), 'r') as file:
privkey1 = file.read()
with open(join(context.config_dir, 'archive/{0}/privkey2.pem').format(certname), 'r') as file:
privkey2 = file.read()
assert privkey1 == privkey2
def test_new_key(context: IntegrationTestsContext) -> None:
"""Tests --new-key and its interactions with --reuse-key"""
def private_key(generation: int) -> Tuple[str, str]:
@@ -566,19 +601,15 @@ def test_default_rsa_size(context: IntegrationTestsContext) -> None:
assert_rsa_key(key1, 2048)
@pytest.mark.parametrize('curve,curve_cls,skip_servers', [
@pytest.mark.parametrize('curve,curve_cls', [
# Curve name, Curve class, ACME servers to skip
('secp256r1', SECP256R1, []),
('secp384r1', SECP384R1, []),
('secp521r1', SECP521R1, ['boulder-v2'])]
('secp256r1', SECP256R1),
('secp384r1', SECP384R1),
('secp521r1', SECP521R1)]
)
def test_ecdsa_curves(context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve],
skip_servers: Iterable[str]) -> None:
def test_ecdsa_curves(context: IntegrationTestsContext, curve: str,
curve_cls: Type[EllipticCurve]) -> None:
"""Test issuance for each supported ECDSA curve"""
if context.acme_server in skip_servers:
pytest.skip('ACME server {} does not support ECDSA curve {}'
.format(context.acme_server, curve))
domain = context.get_domain('curve')
context.certbot([
'certonly',
@@ -640,9 +671,6 @@ def test_renew_with_ec_keys(context: IntegrationTestsContext) -> None:
def test_ocsp_must_staple(context: IntegrationTestsContext) -> None:
"""Test that OCSP Must-Staple is correctly set in the generated certificate."""
if context.acme_server == 'pebble':
pytest.skip('Pebble does not support OCSP Must-Staple.')
certname = context.get_domain('must-staple')
context.certbot(['auth', '--must-staple', '--domains', certname])
@@ -710,17 +738,14 @@ def test_revoke_and_unregister(context: IntegrationTestsContext) -> None:
assert cert3 in stdout
@pytest.mark.parametrize('curve,curve_cls,skip_servers', [
('secp256r1', SECP256R1, []),
('secp384r1', SECP384R1, []),
('secp521r1', SECP521R1, ['boulder-v2'])]
@pytest.mark.parametrize('curve,curve_cls', [
('secp256r1', SECP256R1),
('secp384r1', SECP384R1),
('secp521r1', SECP521R1)]
)
def test_revoke_ecdsa_cert_key(
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve],
skip_servers: Iterable[str]) -> None:
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve]) -> None:
"""Test revoking a certificate """
if context.acme_server in skip_servers:
pytest.skip(f'ACME server {context.acme_server} does not support ECDSA curve {curve}')
cert: str = context.get_domain('curve')
context.certbot([
'certonly',
@@ -738,17 +763,14 @@ def test_revoke_ecdsa_cert_key(
assert stdout.count('INVALID: REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
@pytest.mark.parametrize('curve,curve_cls,skip_servers', [
('secp256r1', SECP256R1, []),
('secp384r1', SECP384R1, []),
('secp521r1', SECP521R1, ['boulder-v2'])]
@pytest.mark.parametrize('curve,curve_cls', [
('secp256r1', SECP256R1),
('secp384r1', SECP384R1),
('secp521r1', SECP521R1)]
)
def test_revoke_ecdsa_cert_key_delete(
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve],
skip_servers: Iterable[str]) -> None:
context: IntegrationTestsContext, curve: str, curve_cls: Type[EllipticCurve]) -> None:
"""Test revoke and deletion for each supported curve type"""
if context.acme_server in skip_servers:
pytest.skip(f'ACME server {context.acme_server} does not support ECDSA curve {curve}')
cert: str = context.get_domain('curve')
context.certbot([
'certonly',
@@ -913,7 +935,7 @@ def test_dry_run_deactivate_authzs(context: IntegrationTestsContext) -> None:
def test_preferred_chain(context: IntegrationTestsContext) -> None:
"""Test that --preferred-chain results in the correct chain.pem being produced"""
try:
issuers = misc.get_acme_issuers(context)
issuers = misc.get_acme_issuers()
except NotImplementedError:
pytest.skip('This ACME server does not support alternative issuers.')

View File

@@ -8,7 +8,6 @@ for a directory a specific configuration using built-in pytest hooks.
See https://docs.pytest.org/en/latest/reference.html#hook-reference
"""
import contextlib
import subprocess
import sys
from certbot_integration_tests.utils import acme_server as acme_lib
@@ -20,10 +19,6 @@ def pytest_addoption(parser):
Standard pytest hook to add options to the pytest parser.
:param parser: current pytest parser that will be used on the CLI
"""
parser.addoption('--acme-server', default='pebble',
choices=['boulder-v2', 'pebble'],
help='select the ACME server to use (boulder-v2, pebble), '
'defaulting to pebble')
parser.addoption('--dns-server', default='challtestsrv',
choices=['bind', 'challtestsrv'],
help='select the DNS server to use (bind, challtestsrv), '
@@ -69,7 +64,7 @@ def _setup_primary_node(config):
Setup the environment for integration tests.
This function will:
- check runtime compatibility (Docker, docker-compose, Nginx)
- check runtime compatibility (Docker, docker compose, Nginx)
- create a temporary workspace and the persistent GIT repositories space
- configure and start a DNS server using Docker, if configured
- configure and start paralleled ACME CA servers using Docker
@@ -80,22 +75,6 @@ def _setup_primary_node(config):
:param config: Configuration of the pytest primary node. Is modified by this function.
"""
# Check for runtime compatibility: some tools are required to be available in PATH
if 'boulder' in config.option.acme_server:
try:
subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT)
except (subprocess.CalledProcessError, OSError):
raise ValueError('Error: docker is required in PATH to launch the integration tests on'
'boulder, but is not installed or not available for current user.')
try:
subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT)
except (subprocess.CalledProcessError, OSError):
raise ValueError(
'Error: docker-compose is required in PATH to launch the integration tests, '
'but is not installed or not available for current user.'
)
# Parameter numprocesses is added to option by pytest-xdist
workers = ['primary'] if not config.option.numprocesses\
else ['gw{0}'.format(i) for i in range(config.option.numprocesses)]
@@ -115,8 +94,7 @@ def _setup_primary_node(config):
# By calling setup_acme_server we ensure that all necessary acme server instances will be
# fully started. This runtime is reflected by the acme_xdist returned.
acme_server = acme_lib.ACMEServer(config.option.acme_server, workers,
dns_server=acme_dns_server)
acme_server = acme_lib.ACMEServer(workers, dns_server=acme_dns_server)
config.add_cleanup(acme_server.stop)
print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist))
acme_server.start()

View File

@@ -2,15 +2,10 @@
"""General purpose nginx test configuration generator."""
import atexit
import getpass
import sys
import importlib.resources
from contextlib import ExitStack
from typing import Optional
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
def construct_nginx_config(nginx_root: str, nginx_webroot: str, http_port: int, https_port: int,
other_port: int, default_server: bool, key_path: Optional[str] = None,
@@ -32,14 +27,16 @@ def construct_nginx_config(nginx_root: str, nginx_webroot: str, http_port: int,
if not key_path:
file_manager = ExitStack()
atexit.register(file_manager.close)
ref = importlib_resources.files('certbot_integration_tests').joinpath('assets', 'key.pem')
key_path = str(file_manager.enter_context(importlib_resources.as_file(ref)))
ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
.joinpath('key.pem'))
key_path = str(file_manager.enter_context(importlib.resources.as_file(ref)))
if not cert_path:
file_manager = ExitStack()
atexit.register(file_manager.close)
ref = importlib_resources.files('certbot_integration_tests').joinpath('assets', 'cert.pem')
cert_path = str(file_manager.enter_context(importlib_resources.as_file(ref)))
ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
.joinpath('cert.pem'))
cert_path = str(file_manager.enter_context(importlib.resources.as_file(ref)))
return '''\
# This error log will be written regardless of server scope error_log

View File

@@ -1,6 +1,6 @@
"""Module to handle the context of RFC2136 integration tests."""
from contextlib import contextmanager
import sys
import importlib.resources
import tempfile
from typing import Generator
from typing import Iterable
@@ -11,11 +11,6 @@ import pytest
from certbot_integration_tests.certbot_tests import context as certbot_context
from certbot_integration_tests.utils import certbot_call
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
"""Integration test context for certbot-dns-rfc2136"""
@@ -48,10 +43,9 @@ class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
:yields: Path to credentials file
:rtype: str
"""
src_ref_file = importlib_resources.files('certbot_integration_tests').joinpath(
'assets', 'bind-config', f'rfc2136-credentials-{label}.ini.tpl'
)
with importlib_resources.as_file(src_ref_file) as src_file:
src_ref_file = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
.joinpath('bind-config').joinpath(f'rfc2136-credentials-{label}.ini.tpl'))
with importlib.resources.as_file(src_ref_file) as src_file:
with open(src_file, 'r') as f:
contents = f.read().format(
server_address=self._dns_xdist['address'],

View File

@@ -5,7 +5,6 @@ import argparse
import errno
import json
import os
from os.path import join
import shutil
import subprocess
import sys
@@ -18,14 +17,12 @@ from typing import Dict
from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Type
import requests
# pylint: disable=wildcard-import,unused-wildcard-import
from certbot_integration_tests.utils import misc
from certbot_integration_tests.utils import pebble_artifacts
from certbot_integration_tests.utils import pebble_ocsp_server
from certbot_integration_tests.utils import proxy
from certbot_integration_tests.utils.constants import *
@@ -42,34 +39,30 @@ class ACMEServer:
ACMEServer is also a context manager, and so can be used to ensure ACME server is
started/stopped upon context enter/exit.
"""
def __init__(self, acme_server: str, nodes: List[str], http_proxy: bool = True,
def __init__(self, nodes: List[str], http_proxy: bool = True,
stdout: bool = False, dns_server: Optional[str] = None,
http_01_port: Optional[int] = None) -> None:
"""
Create an ACMEServer instance.
:param str acme_server: the type of acme server used (boulder-v2 or pebble)
:param list nodes: list of node names that will be setup by pytest xdist
:param bool http_proxy: if False do not start the HTTP proxy
:param bool stdout: if True stream all subprocesses stdout to standard stdout
:param str dns_server: if set, Pebble/Boulder will use it to resolve domains
:param str dns_server: if set, Pebble will use it to resolve domains
:param int http_01_port: port to use for http-01 validation; currently
only supported for pebble without an HTTP proxy
"""
self._construct_acme_xdist(acme_server, nodes)
self._construct_acme_xdist(nodes)
self._acme_type = 'pebble' if acme_server == 'pebble' else 'boulder'
self._proxy = http_proxy
self._workspace = tempfile.mkdtemp()
self._processes: List[subprocess.Popen] = []
self._stdout = sys.stdout if stdout else open(os.devnull, 'w') # pylint: disable=consider-using-with
self._dns_server = dns_server
self._preterminate_cmds_args: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
self._http_01_port = BOULDER_HTTP_01_PORT if self._acme_type == 'boulder' \
else DEFAULT_HTTP_01_PORT
self._http_01_port = DEFAULT_HTTP_01_PORT
if http_01_port:
if (self._acme_type == 'pebble' and self._proxy) or self._acme_type == 'boulder':
if self._proxy:
raise ValueError('Setting http_01_port is not currently supported when '
'using Boulder or the HTTP proxy')
'using the HTTP proxy')
self._http_01_port = http_01_port
def start(self) -> None:
@@ -77,10 +70,7 @@ class ACMEServer:
try:
if self._proxy:
self._prepare_http_proxy()
if self._acme_type == 'pebble':
self._prepare_pebble_server()
if self._acme_type == 'boulder':
self._prepare_boulder_server()
self._prepare_pebble_server()
except BaseException as e:
self.stop()
raise e
@@ -89,7 +79,6 @@ class ACMEServer:
"""Stop the test stack, and clean its resources"""
print('=> Tear down the test infrastructure...')
try:
self._run_preterminate_cmds()
for process in self._processes:
try:
process.terminate()
@@ -115,19 +104,14 @@ class ACMEServer:
traceback: Optional[TracebackType]) -> None:
self.stop()
def _construct_acme_xdist(self, acme_server: str, nodes: List[str]) -> None:
def _construct_acme_xdist(self, nodes: List[str]) -> None:
"""Generate and return the acme_xdist dict"""
acme_xdist: Dict[str, Any] = {'acme_server': acme_server}
acme_xdist: Dict[str, Any] = {}
# Directory and ACME port are set implicitly in the docker-compose.yml
# files of Boulder/Pebble.
if acme_server == 'pebble':
acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL
acme_xdist['challtestsrv_url'] = PEBBLE_CHALLTESTSRV_URL
else: # boulder
acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL
acme_xdist['challtestsrv_url'] = BOULDER_V2_CHALLTESTSRV_URL
# files of Pebble.
acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL
acme_xdist['challtestsrv_url'] = PEBBLE_CHALLTESTSRV_URL
acme_xdist['http_port'] = dict(zip(nodes, range(5200, 5200 + len(nodes))))
acme_xdist['https_port'] = dict(zip(nodes, range(5100, 5100 + len(nodes))))
acme_xdist['other_port'] = dict(zip(nodes, range(5300, 5300 + len(nodes))))
@@ -161,11 +145,6 @@ class ACMEServer:
[pebble_path, '-config', pebble_config_path, '-dnsserver', dns_server, '-strict'],
env=environ)
# pebble_ocsp_server is imported here and not at the top of module in order to avoid a
# useless ImportError, in the case where cryptography dependency is too old to support
# ocsp, but Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is
# the typical situation of integration-certbot-oldest tox testenv.
from certbot_integration_tests.utils import pebble_ocsp_server
self._launch_process([sys.executable, pebble_ocsp_server.__file__])
# Wait for the ACME CA server to be up.
@@ -174,68 +153,6 @@ class ACMEServer:
print('=> Finished pebble instance deployment.')
def _prepare_boulder_server(self) -> None:
"""Configure and launch the Boulder server"""
print('=> Starting boulder instance deployment...')
instance_path = join(self._workspace, 'boulder')
# Load Boulder from git, that includes a docker-compose.yml ready for production.
process = self._launch_process(['git', 'clone', 'https://github.com/letsencrypt/boulder',
'--single-branch', '--depth=1', instance_path])
process.wait(MAX_SUBPROCESS_WAIT)
# Allow Boulder to ignore usual limit rate policies, useful for tests.
os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'),
join(instance_path, 'test/rate-limit-policies.yml'))
if self._dns_server:
# Change Boulder config to use the provided DNS server
for suffix in ["", "-remote-a", "-remote-b"]:
with open(join(instance_path, 'test/config/va{}.json'.format(suffix)), 'r') as f:
config = json.loads(f.read())
config['va']['dnsResolvers'] = [self._dns_server]
with open(join(instance_path, 'test/config/va{}.json'.format(suffix)), 'w') as f:
f.write(json.dumps(config, indent=2, separators=(',', ': ')))
# This command needs to be run before we try and terminate running processes because
# docker-compose up doesn't always respond to SIGTERM. See
# https://github.com/certbot/certbot/pull/9435.
self._register_preterminate_cmd(['docker-compose', 'down'], cwd=instance_path)
# Boulder docker generates build artifacts owned by root with 0o744 permissions.
# If we started the acme server from a normal user that has access to the Docker
# daemon, this user will not be able to delete these artifacts from the host.
# We need to do it through a docker.
self._register_preterminate_cmd(['docker', 'run', '--rm', '-v',
'{0}:/workspace'.format(self._workspace), 'alpine', 'rm',
'-rf', '/workspace/boulder'])
try:
# Launch the Boulder server
self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path)
# Wait for the ACME CA server to be up.
print('=> Waiting for boulder instance to respond...')
misc.check_until_timeout(
self.acme_xdist['directory_url'], attempts=300)
if not self._dns_server:
# Configure challtestsrv to answer any A record request with ip of the docker host.
response = requests.post(
f'{BOULDER_V2_CHALLTESTSRV_URL}/set-default-ipv4',
json={'ip': '10.77.77.1'},
timeout=10
)
response.raise_for_status()
except BaseException:
# If we failed to set up boulder, print its logs.
print('=> Boulder setup failed. Boulder logs are:')
process = self._launch_process([
'docker-compose', 'logs'], cwd=instance_path, force_stderr=True
)
process.wait(MAX_SUBPROCESS_WAIT)
raise
print('=> Finished boulder instance deployment.')
def _prepare_http_proxy(self) -> None:
"""Configure and launch an HTTP proxy"""
print(f'=> Configuring the HTTP proxy on port {self._http_01_port}...')
@@ -260,26 +177,11 @@ class ACMEServer:
self._processes.append(process)
return process
def _register_preterminate_cmd(self, *args: Any, **kwargs: Any) -> None:
self._preterminate_cmds_args.append((args, kwargs))
def _run_preterminate_cmds(self) -> None:
for args, kwargs in self._preterminate_cmds_args:
process = self._launch_process(*args, **kwargs)
process.wait(MAX_SUBPROCESS_WAIT)
# It's unlikely to matter, but let's clear the list of cleanup commands
# once they've been run.
self._preterminate_cmds_args.clear()
def main() -> None:
# pylint: disable=missing-function-docstring
parser = argparse.ArgumentParser(
description='CLI tool to start a local instance of Pebble or Boulder CA server.')
parser.add_argument('--server-type', '-s',
choices=['pebble', 'boulder-v2'], default='pebble',
help='type of CA server to start: can be Pebble or Boulder. '
'Pebble is used if not set.')
description='CLI tool to start a local instance of Pebble CA server.')
parser.add_argument('--dns-server', '-d',
help='specify the DNS server as `IP:PORT` to use by '
'Pebble; if not specified, a local mock DNS server will be used to '
@@ -290,8 +192,8 @@ def main() -> None:
args = parser.parse_args()
acme_server = ACMEServer(
args.server_type, [], http_proxy=False, stdout=True,
dns_server=args.dns_server, http_01_port=args.http_01_port,
[], http_proxy=False, stdout=True, dns_server=args.dns_server,
http_01_port=args.http_01_port,
)
try:

View File

@@ -51,7 +51,7 @@ def _prepare_environ(workspace: str) -> Dict[str, str]:
# So, pytest is nice, and a little too nice for our usage.
# In order to help user to call seamlessly any piece of python code without requiring to
# install it as a full-fledged setuptools distribution for instance, it may inject the path
# install it as a full-fledged Python package for instance, it may inject the path
# to the test files into the PYTHONPATH. This allows the python interpreter to import
# as modules any python file available at this path.
# See https://docs.pytest.org/en/3.2.5/pythonpath.html for the explanation and description.
@@ -96,7 +96,6 @@ def _prepare_args_env(certbot_args: List[str], directory_url: str, http_01_port:
'--no-verify-ssl',
'--http-01-port', str(http_01_port),
'--https-port', str(tls_alpn_01_port),
'--manual-public-ip-logging-ok',
'--config-dir', config_dir,
'--work-dir', os.path.join(workspace, 'work'),
'--logs-dir', os.path.join(workspace, 'logs'),

View File

@@ -1,10 +1,7 @@
"""Some useful constants to use throughout certbot-ci integration tests"""
DEFAULT_HTTP_01_PORT = 5002
BOULDER_HTTP_01_PORT = 80
TLS_ALPN_01_PORT = 5001
CHALLTESTSRV_PORT = 8055
BOULDER_V2_CHALLTESTSRV_URL = f'http://10.77.77.77:{CHALLTESTSRV_PORT}'
BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory'
PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir'
PEBBLE_MANAGEMENT_URL = 'https://localhost:15000'
PEBBLE_CHALLTESTSRV_URL = f'http://localhost:{CHALLTESTSRV_PORT}'

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env python
"""Module to setup an RFC2136-capable DNS server"""
import importlib.resources
import os
import os.path
import shutil
@@ -17,12 +18,7 @@ from typing import Type
from certbot_integration_tests.utils import constants
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
BIND_DOCKER_IMAGE = "internetsystemsconsortium/bind9:9.16"
BIND_DOCKER_IMAGE = "internetsystemsconsortium/bind9:9.20"
BIND_BIND_ADDRESS = ("127.0.0.1", 45953)
# A TCP DNS message which is a query for '. CH A' transaction ID 0xcb37. This is used
@@ -83,8 +79,8 @@ class DNSServer:
def _configure_bind(self) -> None:
"""Configure the BIND9 server based on the prebaked configuration"""
ref = importlib_resources.files("certbot_integration_tests") / "assets" / "bind-config"
with importlib_resources.as_file(ref) as path:
ref = importlib.resources.files("certbot_integration_tests") / "assets" / "bind-config"
with importlib.resources.as_file(ref) as path:
for directory in ("conf", "zones"):
shutil.copytree(
os.path.join(path, directory), os.path.join(self.bind_root, directory)

View File

@@ -7,6 +7,7 @@ import contextlib
import errno
import functools
import http.server as SimpleHTTPServer
import importlib.resources
import os
import re
import shutil
@@ -21,10 +22,13 @@ from typing import Iterable
from typing import List
from typing import Optional
from typing import Tuple
import warnings
from typing import Union
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import NoEncryption
from cryptography.hazmat.primitives.serialization import PrivateFormat
@@ -33,15 +37,9 @@ from cryptography.x509 import load_pem_x509_certificate
from OpenSSL import crypto
import requests
from certbot_integration_tests.certbot_tests.context import IntegrationTestsContext
from certbot_integration_tests.utils.constants import PEBBLE_ALTERNATE_ROOTS
from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
RSA_KEY_TYPE = 'rsa'
ECDSA_KEY_TYPE = 'ecdsa'
@@ -125,9 +123,9 @@ def generate_test_file_hooks(config_dir: str, hook_probe: str) -> None:
"""
file_manager = contextlib.ExitStack()
atexit.register(file_manager.close)
hook_path_ref = importlib_resources.files('certbot_integration_tests').joinpath(
'assets', 'hook.py')
hook_path = str(file_manager.enter_context(importlib_resources.as_file(hook_path_ref)))
hook_path_ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
.joinpath('hook.py'))
hook_path = str(file_manager.enter_context(importlib.resources.as_file(hook_path_ref)))
for hook_dir in list_renewal_hooks_dirs(config_dir):
# We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of
@@ -200,8 +198,9 @@ shutil.rmtree(well_known)
shutil.rmtree(tempdir)
def generate_csr(domains: Iterable[str], key_path: str, csr_path: str,
key_type: str = RSA_KEY_TYPE) -> None:
def generate_csr(
domains: Iterable[str], key_path: str, csr_path: str, key_type: str = RSA_KEY_TYPE
) -> None:
"""
Generate a private key, and a CSR for the given domains using this key.
:param domains: the domain names to include in the CSR
@@ -210,35 +209,33 @@ def generate_csr(domains: Iterable[str], key_path: str, csr_path: str,
:param str csr_path: path to the CSR that will be generated
:param str key_type: type of the key (misc.RSA_KEY_TYPE or misc.ECDSA_KEY_TYPE)
"""
key: Union[rsa.RSAPrivateKey, ec.EllipticCurvePrivateKey]
if key_type == RSA_KEY_TYPE:
key = crypto.PKey()
key.generate_key(crypto.TYPE_RSA, 2048)
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
elif key_type == ECDSA_KEY_TYPE:
with warnings.catch_warnings():
# Ignore a warning on some old versions of cryptography
warnings.simplefilter('ignore', category=PendingDeprecationWarning)
_key = ec.generate_private_key(ec.SECP384R1(), default_backend())
_bytes = _key.private_bytes(encoding=Encoding.PEM,
format=PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=NoEncryption())
key = crypto.load_privatekey(crypto.FILETYPE_PEM, _bytes)
key = ec.generate_private_key(ec.SECP384R1())
else:
raise ValueError('Invalid key type: {0}'.format(key_type))
raise ValueError("Invalid key type: {0}".format(key_type))
with open(key_path, 'wb') as file_h:
file_h.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key))
with open(key_path, "wb") as file_h:
file_h.write(
key.private_bytes(
Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()
)
)
req = crypto.X509Req()
san = ', '.join('DNS:{0}'.format(item) for item in domains)
san_constraint = crypto.X509Extension(b'subjectAltName', False, san.encode('utf-8'))
req.add_extensions([san_constraint])
csr = (
x509.CertificateSigningRequestBuilder()
.subject_name(x509.Name([]))
.add_extension(
x509.SubjectAlternativeName([x509.DNSName(d) for d in domains]),
critical=False,
)
.sign(key, hashes.SHA256())
)
req.set_pubkey(key)
req.set_version(0)
req.sign(key, 'sha256')
with open(csr_path, 'wb') as file_h:
file_h.write(crypto.dump_certificate_request(crypto.FILETYPE_ASN1, req))
with open(csr_path, "wb") as file_h:
file_h.write(csr.public_bytes(Encoding.DER))
def read_certificate(cert_path: str) -> str:
@@ -248,11 +245,13 @@ def read_certificate(cert_path: str) -> str:
:param str cert_path: the path to the certificate
:returns: the TEXT version of the certificate, as it would be displayed by openssl binary
"""
with open(cert_path, 'rb') as file:
with open(cert_path, "rb") as file:
data = file.read()
cert = crypto.load_certificate(crypto.FILETYPE_PEM, data)
return crypto.dump_certificate(crypto.FILETYPE_TEXT, cert).decode('utf-8')
cert = x509.load_pem_x509_certificate(data)
return crypto.dump_certificate(
crypto.FILETYPE_TEXT, crypto.X509.from_cryptography(cert)
).decode("utf-8")
def load_sample_data_path(workspace: str) -> str:
@@ -262,10 +261,9 @@ def load_sample_data_path(workspace: str) -> str:
:returns: the path to the loaded sample data directory
:rtype: str
"""
original_ref = importlib_resources.files('certbot_integration_tests').joinpath(
'assets', 'sample-config'
)
with importlib_resources.as_file(original_ref) as original:
original_ref = (importlib.resources.files('certbot_integration_tests').joinpath('assets')
.joinpath('sample-config'))
with importlib.resources.as_file(original_ref) as original:
copied = os.path.join(workspace, 'sample-config')
shutil.copytree(original, copied, symlinks=True)
@@ -304,16 +302,12 @@ def echo(keyword: str, path: Optional[str] = None) -> str:
os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '')
def get_acme_issuers(context: IntegrationTestsContext) -> List[Certificate]:
def get_acme_issuers() -> List[Certificate]:
"""Gets the list of one or more issuer certificates from the ACME server used by the
context.
:param context: the testing context.
:return: the `list of x509.Certificate` representing the list of issuers.
"""
# TODO: in fact, Boulder has alternate chains in config-next/, just not yet in config/.
if context.acme_server != "pebble":
raise NotImplementedError()
_suppress_x509_verification_warnings()
issuers = []

View File

@@ -1,55 +1,62 @@
# pylint: disable=missing-module-docstring
import atexit
import importlib.resources
import io
import json
import os
import stat
import sys
import zipfile
from contextlib import ExitStack
from typing import Tuple
from typing import Optional, Tuple
import requests
from certbot_integration_tests.utils.constants import DEFAULT_HTTP_01_PORT
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
if sys.version_info >= (3, 9): # pragma: no cover
import importlib.resources as importlib_resources
else: # pragma: no cover
import importlib_resources
PEBBLE_VERSION = 'v2.3.1'
PEBBLE_VERSION = 'v2.5.1'
def fetch(workspace: str, http_01_port: int = DEFAULT_HTTP_01_PORT) -> Tuple[str, str, str]:
# pylint: disable=missing-function-docstring
suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe'
file_manager = ExitStack()
atexit.register(file_manager.close)
pebble_path_ref = importlib_resources.files('certbot_integration_tests') / 'assets'
assets_path = str(file_manager.enter_context(importlib_resources.as_file(pebble_path_ref)))
pebble_path_ref = importlib.resources.files('certbot_integration_tests') / 'assets'
assets_path = str(file_manager.enter_context(importlib.resources.as_file(pebble_path_ref)))
pebble_path = _fetch_asset('pebble', suffix, assets_path)
challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix, assets_path)
pebble_path = _fetch_asset('pebble', assets_path)
challtestsrv_path = _fetch_asset('pebble-challtestsrv', assets_path)
pebble_config_path = _build_pebble_config(workspace, http_01_port, assets_path)
return pebble_path, challtestsrv_path, pebble_config_path
def _fetch_asset(asset: str, suffix: str, assets_path: str) -> str:
asset_path = os.path.join(assets_path, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix))
def _fetch_asset(asset: str, assets_path: str) -> str:
platform = 'linux-amd64'
base_url = 'https://github.com/letsencrypt/pebble/releases/download'
asset_path = os.path.join(assets_path, f'{asset}_{PEBBLE_VERSION}_{platform}')
if not os.path.exists(asset_path):
asset_url = ('https://github.com/letsencrypt/pebble/releases/download/{0}/{1}_{2}'
.format(PEBBLE_VERSION, asset, suffix))
asset_url = f'{base_url}/{PEBBLE_VERSION}/{asset}-{platform}.zip'
response = requests.get(asset_url, timeout=30)
response.raise_for_status()
asset_data = _unzip_asset(response.content, asset)
if asset_data is None:
raise ValueError(f"zipfile {asset_url} didn't contain file {asset}")
with open(asset_path, 'wb') as file_h:
file_h.write(response.content)
file_h.write(asset_data)
os.chmod(asset_path, os.stat(asset_path).st_mode | stat.S_IEXEC)
return asset_path
def _unzip_asset(zipped_data: bytes, asset_name: str) -> Optional[bytes]:
with zipfile.ZipFile(io.BytesIO(zipped_data)) as zip_file:
for entry in zip_file.filelist:
if not entry.is_dir() and entry.filename.endswith(asset_name):
return zip_file.read(entry)
return None
def _build_pebble_config(workspace: str, http_01_port: int, assets_path: str) -> str:
config_path = os.path.join(workspace, 'pebble-config.json')
with open(config_path, 'w') as file_h:

View File

@@ -6,7 +6,6 @@ version = '0.32.0.dev0'
install_requires = [
'coverage',
'cryptography',
'importlib_resources>=1.3.1; python_version < "3.9"',
'pyopenssl',
'pytest',
'pytest-cov',
@@ -22,7 +21,6 @@ install_requires = [
# requests unvendored its dependencies in version 2.16.0 and this code relies on that for
# calling `urllib3.disable_warnings`.
'requests>=2.16.0',
'setuptools',
'types-python-dateutil',
]
@@ -34,17 +32,17 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
],

View File

@@ -1,38 +0,0 @@
# type: ignore
"""
General conftest for pytest execution of all integration tests lying
in the window_installer_integration tests package.
As stated by pytest documentation, conftest module is used to set on
for a directory a specific configuration using built-in pytest hooks.
See https://docs.pytest.org/en/latest/reference.html#hook-reference
"""
import os
ROOT_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
def pytest_addoption(parser):
"""
Standard pytest hook to add options to the pytest parser.
:param parser: current pytest parser that will be used on the CLI
"""
parser.addoption('--installer-path',
default=os.path.join(ROOT_PATH, 'windows-installer', 'build',
'nsis', 'certbot-beta-installer-win_amd64.exe'),
help='set the path of the windows installer to use, default to '
'CERTBOT_ROOT_PATH\\windows-installer\\build\\nsis\\certbot-beta-installer-win_amd64.exe') # pylint: disable=line-too-long
parser.addoption('--allow-persistent-changes', action='store_true',
help='needs to be set, and confirm that the test will make persistent changes on this machine') # pylint: disable=line-too-long
def pytest_configure(config):
"""
Standard pytest hook used to add a configuration logic for each node of a pytest run.
:param config: the current pytest configuration
"""
if not config.option.allow_persistent_changes:
raise RuntimeError('This integration test would install Certbot on your machine. '
'Please run it again with the `--allow-persistent-changes` '
'flag set to acknowledge.')

View File

@@ -1,69 +0,0 @@
"""Module executing integration tests for the windows installer."""
import os
import re
import subprocess
import time
from typing import Any
import pytest
@pytest.mark.skipif(os.name != 'nt', reason='Windows installer tests must be run on Windows.')
def test_it(request: pytest.FixtureRequest) -> None:
try:
subprocess.check_call(['certbot', '--version'])
except (subprocess.CalledProcessError, OSError):
pass
else:
raise AssertionError('Expect certbot to not be available in the PATH.')
try:
# Install certbot
subprocess.check_call([request.config.option.installer_path, '/S'])
# Assert certbot is installed and runnable
output = subprocess.check_output(['certbot', '--version'], universal_newlines=True)
assert re.match(r'^certbot \d+\.\d+\.\d+.*$',
output), 'Flag --version does not output a version.'
# Assert renew task is installed and ready
output = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State',
capture_stdout=True)
assert output.strip() == 'Ready'
# Assert renew task is working
now = time.time()
_ps('Start-ScheduledTask -TaskName "Certbot Renew Task"')
status = 'Running'
while status != 'Ready':
status = _ps('(Get-ScheduledTask -TaskName "Certbot Renew Task").State',
capture_stdout=True).strip()
time.sleep(1)
log_path = os.path.join('C:\\', 'Certbot', 'log', 'letsencrypt.log')
modification_time = os.path.getmtime(log_path)
assert now < modification_time, 'Certbot log file has not been modified by the renew task.'
with open(log_path) as file_h:
data = file_h.read()
assert 'no renewal failures' in data, 'Renew task did not execute properly.'
finally:
# Sadly this command cannot work in non interactive mode: uninstaller will
# ask explicitly permission in an UAC prompt
# print('Uninstalling Certbot ...')
# uninstall_path = _ps('(gci "HKLM:\\SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"' # pylint: disable=line-too-long
# ' | foreach { gp $_.PSPath }'
# ' | ? { $_ -match "Certbot" }'
# ' | select UninstallString)'
# '.UninstallString', capture_stdout=True)
# subprocess.check_call([uninstall_path, '/S'])
pass
def _ps(powershell_str: str, capture_stdout: bool = False) -> Any:
fn = subprocess.check_output if capture_stdout else subprocess.check_call
return fn(['powershell.exe', '-c', powershell_str], # type: ignore[operator]
universal_newlines=True)

View File

@@ -1,4 +1,4 @@
FROM docker.io/python:3.8-buster
FROM docker.io/python:3.11-buster
LABEL maintainer="Brad Warren <bmw@eff.org>"
# This does not include the dependencies needed to build cryptography. See

View File

@@ -75,7 +75,7 @@ def _get_server_root(config: str) -> str:
if os.path.isdir(os.path.join(config, name))]
if len(subdirs) != 1:
errors.Error("Malformed configuration directory {0}".format(config))
raise errors.Error("Malformed configuration directory {0}".format(config))
return os.path.join(config, subdirs[0].rstrip())

View File

@@ -18,7 +18,7 @@ from typing import Optional
from typing import Tuple
from typing import Type
from OpenSSL import crypto
from cryptography.hazmat.primitives import serialization
from urllib3.util import connection
from acme import challenges
@@ -147,10 +147,10 @@ def test_installer(args: argparse.Namespace, plugin: common.Proxy, config: str,
def test_deploy_cert(plugin: common.Proxy, temp_dir: str, domains: List[str]) -> bool:
"""Tests deploy_cert returning True if the tests are successful"""
cert = crypto_util.gen_ss_cert(util.KEY, domains)
cert = crypto_util.gen_ss_cert(util.KEY, domains).to_cryptography()
cert_path = os.path.join(temp_dir, "cert.pem")
with open(cert_path, "wb") as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
f.write(cert.public_bytes(serialization.Encoding.PEM))
for domain in domains:
try:
@@ -390,7 +390,7 @@ def _fake_dns_resolution(resolved_ip: str) -> Generator[None, None, None]:
"""Monkey patch urllib3 to make any hostname be resolved to the provided IP"""
_original_create_connection = connection.create_connection
def _patched_create_connection(address: Tuple[str, str],
def _patched_create_connection(address: Tuple[str, int],
*args: Any, **kwargs: Any) -> socket.socket:
_, port = address
return _original_create_connection((resolved_ip, port), *args, **kwargs)

View File

@@ -6,7 +6,7 @@ from typing import Mapping
from typing import Optional
from typing import Union
from OpenSSL import crypto
from cryptography import x509
import requests
from acme import crypto_util
@@ -21,7 +21,7 @@ _VALIDATION_TIMEOUT = 10
class Validator:
"""Collection of functions to test a live webserver's configuration"""
def certificate(self, cert: crypto.X509, name: Union[str, bytes],
def certificate(self, cert: x509.Certificate, name: Union[str, bytes],
alt_host: Optional[str] = None, port: int = 443) -> bool:
"""Verifies the certificate presented at name is cert"""
if alt_host is None:
@@ -39,7 +39,7 @@ class Validator:
logger.exception(str(error))
return False
return presented_cert.digest("sha256") == cert.digest("sha256")
return presented_cert.to_cryptography() == cert
def redirect(self, name: str, port: int = 80,
headers: Optional[Mapping[str, str]] = None) -> bool:

View File

@@ -19,7 +19,6 @@
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm;

View File

@@ -1,7 +1,7 @@
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'certbot',
@@ -18,17 +18,17 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
],

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-cloudflare/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-cloudflare/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,12 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'cloudflare>=1.5.1',
'setuptools>=41.6.0',
# for now, do not upgrade to cloudflare>=2.20 to avoid deprecation warnings and the breaking
# changes in version 3.0. see https://github.com/certbot/certbot/issues/9938
'cloudflare>=1.5.1, <2.20',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +40,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +49,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-digitalocean/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-digitalocean/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,10 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'python-digitalocean>=1.11', # 1.15.0 or newer is recommended for TTL support
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +38,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +47,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-dnsimple/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-dnsimple/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,13 +4,12 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
# This version of lexicon is required to address the problem described in
# https://github.com/AnalogJ/lexicon/issues/387.
'dns-lexicon>=3.14.1',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -41,7 +40,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -50,10 +49,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-dnsmadeeasy/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,10 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'dns-lexicon>=3.14.1',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +38,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +47,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-gehirn/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-gehirn/readthedocs.org.requirements.txt

View File

@@ -1,7 +1,6 @@
"""Tests for certbot_dns_gehirn._internal.dns_gehirn."""
import sys
import unittest
from unittest import mock
import pytest

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,10 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'dns-lexicon>=3.14.1',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +38,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +47,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-google/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-google/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,12 +4,11 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'google-api-python-client>=1.6.5',
'google-auth>=2.16.0',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -40,7 +39,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -49,10 +48,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-linode/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-linode/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,10 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'dns-lexicon>=3.14.1',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +38,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +47,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-luadns/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-luadns/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,10 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'dns-lexicon>=3.14.1',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +38,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +47,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-nsone/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-nsone/readthedocs.org.requirements.txt

View File

@@ -170,6 +170,6 @@ texinfo_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'acme': ('https://acme-python.readthedocs.io/en/latest/', None),
'certbot': ('https://eff-certbot.readthedocs.io/en/stable/', None),
}

View File

@@ -4,11 +4,10 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '2.8.0.dev0'
version = '3.2.0.dev0'
install_requires = [
'dns-lexicon>=3.14.1',
'setuptools>=41.6.0',
]
if os.environ.get('SNAP_BUILD'):
@@ -39,7 +38,7 @@ setup(
author="Certbot Project",
author_email='certbot-dev@eff.org',
license='Apache License 2.0',
python_requires='>=3.8',
python_requires='>=3.9',
classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Plugins',
@@ -48,10 +47,10 @@ setup(
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 3',
'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',
'Topic :: System :: Installation/Setup',

View File

@@ -14,14 +14,14 @@ build:
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
configuration: certbot-dns-ovh/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:
formats:
- pdf
- epub
@@ -30,4 +30,4 @@ sphinx:
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: ../tools/requirements.txt
- requirements: certbot-dns-ovh/readthedocs.org.requirements.txt

View File

@@ -22,6 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase,
DOMAIN_NOT_FOUND = Exception('Domain example.com not found')
LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: https://eu.api.ovh.com/1.0/...', response=Response())
def setUp(self):
super().setUp()

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