Compare commits

...

112 Commits

Author SHA1 Message Date
Felix Fontein
8ccab3ab80 Release 1.9.20. 2023-01-01 08:14:32 +01:00
patchback[bot]
e2ecd14d86 Fix crash when public key cannot be parsed. (#551) (#552)
(cherry picked from commit 5d24d04adf)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-12-28 20:53:02 +01:00
patchback[bot]
6f8131a628 Make sure that iteration_count=1000 is not used with algorithm=argon* (which is SLOW and takes around 10 minutes). (#546) (#547)
(cherry picked from commit 242c15bf4c)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-12-20 20:16:20 +01:00
Felix Fontein
5761ca12b6 Disable broken CI platforms. (#469)
(cherry picked from commit f7bc3aa77c)
2022-12-20 07:24:19 +01:00
Felix Fontein
bfe181ac1f Move tests with EOL versions of Ansible from AZP to GHA. (#543) 2022-12-20 06:58:28 +01:00
Felix Fontein
f5632c27f4 [TEMP] Create temp remote directory in ~. (#504)
(cherry picked from commit d0d99c31b0)
2022-12-11 18:12:53 +01:00
Felix Fontein
6bec0b402c Be more precise about which private keys are supported in openssl_publickey. (#532)
(cherry picked from commit 1097371cf4)
2022-11-27 18:19:42 +01:00
Felix Fontein
b0262bf8f1 Prepare 1.9.20 release. 2022-11-01 21:14:17 +01:00
Felix Fontein
c57822b3b3 Release 1.9.19. 2022-11-01 20:53:52 +01:00
patchback[bot]
c313bbd83d Action plugin support code: ensure compatibility with newer versions of ansible-core (#515) (#516)
* Only access C.STRING_CONVERSION_ACTION for old ansible-base / Ansible versions.

* Always use self.__xxx instead of xxx directly.

(cherry picked from commit b3f589df62)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-09-23 07:33:48 +02:00
patchback[bot]
b9e8bb70eb Fix docs (#497) (#498)
* Fix docs.

* Fix YAML.

* Prevent crashes with older pyyaml versions.

(cherry picked from commit 2dafef1fab)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-08-04 20:17:55 +02:00
patchback[bot]
62da550c6c Fix ssh-agent tests (#493) (#494)
* Work around stupid ssh-agent output format.

* Workaround for Ansible 2.9.

* Old jinja2...

* Jinja2 on CentOS 6 is really annoying.

(cherry picked from commit e4ebca0945)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-07-22 14:00:53 +02:00
Felix Fontein
3d9fb17d17 Fix typo. 2022-07-09 13:54:52 +02:00
Felix Fontein
2fd7aa6de7 Prepare 1.9.19. 2022-07-09 13:51:12 +02:00
Felix Fontein
b78dd8d542 Release 1.9.18. 2022-07-09 13:17:40 +02:00
Felix Fontein
1df51621fa openssl_pkcs12: fix crash when trying to get non-existing other certificates (#487) (#488)
* Fix crash when trying to get non-existing other certificates.

* Add test.

(cherry picked from commit 9ed4526fee)
2022-07-07 22:53:36 +02:00
Felix Fontein
33703d15e2 Prepare 1.9.18 release. 2022-06-17 09:26:09 +02:00
Felix Fontein
b682c7a281 Release 1.9.17. 2022-06-17 08:23:20 +02:00
Felix Fontein
d393ea233e Add Apache 2.0 license for Apache 2.0 licensed parts. (#479) 2022-06-17 08:21:08 +02:00
patchback[bot]
563e3a2791 Fix ValueError: excluded_subtrees must be a non-empty list or None (#481) (#482)
(cherry picked from commit b29f238083)

Co-authored-by: Songmin Li <lisongmin@protonmail.com>
2022-06-17 08:02:49 +02:00
Felix Fontein
651f2b8f5d x509_crl: do not crash when signing with Ed25519 or Ed448 (#475) (#480)
* Do not crash when signing with Ed25519 or Ed448.

* Forgot replace.

(cherry picked from commit 297b44f24b)
2022-06-15 22:29:34 +02:00
patchback[bot]
077bcba377 Skip Ansible 2.9 coverage reporting with new AZP container. (#476) (#477)
ci_coverage

(cherry picked from commit 429ed5faa5)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-06-14 20:32:32 +00:00
patchback[bot]
9084df1e5c Bump AZP container version. (#472) (#474)
(cherry picked from commit b3029f75cd)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-06-13 21:58:14 +02:00
patchback[bot]
2bb6d7f49d Disable broken CI platforms. (#469) (#470)
(cherry picked from commit f7bc3aa77c)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-06-06 09:36:44 +02:00
Felix Fontein
83cf13a483 Next expected release is 1.9.17. 2022-06-02 12:46:50 +02:00
Felix Fontein
1b22a88b80 Release 1.9.16. 2022-06-02 12:27:44 +02:00
patchback[bot]
252c1a7236 Add simplified_bsd.txt license file (#467) (#468)
* Add simplified_bsd.txt and adjust references.

* Add changelog.

(cherry picked from commit ccd66419f4)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-06-02 08:14:26 +02:00
Felix Fontein
7f371f6915 Update release summary for 1.9.16. 2022-06-02 07:48:31 +02:00
patchback[bot]
1ccd48efba Remove FreeBSD 12.1 from CI. The remote seems to be no longer working. (#459) (#461)
(cherry picked from commit fd0048827d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-05-20 15:33:08 +00:00
patchback[bot]
a4c077c388 certificate_complete_chain: do not stop execution on unsupported algorithm (#457) (#458)
* Do not stop execution on unsupported algorithm.

* Fix typo.

(cherry picked from commit c49102d688)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-05-20 12:47:59 +02:00
Felix Fontein
2b1e85c86c Next expected release is 1.9.16. 2022-05-16 12:52:21 +02:00
Felix Fontein
a68f119afe Release 1.9.15. 2022-05-16 12:26:27 +02:00
Felix Fontein
9ebf7d668f [stable-1] Add PSF-license.txt file (#455)
* Add PSF-license.txt file. (#453)

(cherry picked from commit 5664bfe4b6)

* Update with actual CPython 3.9.5 license. (#454)

(cherry picked from commit 7183596586)
2022-05-16 09:02:58 +02:00
Felix Fontein
4570c481ef Prepare 1.9.15 release. 2022-05-16 07:14:00 +02:00
Felix Fontein
5d56629d10 Prepare 1.9.15 release. 2022-05-09 20:52:41 +02:00
Felix Fontein
37c7100c8b Release 1.9.14. 2022-05-09 20:29:24 +02:00
Felix Fontein
35266bda0e Prepare 1.9.14 release. 2022-05-03 19:25:24 +02:00
Felix Fontein
6a90a43995 Fix stable-1 for new cryptography 37.0.0 release (#446)
* Fix empty check for openssl_pkcs12 tests.

* Prevent crash if PyOpenSSL cannot be imported because of an AttributeError.

* Add changelog fragment.

* Fix constraints file.

* Use Python 2.7 instead of 3.5 for 2.9 cloud tests (pip module is broken).

* Prevent upgrading cryptography on ansible-core 2.12's default container with Python 3.9.
2022-04-26 22:33:13 +02:00
Felix Fontein
096262b6f1 Fix crash in x509_crl when certificate issuer is specified (#441) (#442)
* Fix x509_crl certificate issuer issue.

* Add tests.

* Add changelog fragment.

(cherry picked from commit 9d03178b00)
2022-04-18 10:19:27 +02:00
patchback[bot]
03df636e5e Switch from antsibull to antsibull-docs. (#438) (#439)
(cherry picked from commit c7f581daad)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-04-10 10:58:54 +02:00
Felix Fontein
02bc9de560 On the 'default' image with Python 3.8, cryptography 2.8 is installed from the
system packages, but cryptography 36.0.1 is already present and shadows the
one from the system packages. Since PyOpenSSL is also installed from the system
packages, there is a version mismatch between the two. Temporarily disable the
Python 3.8 tests with the 'default' image on ansible-core 2.13 until this is
resolved, or the stable-1 branch is EOL (whatever comes first).
2022-04-03 15:08:55 +02:00
patchback[bot]
8fe0a2450e Replace antsibull-lint collection-docs with antsibull-docs lint-collection-docs. (#432) (#433)
(cherry picked from commit bc00c30faf)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-03-30 08:13:40 +02:00
Felix Fontein
d8bb99d547 Replace devel with stable-2.13 in stable-1 CI. (#424) 2022-03-29 06:19:54 +02:00
patchback[bot]
79f9ce437a openssh_* - catch and report top-level exceptions via fail_json (#417) (#418)
* ensure exceptions are properly reported

* adding changelog fragment

* applying review suggestions

* typo

* adding back exception msg

(cherry picked from commit 033bab7db1)

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
2022-03-08 20:25:38 +01:00
Felix Fontein
6cb02818ef Next expected release is 1.9.14. 2022-03-04 08:06:24 +01:00
Felix Fontein
e94fb2dff2 Release 1.9.13. 2022-03-04 07:38:05 +01:00
Felix Fontein
14c39b1f99 Prepare 1.9.13 release. 2022-03-03 21:17:36 +01:00
patchback[bot]
6dafa5954e fixing public key return value docs (#412) (#414)
(cherry picked from commit 010f1a4d2d)

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
2022-03-02 14:09:20 +01:00
patchback[bot]
da1dd21a9e Fix parsing of lsblk output. (#410) (#413)
(cherry picked from commit 0d4b3ed991)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-03-02 14:05:07 +01:00
Felix Fontein
011036b87f Next expected release is 1.9.13. 2022-02-21 22:22:36 +01:00
Felix Fontein
35ef2edb3f Release 1.9.12. 2022-02-21 21:48:14 +01:00
Felix Fontein
ebcf866891 Prepare 1.9.12 release. 2022-02-19 18:53:26 +01:00
Felix Fontein
60c6d87b05 [stable-1] x509_certificate: regenerate certificate on CA's subject change (#406)
* Regenerate certificate on CA's subject change. (#402)

(cherry picked from commit 3ebc132c03)

* Add fix for PyOpenSSL backend.

* x509_certificate: check existing certificate's signature for selfsigned and ownca provider (#407)

* Verify whether signature matches.

* Add changelog fragment.

* Forgot imports.

* Fix wrong name.

* Check whether the CA private key fits to the CA certificate. Use correct key in tests.

* Refactor code.

(cherry picked from commit 28729657ac)

* There doesn't seem a way to do this with pyOpenSSL.
2022-02-19 17:51:28 +00:00
patchback[bot]
2aa38fe247 certificate_complete_chain: handle duplicate intermediate subjects (#403) (#405)
* Allow multiple intermediate CAs to have same subject.

* Add tests.

* Fix test name.

* Don't use CN for SAN.

* Make a bit more compatible.

* Include jinja2 compat for CentOS 6.

(cherry picked from commit 11a14543c8)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-02-14 18:04:54 +01:00
Felix Fontein
d19faa1627 Next expected release is 1.9.12. 2022-02-05 21:45:27 +01:00
Felix Fontein
e910f299b9 Release 1.9.11. 2022-02-05 21:28:22 +01:00
Felix Fontein
2ebf26854e Prepare 1.9.11 release. 2022-02-05 20:19:18 +01:00
Andrew Pantuso
7ff067937a openssh_cert - fix full_idempotence for host certificates (#396) (#397)
* fixing host cert idempotence

* adding changelog fragment

(cherry picked from commit a307618872)
2022-02-05 10:00:07 +01:00
Felix Fontein
2727b74cc7 Next expected release is 1.9.11. 2022-02-01 06:18:36 +01:00
Felix Fontein
3bb9c5f9a7 Release 1.9.10. 2022-02-01 05:49:00 +01:00
patchback[bot]
5623590c77 Drop CentOS 8 from CI. (#393) (#394)
(cherry picked from commit 23226dce8f)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-02-01 05:24:21 +01:00
Felix Fontein
29050913b3 Prepare 1.9.10 release. 2022-01-31 06:03:15 +01:00
patchback[bot]
aead2bf783 Set LANG and similar env variables to prevent translated cryptsetup output. (#388) (#390)
(cherry picked from commit ea2e45d63f)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-30 21:57:31 +01:00
patchback[bot]
2aaae70372 [PR #387/5abfe8fc backport][stable-1] PyOpenSSL 22.0.0 no longer supports Python 2.7 (#389)
* PyOpenSSL 22.0.0 no longer supports Python 2.7. (#387)

(cherry picked from commit 5abfe8fca9)

* Do not install PyOpenSSL from PyPi if cryptography cannot be updated - at least on FreeBSD 13.0, latest PyOpenSSL requires a cryptography upgrade, which breaks CI.

* Revert "Do not install PyOpenSSL from PyPi if cryptography cannot be updated - at least on FreeBSD 13.0, latest PyOpenSSL requires a cryptography upgrade, which breaks CI."

This reverts commit 16f9145653.

* Try another approach.

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-30 15:23:29 +01:00
patchback[bot]
de29a258c6 Fix indentation of when in example. (#382) (#383)
(cherry picked from commit a467f036b1)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-19 04:54:17 +00:00
patchback[bot]
8ca5b480e4 Update CI matrix for Remote Devel (#377) (#378)
* Update CI matrix for Remote Devel.

* Add Python info entries.

(cherry picked from commit cd5ed011a5)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-13 09:52:59 +01:00
Felix Fontein
f6ec785b0d Next expected release is 1.9.10. 2022-01-11 07:28:54 +01:00
Felix Fontein
4834c0cb4b Release 1.9.9. 2022-01-11 07:04:43 +01:00
patchback[bot]
1f0016c616 Fix comment. (#372) (#373)
(cherry picked from commit 1b0fcde862)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-06 15:10:40 +01:00
patchback[bot]
74e4be139f Use vendored copy of distutils.version. (#369) (#370)
(cherry picked from commit 46f39efc43)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-05 22:22:25 +01:00
patchback[bot]
b584336b62 Fix typo. (#367) (#368)
(cherry picked from commit 3e307fe062)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-05 20:36:01 +01:00
patchback[bot]
20b0d7a298 certificate_complete_chain: avoid infinite loops, and double roots when root certificate was already part of chain (#360) (#366)
* Avoid infinite loops, and double roots when root certificate was already part of chain.

* Refactor tests for readability.

(cherry picked from commit 6ee238d961)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-04 07:27:15 +01:00
patchback[bot]
5741f24aa9 Fix indentation in docs. (#364) (#365)
(cherry picked from commit f3e431912d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-03 21:47:20 +01:00
patchback[bot]
b5dfc9fc75 Improve changed / nonchanged validations by using new modules from community.internal_test_tools (#183) (#361)
* Use modules from internal_test_tools instead of stat workaround to check whether file actually changed.

* Properly add testing dependency.

(cherry picked from commit 471506c5d4)

Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-03 19:30:21 +01:00
patchback[bot]
4318e95618 Feature/rename test cases (#356) (#358)
* Name test tasks in a more explicite manner

* Space test + verification blocks apart

* Apply suggestions from code review

Co-authored-by: Jens Heinrich <github.com/JensHeinrich>
Co-authored-by: Felix Fontein <felix@fontein.de>
(cherry picked from commit 2c05221d89)

Co-authored-by: Jens Heinrich <59469646+JensHeinrich@users.noreply.github.com>
2021-12-30 10:31:04 +01:00
Felix Fontein
113cbb6eb8 Prepare for distutils.version being removed in Python 3.12 (#353) (#354)
* Prepare for distutils.version being removed in Python 2.12.

* Fix copy'n'paste error.

* Re-add Loose prefix.

* Fix Python version typo.

* Improve formulation.

* Move message into own line.

* Fix casing, now that the object is no longer called Version.

(cherry picked from commit a539cd6939)
2021-12-24 12:15:45 +01:00
Felix Fontein
3ebed060b7 Next expected release is 1.9.9. 2021-12-13 21:11:58 +01:00
Felix Fontein
270ef0db47 Release 1.9.8. 2021-12-13 20:20:11 +01:00
patchback[bot]
0b306b436a Fix module reference in example (#351) (#352)
openssl_privatekey -> openssl_publickey

(cherry picked from commit 45b7aa797e)

Co-authored-by: Jasmine Hegman <jasmine@jhegman.com>
2021-12-13 06:03:13 +00:00
Felix Fontein
c5519bc557 Prepare 1.9.8 release. 2021-12-13 07:01:04 +01:00
patchback[bot]
23d6f2ae75 Fix CSR copy/paste error (#349) (#350)
The first case about ca_csr has been copy/pasted.
But in the following cases, the CSR must be the certificate csr.

(cherry picked from commit 32dab841d7)

Co-authored-by: Bruno Vernay <brunovern.a@gmail.com>
2021-12-09 21:09:52 +01:00
Felix Fontein
9da7c54ae9 Next release will be 1.9.8. 2021-11-22 12:18:34 +01:00
Felix Fontein
928cb3aa9b Release 1.9.7. 2021-11-22 11:41:02 +01:00
patchback[bot]
3e6815d73f [PR #331/3f40795a backport][stable-1] Extension parsing: add new fallback code which uses the new cryptography API (#345)
* Extension parsing: add new fallback code which uses the new cryptography API (#331)

* Add new code as fallback which re-serializes de-serialized extensions using the new cryptography API.

* Forgot Base64 encoding.

* Add extension by OID tests.

* There's one value which is different with the new code.

* Differences in CI.

* Working around older Jinjas.

* Value depends on which SAN was included.

* Force complete CI run now since cryptography 36.0.0 is out.

ci_complete

(cherry picked from commit 3f40795a98)

* Adjust tests.

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-22 08:54:08 +01:00
patchback[bot]
cb08f56066 Use new PKCS#12 deserialization code from cryptography 36.0.0 if available (#302) (#344)
* Use new PKCS#12 deserialization code from cryptography 36.0.0 if available.

* Refactor into smaller functions.

* Force complete CI run now since cryptography 36.0.0 is out.

ci_complete

(cherry picked from commit 73bc0f5de7)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-22 08:27:33 +01:00
Felix Fontein
e3f486a063 Prepare 1.9.7 releaese. 2021-11-22 07:41:21 +01:00
patchback[bot]
0a1e25e16a Fix collection dependency installation in CI. (#341) (#342)
(cherry picked from commit f1a6baadc7)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-18 21:21:57 +01:00
patchback[bot]
ff4966ad3f Fix compatibility to fetch_url change in ansible-core devel (#339) (#340)
* Fix compatibility to fetch_url change in ansible-core devel.

* Adjust tests.

(cherry picked from commit 5de50b9f91)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-17 21:46:13 +01:00
patchback[bot]
0cb10be2d5 Replace RHEL 8.4 by RHEL 8.5 for devel. (#337) (#338)
(cherry picked from commit cf0d2679aa)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-17 07:07:31 +00:00
patchback[bot]
901863989b This is no longer a problem with the dev version of cryptography. (#335) (#336)
(cherry picked from commit 2d388bf8d0)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-13 23:31:52 +01:00
Felix Fontein
99377764c1 Replace Bash codecov uploader by new Python codecov uploader. (#333) (#334)
ci_coverage

(cherry picked from commit 056a86fcae)
2021-11-13 13:22:01 +01:00
patchback[bot]
426d70fbcf luks_device: add built-in signature wiper to work around older wipefs versions with LUKS2 containers (#327) (#330)
* Use 'cryptsetup erase' to kill LUKS signature.

* Adjust unit test.

* Use own wiper for LUKS headers.

* Add comments.

* Fix tests.

* Update changelog.

* Remove 'cryptsetup erase'.

* Improve error messages.

(cherry picked from commit ebbfd7c56f)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-11 07:17:45 +01:00
patchback[bot]
f315722b31 Replace Fedora 33 with Fedora 35 for devel tests. (#328) (#329)
(cherry picked from commit 91d98c4413)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-09 05:52:42 +01:00
patchback[bot]
73afe8e742 acme_certificate: fix crash when using fullchain_dest (#324) (#325)
* Fix crash when using fullchain_dest.

* Adjust changelog.

* Update plugins/module_utils/acme/backend_cryptography.py

Co-authored-by: Ajpantuso <ajpantuso@gmail.com>

Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
(cherry picked from commit 51b6bb210d)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-11-05 09:35:10 +01:00
Felix Fontein
db67b8a857 Next expected release is 1.9.7. 2021-10-30 18:21:40 +02:00
Felix Fontein
e05475d58a Release 1.9.6. 2021-10-30 17:48:48 +02:00
Felix Fontein
dceee8f50e Prepare 1.9.6 release. 2021-10-30 17:07:49 +02:00
Felix Fontein
b893252ad1 [stable-1] cryptography support: improve Python 2 Unicode handling (#314)
* Improve Python 2 Unicode handling. (#313)

(cherry picked from commit eb8dabce84)

* Remove test since it doesn't work with pyOpenSSL.

* Completely remove test.

* Update plugins/module_utils/crypto/cryptography_support.py
2021-10-29 21:10:57 +02:00
patchback[bot]
0755a2b657 Remove centos8 for devel from CI. (#307) (#308)
(cherry picked from commit 78b27ffedb)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-10-16 09:27:45 +02:00
Felix Fontein
90bf8b0b2e Adjust to latest devel changes.
(cherry picked from commit e735bdab60)
2021-10-12 19:32:56 +02:00
patchback[bot]
5ff28c751d Fix shellcheck error. (#303) (#304)
(cherry picked from commit c68bfedbaa)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-10-08 15:10:41 +02:00
Felix Fontein
7b08edb5a4 Next expected release is 1.9.6. 2021-10-06 13:35:44 +02:00
Felix Fontein
fbadcbeb29 Release 1.9.5. 2021-10-06 13:08:43 +02:00
Felix Fontein
e991375f55 Prepare 1.9.5 release. 2021-10-05 22:28:03 +02:00
Felix Fontein
33c99014ae [stable-1] Fix PyOpenSSL backends with cryptography 35.0.0 (#300)
* Try to make compatible with cryptography 35.0.0.

* Forgot import.

ci_complete

* Add changelog fragment.
2021-10-05 22:19:11 +02:00
patchback[bot]
fbd6ff6ead x509_certificate: document that *notBefore/*notAfter are not used for idempotency (#298) (#301)
* Document that *notBefore/*notAfter are not used for idempotency.

* Change formulation.

(cherry picked from commit ed03841fd1)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-10-03 22:20:34 +02:00
patchback[bot]
c4ab2eb3b5 Fix PKCS#12 friendly name extraction for cryptography 35.0.0. (#296) (#299)
(cherry picked from commit d6c0d53442)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-10-03 21:37:37 +02:00
patchback[bot]
44b6df0ce5 Support cryptography 35.0.0 for all modules except openssl_pkcs12 (#294) (#297)
* Add some workarounds for cryptography 35.0.0.

* Make fix work with very old cryptography versions as well (which supported multiple backends).

* [TEMP] Disable openssl_pkcs12 tests to see whether everything else works.

* Revert "[TEMP] Disable openssl_pkcs12 tests to see whether everything else works."

This reverts commit 3f905bc795.

* Add changelog fragment.

* Remove unnecessary assignment.

* Simplify code change.

* [TEMP] Disable openssl_pkcs12 tests to see whether everything else works.

* Revert "[TEMP] Disable openssl_pkcs12 tests to see whether everything else works."

This reverts commit fdb210528e.

(cherry picked from commit a2a7d94055)

Co-authored-by: Felix Fontein <felix@fontein.de>
2021-10-03 17:23:35 +02:00
Felix Fontein
14a42505a9 Ansible-core devel dropped support for Python 2.6.
(cherry picked from commit 2a7e452cf8)
2021-10-01 13:46:08 +02:00
Felix Fontein
44cbd33cb7 Run CI on stable branches only once per week.
(cherry picked from commit 24e7d07973)
2021-10-01 13:44:44 +02:00
Felix Fontein
4411a71d06 Temporarily fix CI for cryptography 35.0.0 release. (#292)
(cherry picked from commit 57c364fe87)
2021-09-30 13:45:38 +02:00
Felix Fontein
bfe37bc668 Next expected 1.x.y release is 1.9.5. 2021-09-28 17:31:41 +02:00
104 changed files with 2718 additions and 559 deletions

View File

@@ -19,6 +19,11 @@ schedules:
branches: branches:
include: include:
- main - main
- cron: 0 12 * * 0
displayName: Weekly (old stable branches)
always: true
branches:
include:
- stable-* - stable-*
variables: variables:
@@ -36,25 +41,25 @@ variables:
resources: resources:
containers: containers:
- container: default - container: default
image: quay.io/ansible/azure-pipelines-test-container:1.9.0 image: quay.io/ansible/azure-pipelines-test-container:3.0.0
pool: Standard pool: Standard
stages: stages:
### Sanity & units ### Sanity & units
- stage: Ansible_devel - stage: Ansible_2_13
displayName: Sanity & Units devel displayName: Sanity & Units 2.13
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/matrix.yml - template: templates/matrix.yml
parameters: parameters:
targets: targets:
- name: Sanity - name: Sanity
test: 'devel/sanity/1' test: '2.13/sanity/1'
- name: Sanity Extra # Only on devel - name: Sanity Extra # Only on devel
test: 'devel/sanity/extra' test: '2.13/sanity/extra'
- name: Units - name: Units
test: 'devel/units/1' test: '2.13/units/1'
- stage: Ansible_2_12 - stage: Ansible_2_12
displayName: Sanity & Units 2.12 displayName: Sanity & Units 2.12
dependsOn: [] dependsOn: []
@@ -66,58 +71,21 @@ stages:
test: '2.12/sanity/1' test: '2.12/sanity/1'
- name: Units - name: Units
test: '2.12/units/1' test: '2.12/units/1'
- stage: Ansible_2_11
displayName: Sanity & Units 2.11
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.11/sanity/1'
- name: Units
test: '2.11/units/1'
- stage: Ansible_2_10
displayName: Sanity & Units 2.10
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.10/sanity/1'
- name: Units
test: '2.10/units/1'
- stage: Ansible_2_9
displayName: Sanity & Units 2.9
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.9/sanity/1'
- name: Units
test: '2.9/units/1'
### Docker ### Docker
- stage: Docker_devel - stage: Docker_2_13
displayName: Docker devel displayName: Docker 2.13
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/matrix.yml - template: templates/matrix.yml
parameters: parameters:
testFormat: devel/linux/{0}/1 testFormat: 2.13/linux/{0}/1
targets: targets:
- name: CentOS 6
test: centos6
- name: CentOS 7 - name: CentOS 7
test: centos7 test: centos7
- name: CentOS 8
test: centos8
- name: Fedora 33
test: fedora33
- name: Fedora 34 - name: Fedora 34
test: fedora34 test: fedora34
- name: Fedora 35
test: fedora35
- name: openSUSE 15 py2 - name: openSUSE 15 py2
test: opensuse15py2 test: opensuse15py2
- name: openSUSE 15 py3 - name: openSUSE 15 py3
@@ -134,46 +102,14 @@ stages:
parameters: parameters:
testFormat: 2.12/linux/{0}/1 testFormat: 2.12/linux/{0}/1
targets: targets:
- name: CentOS 8 - name: CentOS 6
test: centos8 test: centos6
- name: Fedora 33 - name: Fedora 33
test: fedora33 test: fedora33
- name: openSUSE 15 py3 - name: openSUSE 15 py3
test: opensuse15 test: opensuse15
- name: Ubuntu 20.04 - name: Ubuntu 20.04
test: ubuntu2004 test: ubuntu2004
- stage: Docker_2_11
displayName: Docker 2.11
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.11/linux/{0}/1
targets:
- name: CentOS 7
test: centos7
- name: CentOS 8
test: centos8
- name: Fedora 32
test: fedora32
- name: openSUSE 15 py2
test: opensuse15py2
- name: Ubuntu 18.04
test: ubuntu1804
- stage: Docker_2_10
displayName: Docker 2.10
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.10/linux/{0}/1
targets:
- name: CentOS 6
test: centos6
- name: Fedora 31
test: fedora31
- name: Ubuntu 16.04
test: ubuntu1604
- stage: Docker_2_9 - stage: Docker_2_9
displayName: Docker 2.9 displayName: Docker 2.9
dependsOn: [] dependsOn: []
@@ -194,22 +130,22 @@ stages:
test: ubuntu1804 test: ubuntu1804
### Remote ### Remote
- stage: Remote_devel - stage: Remote_2_13
displayName: Remote devel displayName: Remote 2.13
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/matrix.yml - template: templates/matrix.yml
parameters: parameters:
testFormat: devel/{0}/1 testFormat: 2.13/{0}/1
targets: targets:
- name: macOS 11.1 - name: macOS 12.0
test: macos/11.1 test: macos/12.0
- name: RHEL 7.9 - name: RHEL 7.9
test: rhel/7.9 test: rhel/7.9
- name: RHEL 8.4 - name: RHEL 8.5
test: rhel/8.4 test: rhel/8.5
- name: FreeBSD 12.2 - name: FreeBSD 12.3
test: freebsd/12.2 test: freebsd/12.3
- name: FreeBSD 13.0 - name: FreeBSD 13.0
test: freebsd/13.0 test: freebsd/13.0
- stage: Remote_2_12 - stage: Remote_2_12
@@ -220,66 +156,27 @@ stages:
parameters: parameters:
testFormat: 2.12/{0}/1 testFormat: 2.12/{0}/1
targets: targets:
- name: macOS 11.1 # - name: macOS 11.1
test: macos/11.1 # test: macos/11.1
- name: RHEL 8.4 - name: RHEL 8.4
test: rhel/8.4 test: rhel/8.4
- name: FreeBSD 13.0 - name: FreeBSD 13.0
test: freebsd/13.0 test: freebsd/13.0
- stage: Remote_2_11
displayName: Remote 2.11
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.11/{0}/1
targets:
- name: RHEL 7.9
test: rhel/7.9
- name: RHEL 8.3
test: rhel/8.3
- name: FreeBSD 12.2
test: freebsd/12.2
- stage: Remote_2_10
displayName: Remote 2.10
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.10/{0}/1
targets:
- name: OS X 10.11
test: osx/10.11
- name: macOS 10.15
test: macos/10.15
- name: FreeBSD 12.1
test: freebsd/12.1
- stage: Remote_2_9
displayName: Remote 2.9
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.9/{0}/1
targets:
- name: 'RHEL 7.8'
test: 'rhel/7.8'
### cloud ### cloud
- stage: Cloud_devel - stage: Cloud_2_13
displayName: Cloud devel displayName: Cloud 2.13
dependsOn: [] dependsOn: []
jobs: jobs:
- template: templates/matrix.yml - template: templates/matrix.yml
parameters: parameters:
nameFormat: Python {0} nameFormat: Python {0}
testFormat: devel/cloud/{0}/1 testFormat: 2.13/cloud/{0}/1
targets: targets:
- test: 2.6
- test: 2.7 - test: 2.7
- test: 3.5 - test: 3.5
- test: 3.6 - test: 3.6
- test: 3.7 - test: 3.7
- test: 3.8 # - test: 3.8
- test: 3.9 - test: 3.9
- test: "3.10" - test: "3.10"
- stage: Cloud_2_12 - stage: Cloud_2_12
@@ -291,62 +188,21 @@ stages:
nameFormat: Python {0} nameFormat: Python {0}
testFormat: 2.12/cloud/{0}/1 testFormat: 2.12/cloud/{0}/1
targets: targets:
- test: 2.6
- test: 3.9 - test: 3.9
- stage: Cloud_2_11
displayName: Cloud 2.11
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.11/cloud/{0}/1
targets:
- test: 3.8
- stage: Cloud_2_10
displayName: Cloud 2.10
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.10/cloud/{0}/1
targets:
- test: 3.6
- stage: Cloud_2_9
displayName: Cloud 2.9
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.9/cloud/{0}/1
targets:
- test: 3.5
## Finally ## Finally
- stage: Summary - stage: Summary
condition: succeededOrFailed() condition: succeededOrFailed()
dependsOn: dependsOn:
- Ansible_devel - Ansible_2_13
- Ansible_2_12 - Ansible_2_12
- Ansible_2_11 - Remote_2_13
- Ansible_2_10
- Ansible_2_9
- Remote_devel
- Remote_2_12 - Remote_2_12
- Remote_2_11 - Docker_2_13
- Remote_2_10
- Remote_2_9
- Docker_devel
- Docker_2_12 - Docker_2_12
- Docker_2_11 - Cloud_2_13
- Docker_2_10
- Docker_2_9
- Cloud_devel
- Cloud_2_12 - Cloud_2_12
- Cloud_2_11
- Cloud_2_10
- Cloud_2_9
jobs: jobs:
- template: templates/coverage.yml - template: templates/coverage.yml

View File

@@ -9,9 +9,13 @@ PATH="${PWD}/bin:${PATH}"
mkdir "${agent_temp_directory}/coverage/" mkdir "${agent_temp_directory}/coverage/"
if [[ "$(ansible --version)" =~ \ 2\.9\. ]]; then
exit
fi
options=(--venv --venv-system-site-packages --color -v) options=(--venv --venv-system-site-packages --color -v)
ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}" ansible-test coverage combine --group-by command --export "${agent_temp_directory}/coverage/" "${options[@]}"
if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then
# Only analyze coverage if the installed version of ansible-test supports it. # Only analyze coverage if the installed version of ansible-test supports it.

View File

@@ -0,0 +1,101 @@
#!/usr/bin/env python
"""
Upload code coverage reports to codecov.io.
Multiple coverage files from multiple languages are accepted and aggregated after upload.
Python coverage, as well as PowerShell and Python stubs can all be uploaded.
"""
import argparse
import dataclasses
import pathlib
import shutil
import subprocess
import tempfile
import typing as t
import urllib.request
@dataclasses.dataclass(frozen=True)
class CoverageFile:
name: str
path: pathlib.Path
flags: t.List[str]
@dataclasses.dataclass(frozen=True)
class Args:
dry_run: bool
path: pathlib.Path
def parse_args() -> Args:
parser = argparse.ArgumentParser()
parser.add_argument('-n', '--dry-run', action='store_true')
parser.add_argument('path', type=pathlib.Path)
args = parser.parse_args()
# Store arguments in a typed dataclass
fields = dataclasses.fields(Args)
kwargs = {field.name: getattr(args, field.name) for field in fields}
return Args(**kwargs)
def process_files(directory: pathlib.Path) -> t.Tuple[CoverageFile, ...]:
processed = []
for file in directory.joinpath('reports').glob('coverage*.xml'):
name = file.stem.replace('coverage=', '')
# Get flags from name
flags = name.replace('-powershell', '').split('=') # Drop '-powershell' suffix
flags = [flag if not flag.startswith('stub') else flag.split('-')[0] for flag in flags] # Remove "-01" from stub files
processed.append(CoverageFile(name, file, flags))
return tuple(processed)
def upload_files(codecov_bin: pathlib.Path, files: t.Tuple[CoverageFile, ...], dry_run: bool = False) -> None:
for file in files:
cmd = [
str(codecov_bin),
'--name', file.name,
'--file', str(file.path),
]
for flag in file.flags:
cmd.extend(['--flags', flag])
if dry_run:
print(f'DRY-RUN: Would run command: {cmd}')
continue
subprocess.run(cmd, check=True)
def download_file(url: str, dest: pathlib.Path, flags: int, dry_run: bool = False) -> None:
if dry_run:
print(f'DRY-RUN: Would download {url} to {dest} and set mode to {flags:o}')
return
with urllib.request.urlopen(url) as resp:
with dest.open('w+b') as f:
# Read data in chunks rather than all at once
shutil.copyfileobj(resp, f, 64 * 1024)
dest.chmod(flags)
def main():
args = parse_args()
url = 'https://ansible-ci-files.s3.amazonaws.com/codecov/linux/codecov'
with tempfile.TemporaryDirectory(prefix='codecov-') as tmpdir:
codecov_bin = pathlib.Path(tmpdir) / 'codecov'
download_file(url, codecov_bin, 0o755, args.dry_run)
files = process_files(args.path)
upload_files(codecov_bin, files, args.dry_run)
if __name__ == '__main__':
main()

View File

@@ -1,27 +0,0 @@
#!/usr/bin/env bash
# Upload code coverage reports to codecov.io.
# Multiple coverage files from multiple languages are accepted and aggregated after upload.
# Python coverage, as well as PowerShell and Python stubs can all be uploaded.
set -o pipefail -eu
output_path="$1"
curl --silent --show-error https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh > codecov.sh
for file in "${output_path}"/reports/coverage*.xml; do
name="${file}"
name="${name##*/}" # remove path
name="${name##coverage=}" # remove 'coverage=' prefix if present
name="${name%.xml}" # remove '.xml' suffix
bash codecov.sh \
-f "${file}" \
-n "${name}" \
-X coveragepy \
-X gcov \
-X fix \
-X search \
-X xcode \
|| echo "Failed to upload code coverage report to codecov.io: ${file}"
done

View File

@@ -5,6 +5,10 @@ set -o pipefail -eu
PATH="${PWD}/bin:${PATH}" PATH="${PWD}/bin:${PATH}"
if [[ "$(ansible --version)" =~ \ 2\.9\. ]]; then
exit
fi
if ! ansible-test --help >/dev/null 2>&1; then if ! ansible-test --help >/dev/null 2>&1; then
# Install the devel version of ansible-test for generating code coverage reports. # Install the devel version of ansible-test for generating code coverage reports.
# This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs). # This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs).
@@ -12,4 +16,4 @@ if ! ansible-test --help >/dev/null 2>&1; then
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
fi fi
ansible-test coverage xml --stub --venv --venv-system-site-packages --color -v ansible-test coverage xml --group-by command --stub --venv --venv-system-site-packages --color -v

View File

@@ -33,7 +33,7 @@ jobs:
summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml" summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
displayName: Publish to Azure Pipelines displayName: Publish to Azure Pipelines
condition: gt(variables.coverageFileCount, 0) condition: gt(variables.coverageFileCount, 0)
- bash: .azure-pipelines/scripts/publish-codecov.sh "$(outputPath)" - bash: .azure-pipelines/scripts/publish-codecov.py "$(outputPath)"
displayName: Publish to codecov.io displayName: Publish to codecov.io
condition: gt(variables.coverageFileCount, 0) condition: gt(variables.coverageFileCount, 0)
continueOnError: true continueOnError: true

181
.github/workflows/ansible-test.yml vendored Normal file
View File

@@ -0,0 +1,181 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
name: EOL CI
on:
# Run EOL CI against all pushes (direct commits, also merged PRs), Pull Requests
push:
branches:
- stable-1
pull_request:
jobs:
sanity:
name: EOL Sanity (Ⓐ${{ matrix.ansible }})
strategy:
matrix:
ansible:
- '2.9'
- '2.10'
- '2.11'
# Ansible-test on various stable branches does not yet work well with cgroups v2.
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
# image for these stable branches. The list of branches where this is necessary will
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
# for the latest list.
runs-on: >-
${{ contains(fromJson(
'["2.9", "2.10", "2.11"]'
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
steps:
- name: Perform sanity testing
uses: felixfontein/ansible-test-gh-action@main
with:
ansible-core-version: stable-${{ matrix.ansible }}
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
pull-request-change-detection: 'true'
testing-type: sanity
units:
# Ansible-test on various stable branches does not yet work well with cgroups v2.
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
# image for these stable branches. The list of branches where this is necessary will
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
# for the latest list.
runs-on: >-
${{ contains(fromJson(
'["2.9", "2.10", "2.11"]'
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
name: EOL Units (Ⓐ${{ matrix.ansible }})
strategy:
# As soon as the first unit test fails, cancel the others to free up the CI queue
fail-fast: true
matrix:
ansible:
- '2.9'
- '2.10'
- '2.11'
steps:
- name: >-
Perform unit testing against
Ansible version ${{ matrix.ansible }}
uses: felixfontein/ansible-test-gh-action@main
with:
ansible-core-version: stable-${{ matrix.ansible }}
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
pull-request-change-detection: 'true'
testing-type: units
integration:
# Ansible-test on various stable branches does not yet work well with cgroups v2.
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
# image for these stable branches. The list of branches where this is necessary will
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
# for the latest list.
runs-on: >-
${{ contains(fromJson(
'["2.9", "2.10", "2.11"]'
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
name: EOL I (Ⓐ${{ matrix.ansible }}+${{ matrix.docker }}+py${{ matrix.python }}:${{ matrix.target }})
strategy:
fail-fast: false
matrix:
ansible:
- ''
docker:
- ''
python:
- ''
target:
- ''
exclude:
- ansible: ''
include:
# 2.9
- ansible: '2.9'
docker: centos6
python: ''
target: shippable/posix/group1/
- ansible: '2.9'
docker: centos7
python: ''
target: shippable/posix/group1/
- ansible: '2.9'
docker: fedora31
python: ''
target: shippable/posix/group1/
- ansible: '2.9'
docker: ubuntu1604
python: ''
target: shippable/posix/group1/
- ansible: '2.9'
docker: ubuntu1804
python: ''
target: shippable/posix/group1/
- ansible: '2.9'
docker: default
python: '2.7'
target: shippable/cloud/group1/
# 2.10
- ansible: '2.10'
docker: centos6
python: ''
target: shippable/posix/group1/
- ansible: '2.10'
docker: fedora31
python: ''
target: shippable/posix/group1/
- ansible: '2.10'
docker: ubuntu1604
python: ''
target: shippable/posix/group1/
- ansible: '2.10'
docker: default
python: '3.6'
target: shippable/cloud/group1/
# 2.11
- ansible: '2.11'
docker: centos7
python: ''
target: shippable/posix/group1/
- ansible: '2.11'
docker: fedora32
python: ''
target: shippable/posix/group1/
- ansible: '2.11'
docker: opensuse15py2
python: ''
target: shippable/posix/group1/
- ansible: '2.11'
docker: ubuntu1804
python: ''
target: shippable/posix/group1/
- ansible: '2.11'
docker: default
python: '3.8'
target: shippable/cloud/group1/
steps:
- name: >-
Perform integration testing against
Ansible version ${{ matrix.ansible }}
under Python ${{ matrix.python }}
uses: felixfontein/ansible-test-gh-action@main
with:
ansible-core-version: stable-${{ matrix.ansible }}
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
docker-image: ${{ matrix.docker }}
integration-continue-on-error: 'false'
integration-diff: 'false'
integration-retry-on-error: 'true'
pre-test-cmd: >-
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools
;
git clone --depth=1 --single-branch https://github.com/ansible-collections/community.general.git ../../community/general
pull-request-change-detection: 'true'
target: ${{ matrix.target }}
target-python-version: ${{ matrix.python }}
testing-type: integration

202
Apache-2.0.txt Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -5,6 +5,235 @@ Community Crypto Release Notes
.. contents:: Topics .. contents:: Topics
v1.9.20
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- openssl_publickey_info - do not crash with internal error when public key cannot be parsed (https://github.com/ansible-collections/community.crypto/pull/551).
v1.9.19
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- openssl_privatekey_pipe - ensure compatibility with newer versions of ansible-core (https://github.com/ansible-collections/community.crypto/pull/515).
v1.9.18
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- openssl_pkcs12 - when using the pyOpenSSL backend, do not crash when trying to read non-existing other certificates (https://github.com/ansible-collections/community.crypto/issues/486, https://github.com/ansible-collections/community.crypto/pull/487).
v1.9.17
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- Include ``Apache-2.0.txt`` file for ``plugins/module_utils/crypto/_obj2txt.py`` and ``plugins/module_utils/crypto/_objects_data.py``.
- openssl_csr - the module no longer crashes with 'permitted_subtrees/excluded_subtrees must be a non-empty list or None' if only one of ``name_constraints_permitted`` and ``name_constraints_excluded`` is provided (https://github.com/ansible-collections/community.crypto/issues/481).
- x509_crl - do not crash when signing CRL with Ed25519 or Ed448 keys (https://github.com/ansible-collections/community.crypto/issues/473, https://github.com/ansible-collections/community.crypto/pull/474).
v1.9.16
=======
Release Summary
---------------
Maintenance and bugfix release.
Bugfixes
--------
- Include ``simplified_bsd.txt`` license file for the ECS module utils.
- certificate_complete_chain - do not stop execution if an unsupported signature algorithm is encountered; warn instead (https://github.com/ansible-collections/community.crypto/pull/457).
v1.9.15
=======
Release Summary
---------------
Maintenance release.
Bugfixes
--------
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
v1.9.14
=======
Release Summary
---------------
Regular bugfix release.
Bugfixes
--------
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography version (https://github.com/ansible-collections/community.crypto/pull/446).
- openssh_* modules - fix exception handling to report traceback to users for enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
- x509_crl - fix crash when ``issuer`` for a revoked certificate is specified (https://github.com/ansible-collections/community.crypto/pull/441).
v1.9.13
=======
Release Summary
---------------
Regular bugfix release.
Bugfixes
--------
- luks_device - fix parsing of ``lsblk`` output when device name ends with ``crypt`` (https://github.com/ansible-collections/community.crypto/issues/409, https://github.com/ansible-collections/community.crypto/pull/410).
v1.9.12
=======
Release Summary
---------------
Regular bugfix release.
Bugfixes
--------
- certificate_complete_chain - allow multiple potential intermediate certificates to have the same subject (https://github.com/ansible-collections/community.crypto/issues/399, https://github.com/ansible-collections/community.crypto/pull/403).
- x509_certificate - for the ``ownca`` provider, check whether the CA private key actually belongs to the CA certificate. This fix only covers the ``cryptography`` backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - regenerate certificate when the CA's public key changes for ``provider=ownca``. This fix only covers the ``cryptography`` backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - regenerate certificate when the CA's subject changes for ``provider=ownca`` (https://github.com/ansible-collections/community.crypto/issues/400, https://github.com/ansible-collections/community.crypto/pull/402).
- x509_certificate - regenerate certificate when the private key changes for ``provider=selfsigned``. This fix only covers the ``cryptography`` backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
Known Issues
------------
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl`` backend, changing the CA's public key does not cause regeneration of the certificate (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl`` backend, it is possible to specify a CA private key which is not related to the CA certificate (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - when using the ``selfsigned`` provider with the ``pyopenssl`` backend, changing the private key does not cause regeneration of the certificate (https://github.com/ansible-collections/community.crypto/pull/407).
v1.9.11
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- openssh_cert - fixed false ``changed`` status for ``host`` certificates when using ``full_idempotence`` (https://github.com/ansible-collections/community.crypto/issues/395, https://github.com/ansible-collections/community.crypto/pull/396).
v1.9.10
=======
Release Summary
---------------
Regular bugfix release.
Bugfixes
--------
- luks_devices - set ``LANG`` and similar environment variables to avoid translated output, which can break some of the module's functionality like key management (https://github.com/ansible-collections/community.crypto/pull/388, https://github.com/ansible-collections/community.crypto/issues/385).
v1.9.9
======
Bugfixes
--------
- Various modules and plugins - use vendored version of ``distutils.version`` instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.crypto/pull/353).
- certificate_complete_chain - do not append root twice if the chain already ends with a root certificate (https://github.com/ansible-collections/community.crypto/pull/360).
- certificate_complete_chain - do not hang when infinite loop is found (https://github.com/ansible-collections/community.crypto/issues/355, https://github.com/ansible-collections/community.crypto/pull/360).
v1.9.8
======
Release Summary
---------------
Documentation fix release. No actual code changes.
v1.9.7
======
Release Summary
---------------
Bugfix release with extra forward compatibility for newer versions of cryptography.
Minor Changes
-------------
- acme_* modules - fix usage of ``fetch_url`` with changes in latest ansible-core ``devel`` branch (https://github.com/ansible-collections/community.crypto/pull/339).
Bugfixes
--------
- acme_certificate - avoid passing multiple certificates to ``cryptography``'s X.509 certificate loader when ``fullchain_dest`` is used (https://github.com/ansible-collections/community.crypto/pull/324).
- get_certificate, openssl_csr_info, x509_certificate_info - add fallback code for extension parsing that works with cryptography 36.0.0 and newer. This code re-serializes de-serialized extensions and thus can return slightly different values if the extension in the original CSR resp. certificate was not canonicalized correctly. This code is currently used as a fallback if the existing code stops working, but we will switch it to be the main code in a future release (https://github.com/ansible-collections/community.crypto/pull/331).
- luks_device - now also runs a built-in LUKS signature cleaner on ``state=absent`` to make sure that also the secondary LUKS2 header is wiped when older versions of wipefs are used (https://github.com/ansible-collections/community.crypto/issues/326, https://github.com/ansible-collections/community.crypto/pull/327).
- openssl_pkcs12 - use new PKCS#12 deserialization infrastructure from cryptography 36.0.0 if available (https://github.com/ansible-collections/community.crypto/pull/302).
v1.9.6
======
Release Summary
---------------
Regular bugfix release.
Bugfixes
--------
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
v1.9.5
======
Release Summary
---------------
Bugfix release to fully support cryptography 35.0.0.
Bugfixes
--------
- get_certificate - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
- openssl_pkcs12 - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/296).
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
v1.9.4 v1.9.4
====== ======

48
PSF-license.txt Normal file
View File

@@ -0,0 +1,48 @@
PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
--------------------------------------------
1. This LICENSE AGREEMENT is between the Python Software Foundation
("PSF"), and the Individual or Organization ("Licensee") accessing and
otherwise using this software ("Python") in source or binary form and
its associated documentation.
2. Subject to the terms and conditions of this License Agreement, PSF hereby
grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
analyze, test, perform and/or display publicly, prepare derivative works,
distribute, and otherwise use Python alone or in any derivative version,
provided, however, that PSF's License Agreement and PSF's notice of copyright,
i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 Python Software Foundation;
All Rights Reserved" are retained in Python alone or in any derivative version
prepared by Licensee.
3. In the event Licensee prepares a derivative work that is based on
or incorporates Python or any part thereof, and wants to make
the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to Python.
4. PSF is making Python available to Licensee on an "AS IS"
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
INFRINGE ANY THIRD PARTY RIGHTS.
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between PSF and
Licensee. This License Agreement does not grant permission to use PSF
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using Python, Licensee
agrees to be bound by the terms and conditions of this License
Agreement.

View File

@@ -11,7 +11,7 @@ Please note that this collection does **not** support Windows targets.
## Tested with Ansible ## Tested with Ansible
Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11 and ansible-core 2.12 releases and the current development version of ansible-core. Ansible versions before 2.9.10 are not supported. Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11, ansible-core 2.12 and ansible-core 2.13 releases. Ansible versions before 2.9.10 are not supported.
## External requirements ## External requirements

View File

@@ -528,6 +528,148 @@ releases:
changes: changes:
release_summary: Accidental 1.9.1 release. Identical to 1.9.0. release_summary: Accidental 1.9.1 release. Identical to 1.9.0.
release_date: '2021-08-30' release_date: '2021-08-30'
1.9.10:
changes:
bugfixes:
- luks_devices - set ``LANG`` and similar environment variables to avoid translated
output, which can break some of the module's functionality like key management
(https://github.com/ansible-collections/community.crypto/pull/388, https://github.com/ansible-collections/community.crypto/issues/385).
release_summary: Regular bugfix release.
fragments:
- 1.9.10.yml
- 388-luks_device-i18n.yml
release_date: '2022-02-01'
1.9.11:
changes:
bugfixes:
- openssh_cert - fixed false ``changed`` status for ``host`` certificates when
using ``full_idempotence`` (https://github.com/ansible-collections/community.crypto/issues/395,
https://github.com/ansible-collections/community.crypto/pull/396).
release_summary: Bugfix release.
fragments:
- 1.9.11.yml
- 396-openssh_cert-host-cert-idempotence-fix.yml
release_date: '2022-02-05'
1.9.12:
changes:
bugfixes:
- certificate_complete_chain - allow multiple potential intermediate certificates
to have the same subject (https://github.com/ansible-collections/community.crypto/issues/399,
https://github.com/ansible-collections/community.crypto/pull/403).
- x509_certificate - for the ``ownca`` provider, check whether the CA private
key actually belongs to the CA certificate. This fix only covers the ``cryptography``
backend, not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - regenerate certificate when the CA's public key changes
for ``provider=ownca``. This fix only covers the ``cryptography`` backend,
not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - regenerate certificate when the CA's subject changes for
``provider=ownca`` (https://github.com/ansible-collections/community.crypto/issues/400,
https://github.com/ansible-collections/community.crypto/pull/402).
- x509_certificate - regenerate certificate when the private key changes for
``provider=selfsigned``. This fix only covers the ``cryptography`` backend,
not the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/407).
known_issues:
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl``
backend, changing the CA's public key does not cause regeneration of the certificate
(https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - when using the ``ownca`` provider with the ``pyopenssl``
backend, it is possible to specify a CA private key which is not related to
the CA certificate (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - when using the ``selfsigned`` provider with the ``pyopenssl``
backend, changing the private key does not cause regeneration of the certificate
(https://github.com/ansible-collections/community.crypto/pull/407).
release_summary: Regular bugfix release.
fragments:
- 1.9.12.yml
- 402-x509_certificate-ownca-subject.yml
- 403-certificate_complete_chain-same-subject.yml
- 407-x509_certificate-signature.yml
release_date: '2022-02-21'
1.9.13:
changes:
bugfixes:
- luks_device - fix parsing of ``lsblk`` output when device name ends with ``crypt``
(https://github.com/ansible-collections/community.crypto/issues/409, https://github.com/ansible-collections/community.crypto/pull/410).
release_summary: Regular bugfix release.
fragments:
- 1.9.13.yml
- 410-luks_device-lsblk-parsing.yml
release_date: '2022-03-04'
1.9.14:
changes:
bugfixes:
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography
version (https://github.com/ansible-collections/community.crypto/pull/446).
- openssh_* modules - fix exception handling to report traceback to users for
enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
- x509_crl - fix crash when ``issuer`` for a revoked certificate is specified
(https://github.com/ansible-collections/community.crypto/pull/441).
release_summary: Regular bugfix release.
fragments:
- 1.9.14.yml
- 417-openssh_modules-fix-exception-reporting.yml
- 441-x509-crl-cert-issuer.yml
- 446-fix.yml
release_date: '2022-05-09'
1.9.15:
changes:
bugfixes:
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
release_summary: Maintenance release.
fragments:
- 1.9.15.yml
- psf-license.yml
release_date: '2022-05-16'
1.9.16:
changes:
bugfixes:
- Include ``simplified_bsd.txt`` license file for the ECS module utils.
- certificate_complete_chain - do not stop execution if an unsupported signature
algorithm is encountered; warn instead (https://github.com/ansible-collections/community.crypto/pull/457).
release_summary: Maintenance and bugfix release.
fragments:
- 1.9.16.yml
- 457-certificate_complete_chain-unsupported-algorithm.yml
- simplified-bsd-license.yml
release_date: '2022-06-02'
1.9.17:
changes:
bugfixes:
- Include ``Apache-2.0.txt`` file for ``plugins/module_utils/crypto/_obj2txt.py``
and ``plugins/module_utils/crypto/_objects_data.py``.
- openssl_csr - the module no longer crashes with 'permitted_subtrees/excluded_subtrees
must be a non-empty list or None' if only one of ``name_constraints_permitted``
and ``name_constraints_excluded`` is provided (https://github.com/ansible-collections/community.crypto/issues/481).
- x509_crl - do not crash when signing CRL with Ed25519 or Ed448 keys (https://github.com/ansible-collections/community.crypto/issues/473,
https://github.com/ansible-collections/community.crypto/pull/474).
release_summary: Bugfix release.
fragments:
- 1.9.17.yml
- 474-x509_crl-ed25519-ed448.yml
- 481-fix-excluded_subtrees-must-be-a-non-empty-list-or-None.yml
- apache-license.yml
release_date: '2022-06-17'
1.9.18:
changes:
bugfixes:
- openssl_pkcs12 - when using the pyOpenSSL backend, do not crash when trying
to read non-existing other certificates (https://github.com/ansible-collections/community.crypto/issues/486,
https://github.com/ansible-collections/community.crypto/pull/487).
release_summary: Bugfix release.
fragments:
- 1.9.18.yml
- 487-openssl_pkcs12-other-certs-crash.yml
release_date: '2022-07-09'
1.9.19:
changes:
bugfixes:
- openssl_privatekey_pipe - ensure compatibility with newer versions of ansible-core
(https://github.com/ansible-collections/community.crypto/pull/515).
release_summary: Bugfix release.
fragments:
- 1.9.19.yml
- 515-action-module-compat.yml
release_date: '2022-11-01'
1.9.2: 1.9.2:
changes: changes:
release_summary: Bugfix release to fix the changelog. No other change compared release_summary: Bugfix release to fix the changelog. No other change compared
@@ -535,6 +677,16 @@ releases:
fragments: fragments:
- 1.9.2.yml - 1.9.2.yml
release_date: '2021-08-30' release_date: '2021-08-30'
1.9.20:
changes:
bugfixes:
- openssl_publickey_info - do not crash with internal error when public key
cannot be parsed (https://github.com/ansible-collections/community.crypto/pull/551).
release_summary: Bugfix release.
fragments:
- 1.9.20.yml
- 551-publickey-info.yml
release_date: '2023-01-01'
1.9.3: 1.9.3:
changes: changes:
bugfixes: bugfixes:
@@ -562,3 +714,82 @@ releases:
- 279-acme-openssl.yml - 279-acme-openssl.yml
- 282-acme_challenge_cert_helper-error.yml - 282-acme_challenge_cert_helper-error.yml
release_date: '2021-09-28' release_date: '2021-09-28'
1.9.5:
changes:
bugfixes:
- get_certificate - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/294).
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release
(https://github.com/ansible-collections/community.crypto/pull/294).
- openssl_csr_info - fix compatibility with the cryptography 35.0.0 release
in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
- openssl_pkcs12 - fix compatibility with the cryptography 35.0.0 release (https://github.com/ansible-collections/community.crypto/pull/296).
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release
(https://github.com/ansible-collections/community.crypto/pull/294).
- x509_certificate_info - fix compatibility with the cryptography 35.0.0 release
in PyOpenSSL backend (https://github.com/ansible-collections/community.crypto/pull/300).
release_summary: Bugfix release to fully support cryptography 35.0.0.
fragments:
- 1.9.5.yml
- 294-cryptography-35.0.0.yml
- 296-openssl_pkcs12-cryptography-35.yml
- 300-pyopenssl-cryptography-35.yml
release_date: '2021-10-06'
1.9.6:
changes:
bugfixes:
- cryptography backend - improve Unicode handling for Python 2 (https://github.com/ansible-collections/community.crypto/pull/313).
release_summary: Regular bugfix release.
fragments:
- 1.9.6.yml
- 313-unicode-names.yml
release_date: '2021-10-30'
1.9.7:
changes:
bugfixes:
- acme_certificate - avoid passing multiple certificates to ``cryptography``'s
X.509 certificate loader when ``fullchain_dest`` is used (https://github.com/ansible-collections/community.crypto/pull/324).
- get_certificate, openssl_csr_info, x509_certificate_info - add fallback code
for extension parsing that works with cryptography 36.0.0 and newer. This
code re-serializes de-serialized extensions and thus can return slightly different
values if the extension in the original CSR resp. certificate was not canonicalized
correctly. This code is currently used as a fallback if the existing code
stops working, but we will switch it to be the main code in a future release
(https://github.com/ansible-collections/community.crypto/pull/331).
- luks_device - now also runs a built-in LUKS signature cleaner on ``state=absent``
to make sure that also the secondary LUKS2 header is wiped when older versions
of wipefs are used (https://github.com/ansible-collections/community.crypto/issues/326,
https://github.com/ansible-collections/community.crypto/pull/327).
- openssl_pkcs12 - use new PKCS#12 deserialization infrastructure from cryptography
36.0.0 if available (https://github.com/ansible-collections/community.crypto/pull/302).
minor_changes:
- acme_* modules - fix usage of ``fetch_url`` with changes in latest ansible-core
``devel`` branch (https://github.com/ansible-collections/community.crypto/pull/339).
release_summary: Bugfix release with extra forward compatibility for newer versions
of cryptography.
fragments:
- 1.9.7.yml
- 302-openssl_pkcs12-cryptography-36.0.0.yml
- 324-acme_certificate-fullchain.yml
- 327-luks_device-wipe.yml
- 331-cryptography-extensions.yml
- fetch_url-devel.yml
release_date: '2021-11-22'
1.9.8:
changes:
release_summary: Documentation fix release. No actual code changes.
fragments:
- 1.9.8.yml
release_date: '2021-12-13'
1.9.9:
changes:
bugfixes:
- Various modules and plugins - use vendored version of ``distutils.version``
instead of the deprecated Python standard library ``distutils`` (https://github.com/ansible-collections/community.crypto/pull/353).
- certificate_complete_chain - do not append root twice if the chain already
ends with a root certificate (https://github.com/ansible-collections/community.crypto/pull/360).
- certificate_complete_chain - do not hang when infinite loop is found (https://github.com/ansible-collections/community.crypto/issues/355,
https://github.com/ansible-collections/community.crypto/pull/360).
fragments:
- 353-distutils.version.yml
- 360-certificate_complete_chain-loop.yml
release_date: '2022-01-11'

View File

@@ -71,7 +71,7 @@ In the following example, we assume that the certificate to sign (including its
- name: Sign certificate with our CA - name: Sign certificate with our CA
community.crypto.x509_certificate_pipe: community.crypto.x509_certificate_pipe:
csr_content: "{{ ca_csr.csr }}" csr_content: "{{ csr.csr }}"
provider: ownca provider: ownca
ownca_path: /path/to/ca-certificate.pem ownca_path: /path/to/ca-certificate.pem
ownca_privatekey_path: /path/to/ca-certificate.key ownca_privatekey_path: /path/to/ca-certificate.key
@@ -128,7 +128,7 @@ Please note that the above procedure is **not idempotent**. The following extend
- name: Sign certificate with our CA - name: Sign certificate with our CA
community.crypto.x509_certificate_pipe: community.crypto.x509_certificate_pipe:
content: "{{ (certificate.content | b64decode) if certificate_exists.stat.exists else omit }}" content: "{{ (certificate.content | b64decode) if certificate_exists.stat.exists else omit }}"
csr_content: "{{ ca_csr.csr }}" csr_content: "{{ csr.csr }}"
provider: ownca provider: ownca
ownca_path: /path/to/ca-certificate.pem ownca_path: /path/to/ca-certificate.pem
ownca_privatekey_path: /path/to/ca-certificate.key ownca_privatekey_path: /path/to/ca-certificate.key

View File

@@ -1,6 +1,6 @@
namespace: community namespace: community
name: crypto name: crypto
version: 1.9.4 version: 1.9.20
readme: README.md readme: README.md
authors: authors:
- Ansible (github.com/ansible) - Ansible (github.com/ansible)

View File

@@ -457,8 +457,8 @@ options:
- Time will always be interpreted as UTC. - Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h). + C([w | d | h | m | s]) (e.g. C(+32w1d2h).
- Note that if using relative time this module is NOT idempotent.
- If this value is not specified, the certificate will start being valid from now. - If this value is not specified, the certificate will start being valid from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(ownca) provider. - This is only used by the C(ownca) provider.
type: str type: str
default: +0s default: +0s
@@ -470,8 +470,8 @@ options:
- Time will always be interpreted as UTC. - Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h). + C([w | d | h | m | s]) (e.g. C(+32w1d2h).
- Note that if using relative time this module is NOT idempotent.
- If this value is not specified, the certificate will stop being valid 10 years from now. - If this value is not specified, the certificate will stop being valid 10 years from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(ownca) provider. - This is only used by the C(ownca) provider.
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer. - On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
Please see U(https://support.apple.com/en-us/HT210176) for more details. Please see U(https://support.apple.com/en-us/HT210176) for more details.
@@ -548,8 +548,8 @@ options:
- Time will always be interpreted as UTC. - Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h). + C([w | d | h | m | s]) (e.g. C(+32w1d2h).
- Note that if using relative time this module is NOT idempotent.
- If this value is not specified, the certificate will start being valid from now. - If this value is not specified, the certificate will start being valid from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(selfsigned) provider. - This is only used by the C(selfsigned) provider.
type: str type: str
default: +0s default: +0s
@@ -562,8 +562,8 @@ options:
- Time will always be interpreted as UTC. - Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer - Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h). + C([w | d | h | m | s]) (e.g. C(+32w1d2h).
- Note that if using relative time this module is NOT idempotent.
- If this value is not specified, the certificate will stop being valid 10 years from now. - If this value is not specified, the certificate will stop being valid 10 years from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(selfsigned) provider. - This is only used by the C(selfsigned) provider.
- On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer. - On macOS 10.15 and onwards, TLS server certificates must have a validity period of 825 days or fewer.
Please see U(https://support.apple.com/en-us/HT210176) for more details. Please see U(https://support.apple.com/en-us/HT210176) for more details.

View File

@@ -100,7 +100,7 @@ options:
description: description:
- Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format) - Determines which format the private key is written in. By default, PKCS1 (traditional OpenSSL format)
is used for all keys which support it. Please note that not every key can be exported in any format. is used for all keys which support it. Please note that not every key can be exported in any format.
- The value C(auto) selects a fromat based on the key format. The value C(auto_ignore) does the same, - The value C(auto) selects a format based on the key format. The value C(auto_ignore) does the same,
but for existing private key files, it will not force a regenerate when its format is not the automatically but for existing private key files, it will not force a regenerate when its format is not the automatically
selected one for generation. selected one for generation.
- Note that if the format for an existing private key mismatches, the key is B(regenerated) by default. - Note that if the format for an existing private key mismatches, the key is B(regenerated) by default.

View File

@@ -0,0 +1,343 @@
# Vendored copy of distutils/version.py from CPython 3.9.5
#
# Implements multiple version numbering conventions for the
# Python Module Distribution Utilities.
#
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
#
"""Provides classes to represent module version numbers (one class for
each style of version numbering). There are currently two such classes
implemented: StrictVersion and LooseVersion.
Every version number class implements the following interface:
* the 'parse' method takes a string and parses it to some internal
representation; if the string is an invalid version number,
'parse' raises a ValueError exception
* the class constructor takes an optional string argument which,
if supplied, is passed to 'parse'
* __str__ reconstructs the string that was passed to 'parse' (or
an equivalent string -- ie. one that will generate an equivalent
version number instance)
* __repr__ generates Python code to recreate the version number instance
* _cmp compares the current instance with either another instance
of the same class or a string (which will be parsed to an instance
of the same class, thus must follow the same rules)
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import re
try:
RE_FLAGS = re.VERBOSE | re.ASCII
except AttributeError:
RE_FLAGS = re.VERBOSE
class Version:
"""Abstract base class for version numbering classes. Just provides
constructor (__init__) and reproducer (__repr__), because those
seem to be the same for all version numbering classes; and route
rich comparisons to _cmp.
"""
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def __repr__(self):
return "%s ('%s')" % (self.__class__.__name__, str(self))
def __eq__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c == 0
def __lt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c < 0
def __le__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c <= 0
def __gt__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c > 0
def __ge__(self, other):
c = self._cmp(other)
if c is NotImplemented:
return c
return c >= 0
# Interface for version-number classes -- must be implemented
# by the following classes (the concrete ones -- Version should
# be treated as an abstract class).
# __init__ (string) - create and take same action as 'parse'
# (string parameter is optional)
# parse (string) - convert a string representation to whatever
# internal representation is appropriate for
# this style of version numbering
# __str__ (self) - convert back to a string; should be very similar
# (if not identical to) the string supplied to parse
# __repr__ (self) - generate Python code to recreate
# the instance
# _cmp (self, other) - compare two version numbers ('other' may
# be an unparsed version string, or another
# instance of your version class)
class StrictVersion(Version):
"""Version numbering for anal retentives and software idealists.
Implements the standard interface for version number classes as
described above. A version number consists of two or three
dot-separated numeric components, with an optional "pre-release" tag
on the end. The pre-release tag consists of the letter 'a' or 'b'
followed by a number. If the numeric components of two version
numbers are equal, then one with a pre-release tag will always
be deemed earlier (lesser) than one without.
The following are valid version numbers (shown in the order that
would be obtained by sorting according to the supplied cmp function):
0.4 0.4.0 (these two are equivalent)
0.4.1
0.5a1
0.5b3
0.5
0.9.6
1.0
1.0.4a3
1.0.4b1
1.0.4
The following are examples of invalid version numbers:
1
2.7.2.2
1.3.a4
1.3pl1
1.3c4
The rationale for this version numbering system will be explained
in the distutils documentation.
"""
version_re = re.compile(r'^(\d+) \. (\d+) (\. (\d+))? ([ab](\d+))?$',
RE_FLAGS)
def parse(self, vstring):
match = self.version_re.match(vstring)
if not match:
raise ValueError("invalid version number '%s'" % vstring)
(major, minor, patch, prerelease, prerelease_num) = \
match.group(1, 2, 4, 5, 6)
if patch:
self.version = tuple(map(int, [major, minor, patch]))
else:
self.version = tuple(map(int, [major, minor])) + (0,)
if prerelease:
self.prerelease = (prerelease[0], int(prerelease_num))
else:
self.prerelease = None
def __str__(self):
if self.version[2] == 0:
vstring = '.'.join(map(str, self.version[0:2]))
else:
vstring = '.'.join(map(str, self.version))
if self.prerelease:
vstring = vstring + self.prerelease[0] + str(self.prerelease[1])
return vstring
def _cmp(self, other):
if isinstance(other, str):
other = StrictVersion(other)
elif not isinstance(other, StrictVersion):
return NotImplemented
if self.version != other.version:
# numeric versions don't match
# prerelease stuff doesn't matter
if self.version < other.version:
return -1
else:
return 1
# have to compare prerelease
# case 1: neither has prerelease; they're equal
# case 2: self has prerelease, other doesn't; other is greater
# case 3: self doesn't have prerelease, other does: self is greater
# case 4: both have prerelease: must compare them!
if (not self.prerelease and not other.prerelease):
return 0
elif (self.prerelease and not other.prerelease):
return -1
elif (not self.prerelease and other.prerelease):
return 1
elif (self.prerelease and other.prerelease):
if self.prerelease == other.prerelease:
return 0
elif self.prerelease < other.prerelease:
return -1
else:
return 1
else:
raise AssertionError("never get here")
# end class StrictVersion
# The rules according to Greg Stein:
# 1) a version number has 1 or more numbers separated by a period or by
# sequences of letters. If only periods, then these are compared
# left-to-right to determine an ordering.
# 2) sequences of letters are part of the tuple for comparison and are
# compared lexicographically
# 3) recognize the numeric components may have leading zeroes
#
# The LooseVersion class below implements these rules: a version number
# string is split up into a tuple of integer and string components, and
# comparison is a simple tuple comparison. This means that version
# numbers behave in a predictable and obvious way, but a way that might
# not necessarily be how people *want* version numbers to behave. There
# wouldn't be a problem if people could stick to purely numeric version
# numbers: just split on period and compare the numbers as tuples.
# However, people insist on putting letters into their version numbers;
# the most common purpose seems to be:
# - indicating a "pre-release" version
# ('alpha', 'beta', 'a', 'b', 'pre', 'p')
# - indicating a post-release patch ('p', 'pl', 'patch')
# but of course this can't cover all version number schemes, and there's
# no way to know what a programmer means without asking him.
#
# The problem is what to do with letters (and other non-numeric
# characters) in a version number. The current implementation does the
# obvious and predictable thing: keep them as strings and compare
# lexically within a tuple comparison. This has the desired effect if
# an appended letter sequence implies something "post-release":
# eg. "0.99" < "0.99pl14" < "1.0", and "5.001" < "5.001m" < "5.002".
#
# However, if letters in a version number imply a pre-release version,
# the "obvious" thing isn't correct. Eg. you would expect that
# "1.5.1" < "1.5.2a2" < "1.5.2", but under the tuple/lexical comparison
# implemented here, this just isn't so.
#
# Two possible solutions come to mind. The first is to tie the
# comparison algorithm to a particular set of semantic rules, as has
# been done in the StrictVersion class above. This works great as long
# as everyone can go along with bondage and discipline. Hopefully a
# (large) subset of Python module programmers will agree that the
# particular flavour of bondage and discipline provided by StrictVersion
# provides enough benefit to be worth using, and will submit their
# version numbering scheme to its domination. The free-thinking
# anarchists in the lot will never give in, though, and something needs
# to be done to accommodate them.
#
# Perhaps a "moderately strict" version class could be implemented that
# lets almost anything slide (syntactically), and makes some heuristic
# assumptions about non-digits in version number strings. This could
# sink into special-case-hell, though; if I was as talented and
# idiosyncratic as Larry Wall, I'd go ahead and implement a class that
# somehow knows that "1.2.1" < "1.2.2a2" < "1.2.2" < "1.2.2pl3", and is
# just as happy dealing with things like "2g6" and "1.13++". I don't
# think I'm smart enough to do it right though.
#
# In any case, I've coded the test suite for this module (see
# ../test/test_version.py) specifically to fail on things like comparing
# "1.2a2" and "1.2". That's not because the *code* is doing anything
# wrong, it's because the simple, obvious design doesn't match my
# complicated, hairy expectations for real-world version numbers. It
# would be a snap to fix the test suite to say, "Yep, LooseVersion does
# the Right Thing" (ie. the code matches the conception). But I'd rather
# have a conception that matches common notions about version numbers.
class LooseVersion(Version):
"""Version numbering for anarchists and software realists.
Implements the standard interface for version number classes as
described above. A version number consists of a series of numbers,
separated by either periods or strings of letters. When comparing
version numbers, the numeric components will be compared
numerically, and the alphabetic components lexically. The following
are all valid version numbers, in no particular order:
1.5.1
1.5.2b2
161
3.10a
8.02
3.4j
1996.07.12
3.2.pl0
3.1.1.6
2g6
11g
0.960923
2.2beta29
1.13++
5.5.kw
2.0b1pl0
In fact, there is no such thing as an invalid version number under
this scheme; the rules for comparison are simple and predictable,
but may not always give the results you want (for some definition
of "want").
"""
component_re = re.compile(r'(\d+ | [a-z]+ | \.)', re.VERBOSE)
def __init__(self, vstring=None):
if vstring:
self.parse(vstring)
def parse(self, vstring):
# I've given up on thinking I can reconstruct the version string
# from the parsed tuple -- so I just store the string here for
# use by __str__
self.vstring = vstring
components = [x for x in self.component_re.split(vstring) if x and x != '.']
for i, obj in enumerate(components):
try:
components[i] = int(obj)
except ValueError:
pass
self.version = components
def __str__(self):
return self.vstring
def __repr__(self):
return "LooseVersion ('%s')" % str(self)
def _cmp(self, other):
if isinstance(other, str):
other = LooseVersion(other)
elif not isinstance(other, LooseVersion):
return NotImplemented
if self.version == other.version:
return 0
if self.version < other.version:
return -1
if self.version > other.version:
return 1
# end class LooseVersion

View File

@@ -14,8 +14,9 @@ import json
import locale import locale
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.six import PY3
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import ( from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
OpenSSLCLIBackend, OpenSSLCLIBackend,
@@ -228,9 +229,14 @@ class ACMEClient(object):
resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST') resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST')
_assert_fetch_url_success(self.module, resp, info) _assert_fetch_url_success(self.module, resp, info)
result = {} result = {}
try: try:
# In Python 2, reading from a closed response yields a TypeError.
# In Python 3, read() simply returns ''
if PY3 and resp.closed:
raise TypeError
content = resp.read() content = resp.read()
except AttributeError: except (AttributeError, TypeError):
content = info.pop('body', None) content = info.pop('body', None)
if content or not parse_json_result: if content or not parse_json_result:
@@ -284,8 +290,12 @@ class ACMEClient(object):
_assert_fetch_url_success(self.module, resp, info) _assert_fetch_url_success(self.module, resp, info)
try: try:
# In Python 2, reading from a closed response yields a TypeError.
# In Python 3, read() simply returns ''
if PY3 and resp.closed:
raise TypeError
content = resp.read() content = resp.read()
except AttributeError: except (AttributeError, TypeError):
content = info.pop('body', None) content = info.pop('body', None)
# Process result # Process result

View File

@@ -14,7 +14,9 @@ import datetime
import os import os
import sys import sys
from ansible.module_utils.common.text.converters import to_bytes, to_native from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import ( from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
CryptoBackend, CryptoBackend,
@@ -41,6 +43,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
cryptography_name_to_oid, cryptography_name_to_oid,
) )
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
extract_first_pem,
)
try: try:
import cryptography import cryptography
import cryptography.hazmat.backends import cryptography.hazmat.backends
@@ -53,7 +59,6 @@ try:
import cryptography.hazmat.primitives.serialization import cryptography.hazmat.primitives.serialization
import cryptography.x509 import cryptography.x509
import cryptography.x509.oid import cryptography.x509.oid
from distutils.version import LooseVersion
CRYPTOGRAPHY_VERSION = cryptography.__version__ CRYPTOGRAPHY_VERSION = cryptography.__version__
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5')) HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5'))
if HAS_CURRENT_CRYPTOGRAPHY: if HAS_CURRENT_CRYPTOGRAPHY:
@@ -357,6 +362,9 @@ class CryptographyBackend(CryptoBackend):
if cert_content is None: if cert_content is None:
return -1 return -1
# Make sure we have at most one PEM. Otherwise cryptography 36.0.0 will barf.
cert_content = to_bytes(extract_first_pem(to_text(cert_content)) or '')
try: try:
cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend) cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend)
except Exception as e: except Exception as e:

View File

@@ -7,8 +7,8 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from ansible.module_utils.six import binary_type
from ansible.module_utils.common.text.converters import to_text from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.six import binary_type, PY3
def format_error_problem(problem, subproblem_prefix=''): def format_error_problem(problem, subproblem_prefix=''):
@@ -52,8 +52,12 @@ class ACMEProtocolException(ModuleFailException):
# Try to get hold of content, if response is given and content is not provided # Try to get hold of content, if response is given and content is not provided
if content is None and content_json is None and response is not None: if content is None and content_json is None and response is not None:
try: try:
# In Python 2, reading from a closed response yields a TypeError.
# In Python 3, read() simply returns ''
if PY3 and response.closed:
raise TypeError
content = response.read() content = response.read()
except AttributeError: except (AttributeError, TypeError):
content = info.pop('body', None) content = info.pop('body', None)
# Make sure that content_json is None or a dictionary # Make sure that content_json is None or a dictionary

View File

@@ -2,6 +2,8 @@
# 2.0, and the BSD License. See the LICENSE file at # 2.0, and the BSD License. See the LICENSE file at
# https://github.com/pyca/cryptography/blob/master/LICENSE for complete details. # https://github.com/pyca/cryptography/blob/master/LICENSE for complete details.
# #
# The Apache 2.0 license has been included as Apache-2.0.txt in this collection.
#
# Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py # Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py
# #
# Copyright (c) 2015, 2016 Paul Kehrer (@reaperhulk) # Copyright (c) 2015, 2016 Paul Kehrer (@reaperhulk)
@@ -20,6 +22,10 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
# WARNING: this function no longer works with cryptography 35.0.0 and newer!
# It must **ONLY** be used in compatibility code for older
# cryptography versions!
def obj2txt(openssl_lib, openssl_ffi, obj): def obj2txt(openssl_lib, openssl_ffi, obj):
# Set to 80 on the recommendation of # Set to 80 on the recommendation of
# https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values

View File

@@ -5,7 +5,7 @@
# In case the following data structure has any copyrightable content, note that it is licensed as follows: # In case the following data structure has any copyrightable content, note that it is licensed as follows:
# Copyright (c) the OpenSSL contributors # Copyright (c) the OpenSSL contributors
# Licensed under the Apache License 2.0 # Licensed under the Apache License 2.0
# https://github.com/openssl/openssl/blob/master/LICENSE # https://github.com/openssl/openssl/blob/master/LICENSE.txt or Apache-2.0.txt
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type

View File

@@ -20,13 +20,13 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type __metaclass__ = type
from distutils.version import LooseVersion from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
try: try:
import OpenSSL # noqa import OpenSSL # noqa
from OpenSSL import crypto # noqa from OpenSSL import crypto # noqa
HAS_PYOPENSSL = True HAS_PYOPENSSL = True
except ImportError: except (ImportError, AttributeError):
# Error handled in the calling module. # Error handled in the calling module.
HAS_PYOPENSSL = False HAS_PYOPENSSL = False

View File

@@ -26,15 +26,41 @@ import re
from ansible.module_utils.common.text.converters import to_text, to_bytes from ansible.module_utils.common.text.converters import to_text, to_bytes
from ._asn1 import serialize_asn1_string_as_der from ._asn1 import serialize_asn1_string_as_der
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
try: try:
import cryptography import cryptography
from cryptography import x509 from cryptography import x509
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
import ipaddress import ipaddress
except ImportError: except ImportError:
# Error handled in the calling module. # Error handled in the calling module.
pass pass
try:
import cryptography.hazmat.primitives.asymmetric.rsa
except ImportError:
pass
try:
import cryptography.hazmat.primitives.asymmetric.ec
except ImportError:
pass
try:
import cryptography.hazmat.primitives.asymmetric.dsa
except ImportError:
pass
try:
import cryptography.hazmat.primitives.asymmetric.ed25519
except ImportError:
pass
try:
import cryptography.hazmat.primitives.asymmetric.ed448
except ImportError:
pass
try: try:
# This is a separate try/except since this is only present in cryptography 2.5 or newer # This is a separate try/except since this is only present in cryptography 2.5 or newer
from cryptography.hazmat.primitives.serialization.pkcs12 import ( from cryptography.hazmat.primitives.serialization.pkcs12 import (
@@ -44,9 +70,23 @@ except ImportError:
# Error handled in the calling module. # Error handled in the calling module.
_load_key_and_certificates = None _load_key_and_certificates = None
try:
# This is a separate try/except since this is only present in cryptography 36.0.0 or newer
from cryptography.hazmat.primitives.serialization.pkcs12 import (
load_pkcs12 as _load_pkcs12,
)
except ImportError:
# Error handled in the calling module.
_load_pkcs12 = None
from .basic import ( from .basic import (
CRYPTOGRAPHY_HAS_DSA_SIGN,
CRYPTOGRAPHY_HAS_EC_SIGN,
CRYPTOGRAPHY_HAS_ED25519, CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED25519_SIGN,
CRYPTOGRAPHY_HAS_ED448, CRYPTOGRAPHY_HAS_ED448,
CRYPTOGRAPHY_HAS_ED448_SIGN,
CRYPTOGRAPHY_HAS_RSA_SIGN,
OpenSSLObjectError, OpenSSLObjectError,
) )
@@ -64,60 +104,114 @@ DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
def cryptography_get_extensions_from_cert(cert): def cryptography_get_extensions_from_cert(cert):
# Since cryptography won't give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
result = dict() result = dict()
backend = cert._backend try:
x509_obj = cert._x509 # Since cryptography won't give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
backend = default_backend()
try:
# For certain old versions of cryptography, backend is a MultiBackend object,
# which has no _lib attribute. In that case, revert to the old approach.
backend._lib
except AttributeError:
backend = cert._backend
x509_obj = cert._x509
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
# not allow to get the raw value of an extension, so we have to use this ugly hack:
exts = list(cert.extensions)
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
ext = backend._lib.X509_get_ext(x509_obj, i)
if ext == backend._ffi.NULL:
continue
crit = backend._lib.X509_EXTENSION_get_critical(ext)
data = backend._lib.X509_EXTENSION_get_data(ext)
backend.openssl_assert(data != backend._ffi.NULL)
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der),
)
try:
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
except AttributeError:
oid = exts[i].oid.dotted_string
result[oid] = entry
except Exception:
# In case the above method breaks, we likely have cryptography 36.0.0 or newer.
# Use it's public_bytes() feature in that case. We will later switch this around
# so that this code will be the default, but for now this will act as a fallback
# since it will re-serialize de-serialized data, which can be different (if the
# original data was not canonicalized) from what was contained in the certificate.
for ext in cert.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()),
)
for i in range(backend._lib.X509_get_ext_count(x509_obj)):
ext = backend._lib.X509_get_ext(x509_obj, i)
if ext == backend._ffi.NULL:
continue
crit = backend._lib.X509_EXTENSION_get_critical(ext)
data = backend._lib.X509_EXTENSION_get_data(ext)
backend.openssl_assert(data != backend._ffi.NULL)
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der),
)
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
result[oid] = entry
return result return result
def cryptography_get_extensions_from_csr(csr): def cryptography_get_extensions_from_csr(csr):
# Since cryptography won't give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
result = dict() result = dict()
backend = csr._backend try:
# Since cryptography won't give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
backend = default_backend()
try:
# For certain old versions of cryptography, backend is a MultiBackend object,
# which has no _lib attribute. In that case, revert to the old approach.
backend._lib
except AttributeError:
backend = csr._backend
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req) extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
extensions = backend._ffi.gc( extensions = backend._ffi.gc(
extensions, extensions,
lambda ext: backend._lib.sk_X509_EXTENSION_pop_free( lambda ext: backend._lib.sk_X509_EXTENSION_pop_free(
ext, ext,
backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free") backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free")
)
) )
)
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)): # With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
ext = backend._lib.sk_X509_EXTENSION_value(extensions, i) # not allow to get the raw value of an extension, so we have to use this ugly hack:
if ext == backend._ffi.NULL: exts = list(csr.extensions)
continue
crit = backend._lib.X509_EXTENSION_get_critical(ext) for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
data = backend._lib.X509_EXTENSION_get_data(ext) ext = backend._lib.sk_X509_EXTENSION_value(extensions, i)
backend.openssl_assert(data != backend._ffi.NULL) if ext == backend._ffi.NULL:
der = backend._ffi.buffer(data.data, data.length)[:] continue
entry = dict( crit = backend._lib.X509_EXTENSION_get_critical(ext)
critical=(crit == 1), data = backend._lib.X509_EXTENSION_get_data(ext)
value=base64.b64encode(der), backend.openssl_assert(data != backend._ffi.NULL)
) der = backend._ffi.buffer(data.data, data.length)[:]
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext)) entry = dict(
result[oid] = entry critical=(crit == 1),
value=base64.b64encode(der),
)
try:
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
except AttributeError:
oid = exts[i].oid.dotted_string
result[oid] = entry
except Exception:
# In case the above method breaks, we likely have cryptography 36.0.0 or newer.
# Use it's public_bytes() feature in that case. We will later switch this around
# so that this code will be the default, but for now this will act as a fallback
# since it will re-serialize de-serialized data, which can be different (if the
# original data was not canonicalized) from what was contained in the CSR.
for ext in csr.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()),
)
return result return result
@@ -280,11 +374,11 @@ def _dn_escape_value(value):
''' '''
Escape Distinguished Name's attribute value. Escape Distinguished Name's attribute value.
''' '''
value = value.replace('\\', '\\\\') value = value.replace(u'\\', u'\\\\')
for ch in [',', '#', '+', '<', '>', ';', '"', '=', '/']: for ch in [u',', u'#', u'+', u'<', u'>', u';', u'"', u'=', u'/']:
value = value.replace(ch, '\\%s' % ch) value = value.replace(ch, u'\\%s' % ch)
if value.startswith(' '): if value.startswith(u' '):
value = r'\ ' + value[1:] value = u'\\ ' + value[1:]
return value return value
@@ -294,24 +388,24 @@ def cryptography_decode_name(name):
Raises an OpenSSLObjectError if the name is not supported. Raises an OpenSSLObjectError if the name is not supported.
''' '''
if isinstance(name, x509.DNSName): if isinstance(name, x509.DNSName):
return 'DNS:{0}'.format(name.value) return u'DNS:{0}'.format(name.value)
if isinstance(name, x509.IPAddress): if isinstance(name, x509.IPAddress):
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)): if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen) return u'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
return 'IP:{0}'.format(name.value.compressed) return u'IP:{0}'.format(name.value.compressed)
if isinstance(name, x509.RFC822Name): if isinstance(name, x509.RFC822Name):
return 'email:{0}'.format(name.value) return u'email:{0}'.format(name.value)
if isinstance(name, x509.UniformResourceIdentifier): if isinstance(name, x509.UniformResourceIdentifier):
return 'URI:{0}'.format(name.value) return u'URI:{0}'.format(name.value)
if isinstance(name, x509.DirectoryName): if isinstance(name, x509.DirectoryName):
return 'dirName:' + ''.join([ return u'dirName:' + u''.join([
'/{0}={1}'.format(cryptography_oid_to_name(attribute.oid, short=True), _dn_escape_value(attribute.value)) u'/{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
for attribute in name.value for attribute in name.value
]) ])
if isinstance(name, x509.RegisteredID): if isinstance(name, x509.RegisteredID):
return 'RID:{0}'.format(name.value.dotted_string) return u'RID:{0}'.format(name.value.dotted_string)
if isinstance(name, x509.OtherName): if isinstance(name, x509.OtherName):
return 'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value)) return u'otherName:{0};{1}'.format(name.type_id.dotted_string, _get_hex(name.value))
raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name)) raise OpenSSLObjectError('Cannot decode name "{0}"'.format(name))
@@ -442,17 +536,111 @@ def cryptography_serial_number_of_cert(cert):
def parse_pkcs12(pkcs12_bytes, passphrase=None): def parse_pkcs12(pkcs12_bytes, passphrase=None):
'''Returns a tuple (private_key, certificate, additional_certificates, friendly_name). '''Returns a tuple (private_key, certificate, additional_certificates, friendly_name).
''' '''
if _load_key_and_certificates is None: if _load_pkcs12 is None and _load_key_and_certificates is None:
raise ValueError('load_key_and_certificates() not present in the current cryptography version') raise ValueError('neither load_pkcs12() nor load_key_and_certificates() present in the current cryptography version')
private_key, certificate, additional_certificates = _load_key_and_certificates(
pkcs12_bytes, to_bytes(passphrase) if passphrase is not None else None) if passphrase is not None:
passphrase = to_bytes(passphrase)
# Main code for cryptography 36.0.0 and forward
if _load_pkcs12 is not None:
return _parse_pkcs12_36_0_0(pkcs12_bytes, passphrase)
if LooseVersion(cryptography.__version__) >= LooseVersion('35.0'):
return _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase)
return _parse_pkcs12_legacy(pkcs12_bytes, passphrase)
def _parse_pkcs12_36_0_0(pkcs12_bytes, passphrase=None):
# Requires cryptography 36.0.0 or newer
pkcs12 = _load_pkcs12(pkcs12_bytes, passphrase)
additional_certificates = [cert.certificate for cert in pkcs12.additional_certs]
private_key = pkcs12.key
certificate = None
friendly_name = None
if pkcs12.cert:
certificate = pkcs12.cert.certificate
friendly_name = pkcs12.cert.friendly_name
return private_key, certificate, additional_certificates, friendly_name
def _parse_pkcs12_35_0_0(pkcs12_bytes, passphrase=None):
# Backwards compatibility code for cryptography 35.x
private_key, certificate, additional_certificates = _load_key_and_certificates(pkcs12_bytes, passphrase)
friendly_name = None friendly_name = None
if certificate: if certificate:
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238 # See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
maybe_name = certificate._backend._lib.X509_alias_get0( backend = default_backend()
certificate._x509, certificate._backend._ffi.NULL)
if maybe_name != certificate._backend._ffi.NULL: # This code basically does what load_key_and_certificates() does, but without error-checking.
friendly_name = certificate._backend._ffi.string(maybe_name) # Since load_key_and_certificates succeeded, it should not fail.
pkcs12 = backend._ffi.gc(
backend._lib.d2i_PKCS12_bio(backend._bytes_to_bio(pkcs12_bytes).bio, backend._ffi.NULL),
backend._lib.PKCS12_free)
certificate_x509_ptr = backend._ffi.new("X509 **")
with backend._zeroed_null_terminated_buf(to_bytes(passphrase) if passphrase is not None else None) as passphrase_buffer:
backend._lib.PKCS12_parse(
pkcs12,
passphrase_buffer,
backend._ffi.new("EVP_PKEY **"),
certificate_x509_ptr,
backend._ffi.new("Cryptography_STACK_OF_X509 **"))
if certificate_x509_ptr[0] != backend._ffi.NULL:
maybe_name = backend._lib.X509_alias_get0(certificate_x509_ptr[0], backend._ffi.NULL)
if maybe_name != backend._ffi.NULL:
friendly_name = backend._ffi.string(maybe_name)
return private_key, certificate, additional_certificates, friendly_name return private_key, certificate, additional_certificates, friendly_name
def _parse_pkcs12_legacy(pkcs12_bytes, passphrase=None):
# Backwards compatibility code for cryptography < 35.0.0
private_key, certificate, additional_certificates = _load_key_and_certificates(pkcs12_bytes, passphrase)
friendly_name = None
if certificate:
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
backend = certificate._backend
maybe_name = backend._lib.X509_alias_get0(certificate._x509, backend._ffi.NULL)
if maybe_name != backend._ffi.NULL:
friendly_name = backend._ffi.string(maybe_name)
return private_key, certificate, additional_certificates, friendly_name
def cryptography_verify_signature(signature, data, hash_algorithm, signer_public_key):
'''
Check whether the given signature of the given data was signed by the given public key object.
'''
try:
if CRYPTOGRAPHY_HAS_RSA_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey):
signer_public_key.verify(signature, data, padding.PKCS1v15(), hash_algorithm)
return True
if CRYPTOGRAPHY_HAS_EC_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey):
signer_public_key.verify(signature, data, cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hash_algorithm))
return True
if CRYPTOGRAPHY_HAS_DSA_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey):
signer_public_key.verify(signature, data, hash_algorithm)
return True
if CRYPTOGRAPHY_HAS_ED25519_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey):
signer_public_key.verify(signature, data)
return True
if CRYPTOGRAPHY_HAS_ED448_SIGN and isinstance(signer_public_key, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey):
signer_public_key.verify(signature, data)
return True
raise OpenSSLObjectError(u'Unsupported public key type {0}'.format(type(signer_public_key)))
except InvalidSignature:
return False
def cryptography_verify_certificate_signature(certificate, signer_public_key):
'''
Check whether the given X509 certificate object was signed by the given public key object.
'''
return cryptography_verify_signature(
certificate.signature,
certificate.tbs_certificate_bytes,
certificate.signature_hash_algorithm,
signer_public_key
)

View File

@@ -11,11 +11,11 @@ __metaclass__ = type
import abc import abc
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -45,7 +45,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -37,7 +37,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
try: try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
except ImportError: except (ImportError, AttributeError):
pass pass
try: try:

View File

@@ -15,12 +15,12 @@ import datetime
import re import re
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate, load_certificate,
get_fingerprint_of_bytes, get_fingerprint_of_bytes,
@@ -59,7 +59,7 @@ try:
# OpenSSL 1.0.x or older # OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -10,11 +10,12 @@ __metaclass__ = type
import os import os
from distutils.version import LooseVersion
from random import randrange from random import randrange
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLBadPassphraseError, OpenSSLBadPassphraseError,
) )
@@ -27,8 +28,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
) )
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_compare_public_keys,
cryptography_key_needs_digest_for_signing, cryptography_key_needs_digest_for_signing,
cryptography_serial_number_of_cert, cryptography_serial_number_of_cert,
cryptography_verify_certificate_signature,
) )
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -40,7 +43,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
try: try:
from OpenSSL import crypto from OpenSSL import crypto
except ImportError: except (ImportError, AttributeError):
pass pass
try: try:
@@ -106,6 +109,9 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
except OpenSSLBadPassphraseError as exc: except OpenSSLBadPassphraseError as exc:
module.fail_json(msg=str(exc)) module.fail_json(msg=str(exc))
if not cryptography_compare_public_keys(self.ca_cert.public_key(), self.ca_private_key.public_key()):
raise CertificateError('The CA private key does not belong to the CA certificate')
if cryptography_key_needs_digest_for_signing(self.ca_private_key): if cryptography_key_needs_digest_for_signing(self.ca_private_key):
if self.digest is None: if self.digest is None:
raise CertificateError( raise CertificateError(
@@ -172,6 +178,16 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(): if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
return True return True
self._ensure_existing_certificate_loaded()
# Check whether certificate is signed by CA certificate
if not cryptography_verify_certificate_signature(self.existing_certificate, self.ca_cert.public_key()):
return True
# Check subject
if self.ca_cert.subject != self.existing_certificate.issuer:
return True
# Check AuthorityKeyIdentifier # Check AuthorityKeyIdentifier
if self.create_authority_key_identifier: if self.create_authority_key_identifier:
try: try:
@@ -184,7 +200,6 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
except cryptography.x509.ExtensionNotFound: except cryptography.x509.ExtensionNotFound:
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key()) expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key())
self._ensure_existing_certificate_loaded()
try: try:
ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
if ext.value != expected_ext: if ext.value != expected_ext:
@@ -296,6 +311,18 @@ class OwnCACertificateBackendPyOpenSSL(CertificateBackend):
"""Return bytes for self.cert.""" """Return bytes for self.cert."""
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert) return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
def needs_regeneration(self):
if super(OwnCACertificateBackendPyOpenSSL, self).needs_regeneration():
return True
self._ensure_existing_certificate_loaded()
# Check subject
if self.ca_cert.get_subject() != self.existing_certificate.get_issuer():
return True
return False
def dump(self, include_certificate): def dump(self, include_certificate):
result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate) result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate)
result.update({ result.update({

View File

@@ -22,6 +22,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_key_needs_digest_for_signing, cryptography_key_needs_digest_for_signing,
cryptography_serial_number_of_cert, cryptography_serial_number_of_cert,
cryptography_verify_certificate_signature,
) )
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -32,7 +33,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
try: try:
from OpenSSL import crypto from OpenSSL import crypto
except ImportError: except (ImportError, AttributeError):
pass pass
try: try:
@@ -134,6 +135,18 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
"""Return bytes for self.cert.""" """Return bytes for self.cert."""
return self.cert.public_bytes(Encoding.PEM) return self.cert.public_bytes(Encoding.PEM)
def needs_regeneration(self):
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration():
return True
self._ensure_existing_certificate_loaded()
# Check whether certificate is signed by private key
if not cryptography_verify_certificate_signature(self.existing_certificate, self.privatekey.public_key()):
return True
return False
def dump(self, include_certificate): def dump(self, include_certificate):
result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate) result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)

View File

@@ -9,10 +9,10 @@ __metaclass__ = type
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_oid_to_name, cryptography_oid_to_name,
) )

View File

@@ -12,12 +12,12 @@ import abc
import binascii import binascii
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError, OpenSSLObjectError,
OpenSSLBadPassphraseError, OpenSSLBadPassphraseError,
@@ -63,7 +63,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:
@@ -528,8 +528,8 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
if self.name_constraints_permitted or self.name_constraints_excluded: if self.name_constraints_permitted or self.name_constraints_excluded:
try: try:
csr = csr.add_extension(cryptography.x509.NameConstraints( csr = csr.add_extension(cryptography.x509.NameConstraints(
[cryptography_get_name(name, 'name constraints permitted') for name in self.name_constraints_permitted], [cryptography_get_name(name, 'name constraints permitted') for name in self.name_constraints_permitted] or None,
[cryptography_get_name(name, 'name constraints excluded') for name in self.name_constraints_excluded], [cryptography_get_name(name, 'name constraints excluded') for name in self.name_constraints_excluded] or None,
), critical=self.name_constraints_critical) ), critical=self.name_constraints_critical)
except TypeError as e: except TypeError as e:
raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e)) raise OpenSSLObjectError('Error while parsing name constraint: {0}'.format(e))
@@ -678,8 +678,8 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def _check_nameConstraints(extensions): def _check_nameConstraints(extensions):
current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints) current_nc_ext = _find_extension(extensions, cryptography.x509.NameConstraints)
current_nc_perm = [to_text(altname) for altname in current_nc_ext.value.permitted_subtrees] if current_nc_ext else [] current_nc_perm = [to_text(altname) for altname in current_nc_ext.value.permitted_subtrees or []] if current_nc_ext else []
current_nc_excl = [to_text(altname) for altname in current_nc_ext.value.excluded_subtrees] if current_nc_ext else [] current_nc_excl = [to_text(altname) for altname in current_nc_ext.value.excluded_subtrees or []] if current_nc_ext else []
nc_perm = [to_text(cryptography_get_name(altname, 'name constraints permitted')) for altname in self.name_constraints_permitted] nc_perm = [to_text(cryptography_get_name(altname, 'name constraints permitted')) for altname in self.name_constraints_permitted]
nc_excl = [to_text(cryptography_get_name(altname, 'name constraints excluded')) for altname in self.name_constraints_excluded] nc_excl = [to_text(cryptography_get_name(altname, 'name constraints excluded')) for altname in self.name_constraints_excluded]
if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl): if set(nc_perm) != set(current_nc_perm) or set(nc_excl) != set(current_nc_excl):

View File

@@ -13,12 +13,12 @@ import abc
import binascii import binascii
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate_request, load_certificate_request,
get_fingerprint_of_bytes, get_fingerprint_of_bytes,
@@ -57,7 +57,7 @@ try:
# OpenSSL 1.0.x or older # OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24" OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05" OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -12,12 +12,12 @@ import abc
import base64 import base64
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_X25519, CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL, CRYPTOGRAPHY_HAS_X25519_FULL,
@@ -54,7 +54,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -12,12 +12,12 @@ __metaclass__ = type
import abc import abc
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_bytes from ansible.module_utils.common.text.converters import to_native, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_ED25519, CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448, CRYPTOGRAPHY_HAS_ED448,
@@ -49,7 +49,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -10,12 +10,12 @@ __metaclass__ = type
import abc import abc
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
CRYPTOGRAPHY_HAS_X25519, CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X448, CRYPTOGRAPHY_HAS_X448,
@@ -38,7 +38,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:
@@ -215,7 +215,7 @@ class PublicKeyInfoRetrieval(object):
try: try:
self.key = load_publickey(content=self.content, backend=self.backend) self.key = load_publickey(content=self.content, backend=self.backend)
except OpenSSLObjectError as e: except OpenSSLObjectError as e:
raise PublicKeyParseError(to_native(e)) raise PublicKeyParseError(to_native(e), {})
pk = self._get_public_key(binary=True) pk = self._get_public_key(binary=True)
result['fingerprints'] = get_fingerprint_of_bytes( result['fingerprints'] = get_fingerprint_of_bytes(

View File

@@ -72,3 +72,13 @@ def split_pem_list(text, keep_inbetween=False):
result.append(''.join(current)) result.append(''.join(current))
current = [] if keep_inbetween else None current = [] if keep_inbetween else None
return result return result
def extract_first_pem(text):
'''
Given one PEM or multiple concatenated PEM objects, return only the first one, or None if there is none.
'''
all_pems = split_pem_list(text)
if not all_pems:
return None
return all_pems[0]

View File

@@ -21,13 +21,15 @@ __metaclass__ = type
import base64 import base64
from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.common.text.converters import to_bytes, to_text, to_native
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ._objects import OID_LOOKUP
try: try:
import OpenSSL import OpenSSL
except ImportError: except (ImportError, AttributeError):
# Error handled in the calling module. # Error handled in the calling module.
pass pass
@@ -87,18 +89,25 @@ def pyopenssl_get_extensions_from_cert(cert):
critical=bool(ext.get_critical()), critical=bool(ext.get_critical()),
value=base64.b64encode(ext.get_data()), value=base64.b64encode(ext.get_data()),
) )
oid = obj2txt( try:
OpenSSL._util.lib, oid = obj2txt(
OpenSSL._util.ffi, OpenSSL._util.lib,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension) OpenSSL._util.ffi,
) OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
# This could also be done a bit simpler: )
# # This could also be done a bit simpler:
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid)) #
# # oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
# Unfortunately this gives the wrong result in case the linked OpenSSL #
# doesn't know the OID. That's why we have to get the OID dotted string # Unfortunately this gives the wrong result in case the linked OpenSSL
# similarly to how cryptography does it. # doesn't know the OID. That's why we have to get the OID dotted string
# similarly to how cryptography does it.
except AttributeError:
# When PyOpenSSL is used with cryptography >= 35.0.0, obj2txt cannot be used.
# We try to figure out the OID with our internal lookup table, and if we fail,
# we use the short name OpenSSL returns.
oid = to_native(ext.get_short_name())
oid = OID_LOOKUP.get(oid, oid)
result[oid] = entry result[oid] = entry
return result return result
@@ -113,18 +122,25 @@ def pyopenssl_get_extensions_from_csr(csr):
critical=bool(ext.get_critical()), critical=bool(ext.get_critical()),
value=base64.b64encode(ext.get_data()), value=base64.b64encode(ext.get_data()),
) )
oid = obj2txt( try:
OpenSSL._util.lib, oid = obj2txt(
OpenSSL._util.ffi, OpenSSL._util.lib,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension) OpenSSL._util.ffi,
) OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
# This could also be done a bit simpler: )
# # This could also be done a bit simpler:
# oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid)) #
# # oid = obj2txt(OpenSSL._util.lib, OpenSSL._util.ffi, OpenSSL._util.lib.OBJ_nid2obj(ext._nid))
# Unfortunately this gives the wrong result in case the linked OpenSSL #
# doesn't know the OID. That's why we have to get the OID dotted string # Unfortunately this gives the wrong result in case the linked OpenSSL
# similarly to how cryptography does it. # doesn't know the OID. That's why we have to get the OID dotted string
# similarly to how cryptography does it.
except AttributeError:
# When PyOpenSSL is used with cryptography >= 35.0.0, obj2txt cannot be used.
# We try to figure out the OID with our internal lookup table, and if we fail,
# we use the short name OpenSSL returns.
oid = to_native(ext.get_short_name())
oid = OID_LOOKUP.get(oid, oid)
result[oid] = entry result[oid] = entry
return result return result

View File

@@ -32,7 +32,7 @@ from ansible.module_utils.common.text.converters import to_native, to_bytes
try: try:
from OpenSSL import crypto from OpenSSL import crypto
HAS_PYOPENSSL = True HAS_PYOPENSSL = True
except ImportError: except (ImportError, AttributeError):
# Error handled in the calling module. # Error handled in the calling module.
HAS_PYOPENSSL = False HAS_PYOPENSSL = False

View File

@@ -7,7 +7,7 @@
# their own license to the complete work. # their own license to the complete work.
# #
# Copyright (c), Entrust Datacard Corporation, 2019 # Copyright (c), Entrust Datacard Corporation, 2019
# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) # Simplified BSD License (see simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met: # are permitted provided that the following conditions are met:

View File

@@ -21,9 +21,11 @@ __metaclass__ = type
import abc import abc
import os import os
import stat import stat
import traceback
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import ( from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import (
parse_openssh_version, parse_openssh_version,
) )
@@ -75,7 +77,14 @@ class OpensshModule(object):
self.check_mode = self.module.check_mode self.check_mode = self.module.check_mode
def execute(self): def execute(self):
self._execute() try:
self._execute()
except Exception as e:
self.module.fail_json(
msg="unexpected error occurred: %s" % to_native(e),
exception=traceback.format_exc(),
)
self.module.exit_json(**self.result) self.module.exit_json(**self.result)
@abc.abstractmethod @abc.abstractmethod

View File

@@ -21,12 +21,13 @@ __metaclass__ = type
import abc import abc
import os import os
from distutils.version import LooseVersion
from ansible.module_utils import six from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptography import ( from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptography import (
HAS_OPENSSH_SUPPORT, HAS_OPENSSH_SUPPORT,
HAS_OPENSSH_PRIVATE_FORMAT, HAS_OPENSSH_PRIVATE_FORMAT,

View File

@@ -20,10 +20,11 @@ __metaclass__ = type
import os import os
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from distutils.version import LooseVersion
from getpass import getuser from getpass import getuser
from socket import gethostname from socket import gethostname
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
try: try:
from cryptography import __version__ as CRYPTOGRAPHY_VERSION from cryptography import __version__ as CRYPTOGRAPHY_VERSION
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2021, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Provide version object to compare version numbers."""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# Once we drop support for Ansible 2.9, ansible-base 2.10, and ansible-core 2.11, we can
# remove the _version.py file, and replace the following import by
#
# from ansible.module_utils.compat.version import LooseVersion
from ._version import LooseVersion

View File

@@ -54,7 +54,7 @@ EXAMPLES = '''
- name: Check whether an account with the given account key exists - name: Check whether an account with the given account key exists
community.crypto.acme_account_info: community.crypto.acme_account_info:
account_key_src: /etc/pki/cert/private/account.key account_key_src: /etc/pki/cert/private/account.key
register: account_data register: account_data
- name: Verify that account exists - name: Verify that account exists
assert: assert:
that: that:
@@ -70,7 +70,7 @@ EXAMPLES = '''
acme_account_info: acme_account_info:
account_key_content: "{{ acme_account_key }}" account_key_content: "{{ acme_account_key }}"
account_uri: "{{ acme_account_uri }}" account_uri: "{{ acme_account_uri }}"
register: account_data register: account_data
- name: Verify that account exists - name: Verify that account exists
assert: assert:
that: that:
@@ -101,7 +101,7 @@ account:
returned: always returned: always
type: list type: list
elements: str elements: str
sample: "['mailto:me@example.com', 'tel:00123456789']" sample: ['mailto:me@example.com', 'tel:00123456789']
status: status:
description: the account's status description: the account's status
returned: always returned: always

View File

@@ -308,7 +308,7 @@ EXAMPLES = r'''
# - copy: # - copy:
# dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }} # dest: /var/www/html/{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource'] }}
# content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}" # content: "{{ sample_com_challenge['challenge_data']['sample.com']['http-01']['resource_value'] }}"
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data'] # when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge['challenge_data']
# #
# Alternative way: # Alternative way:
# #
@@ -467,7 +467,20 @@ authorizations:
- Maps an identifier to ACME authorization objects. See U(https://tools.ietf.org/html/rfc8555#section-7.1.4). - Maps an identifier to ACME authorization objects. See U(https://tools.ietf.org/html/rfc8555#section-7.1.4).
returned: changed returned: changed
type: dict type: dict
sample: '{"example.com":{...}}' sample:
example.com:
identifier:
type: dns
value: example.com
status: valid
expires: '2022-08-04T01:02:03.45Z'
challenges:
- url: https://example.org/acme/challenge/12345
type: http-01
status: valid
token: A5b1C3d2E9f8G7h6
validated: '2022-08-01T01:01:02.34Z'
wildcard: false
order_uri: order_uri:
description: ACME order URI. description: ACME order URI.
returned: changed returned: changed

View File

@@ -145,6 +145,8 @@ import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes, to_text from ansible.module_utils.common.text.converters import to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
from ansible_collections.community.crypto.plugins.module_utils.acme.io import ( from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
@@ -164,7 +166,6 @@ try:
import cryptography.x509 import cryptography.x509
import cryptography.x509.oid import cryptography.x509.oid
import ipaddress import ipaddress
from distutils.version import LooseVersion
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3')) HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3'))
_cryptography_backend = cryptography.hazmat.backends.default_backend() _cryptography_backend = cryptography.hazmat.backends.default_backend()
except ImportError as dummy: except ImportError as dummy:

View File

@@ -183,7 +183,7 @@ directory:
description: The ACME directory's content description: The ACME directory's content
returned: always returned: always
type: dict type: dict
sample: | sample:
{ {
"a85k3x9f91A4": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417", "a85k3x9f91A4": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
"keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change", "keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
@@ -203,7 +203,7 @@ headers:
description: The request's HTTP headers (with lowercase keys) description: The request's HTTP headers (with lowercase keys)
returned: always returned: always
type: dict type: dict
sample: | sample:
{ {
"boulder-requester": "12345", "boulder-requester": "12345",
"cache-control": "max-age=0, no-cache, no-store", "cache-control": "max-age=0, no-cache, no-store",
@@ -214,7 +214,7 @@ headers:
"cookies_string": "", "cookies_string": "",
"date": "Wed, 07 Nov 2018 12:34:56 GMT", "date": "Wed, 07 Nov 2018 12:34:56 GMT",
"expires": "Wed, 07 Nov 2018 12:44:56 GMT", "expires": "Wed, 07 Nov 2018 12:44:56 GMT",
"link": "<https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf>;rel=\"terms-of-service\"", "link": '<https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf>;rel="terms-of-service"',
"msg": "OK (904 bytes)", "msg": "OK (904 bytes)",
"pragma": "no-cache", "pragma": "no-cache",
"replay-nonce": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGH", "replay-nonce": "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGH",

View File

@@ -124,6 +124,8 @@ import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
split_pem_list, split_pem_list,
) )
@@ -131,6 +133,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
CRYPTOGRAPHY_IMP_ERR = None CRYPTOGRAPHY_IMP_ERR = None
try: try:
import cryptography import cryptography
import cryptography.exceptions
import cryptography.hazmat.backends import cryptography.hazmat.backends
import cryptography.hazmat.primitives.serialization import cryptography.hazmat.primitives.serialization
import cryptography.hazmat.primitives.asymmetric.rsa import cryptography.hazmat.primitives.asymmetric.rsa
@@ -140,7 +143,6 @@ try:
import cryptography.hazmat.primitives.asymmetric.utils import cryptography.hazmat.primitives.asymmetric.utils
import cryptography.x509 import cryptography.x509
import cryptography.x509.oid import cryptography.x509.oid
from distutils.version import LooseVersion
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5')) HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5'))
_cryptography_backend = cryptography.hazmat.backends.default_backend() _cryptography_backend = cryptography.hazmat.backends.default_backend()
except ImportError as dummy: except ImportError as dummy:
@@ -189,6 +191,9 @@ def is_parent(module, cert, potential_parent):
return True return True
except cryptography.exceptions.InvalidSignature as dummy: except cryptography.exceptions.InvalidSignature as dummy:
return False return False
except cryptography.exceptions.UnsupportedAlgorithm as dummy:
module.warn('Unsupported algorithm "{0}"'.format(cert.cert.signature_hash_algorithm))
return False
except Exception as e: except Exception as e:
module.fail_json(msg='Unknown error on signature validation: {0}'.format(e)) module.fail_json(msg='Unknown error on signature validation: {0}'.format(e))
@@ -236,13 +241,17 @@ class CertificateSet(object):
def __init__(self, module): def __init__(self, module):
self.module = module self.module = module
self.certificates = set() self.certificates = set()
self.certificate_by_issuer = dict() self.certificates_by_issuer = dict()
self.certificate_by_cert = dict()
def _load_file(self, path): def _load_file(self, path):
certs = load_PEM_list(self.module, path, fail_on_error=False) certs = load_PEM_list(self.module, path, fail_on_error=False)
for cert in certs: for cert in certs:
self.certificates.add(cert) self.certificates.add(cert)
self.certificate_by_issuer[cert.cert.subject] = cert if cert.cert.subject not in self.certificates_by_issuer:
self.certificates_by_issuer[cert.cert.subject] = []
self.certificates_by_issuer[cert.cert.subject].append(cert)
self.certificate_by_cert[cert.cert] = cert
def load(self, path): def load(self, path):
''' '''
@@ -260,8 +269,8 @@ class CertificateSet(object):
''' '''
Search for the parent (issuer) of a certificate. Return ``None`` if none was found. Search for the parent (issuer) of a certificate. Return ``None`` if none was found.
''' '''
potential_parent = self.certificate_by_issuer.get(cert.cert.issuer) potential_parents = self.certificates_by_issuer.get(cert.cert.issuer, [])
if potential_parent is not None: for potential_parent in potential_parents:
if is_parent(self.module, cert, potential_parent): if is_parent(self.module, cert, potential_parent):
return potential_parent return potential_parent
return None return None
@@ -274,6 +283,16 @@ def format_cert(cert):
return str(cert.cert) return str(cert.cert)
def check_cycle(module, occured_certificates, next):
'''
Make sure that next is not in occured_certificates so far, and add it.
'''
next_cert = next.cert
if next_cert in occured_certificates:
module.fail_json(msg='Found cycle while building certificate chain')
occured_certificates.add(next_cert)
def main(): def main():
module = AnsibleModule( module = AnsibleModule(
argument_spec=dict( argument_spec=dict(
@@ -312,13 +331,19 @@ def main():
# Try to complete chain # Try to complete chain
current = chain[-1] current = chain[-1]
completed = [] completed = []
occured_certificates = set([cert.cert for cert in chain])
if current.cert in roots.certificate_by_cert:
# Don't try to complete the chain when it's already ending with a root certificate
current = None
while current: while current:
root = roots.find_parent(current) root = roots.find_parent(current)
if root: if root:
check_cycle(module, occured_certificates, root)
completed.append(root) completed.append(root)
break break
intermediate = intermediates.find_parent(current) intermediate = intermediates.find_parent(current)
if intermediate: if intermediate:
check_cycle(module, occured_certificates, intermediate)
completed.append(intermediate) completed.append(intermediate)
current = intermediate current = intermediate
else: else:

View File

@@ -519,11 +519,11 @@ import re
import time import time
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_bytes from ansible.module_utils.common.text.converters import to_native, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.io import ( from ansible_collections.community.crypto.plugins.module_utils.io import (
write_file, write_file,
) )

View File

@@ -167,7 +167,6 @@ import base64
import datetime import datetime
import traceback import traceback
from distutils.version import LooseVersion
from os.path import isfile from os.path import isfile
from socket import create_connection, setdefaulttimeout, socket from socket import create_connection, setdefaulttimeout, socket
from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_REQUIRED
@@ -175,6 +174,8 @@ from ssl import get_server_certificate, DER_cert_to_PEM_cert, CERT_NONE, CERT_RE
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_oid_to_name, cryptography_oid_to_name,
cryptography_get_extensions_from_cert, cryptography_get_extensions_from_cert,
@@ -197,7 +198,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -350,12 +350,40 @@ STDERR = 2
# used to get <luks-name> out of lsblk output in format 'crypt <luks-name>' # used to get <luks-name> out of lsblk output in format 'crypt <luks-name>'
# regex takes care of any possible blank characters # regex takes care of any possible blank characters
LUKS_NAME_REGEX = re.compile(r'\s*crypt\s+([^\s]*)\s*') LUKS_NAME_REGEX = re.compile(r'^crypt\s+([^\s]*)\s*$')
# used to get </luks/device> out of lsblk output # used to get </luks/device> out of lsblk output
# in format 'device: </luks/device>' # in format 'device: </luks/device>'
LUKS_DEVICE_REGEX = re.compile(r'\s*device:\s+([^\s]*)\s*') LUKS_DEVICE_REGEX = re.compile(r'\s*device:\s+([^\s]*)\s*')
# See https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf
LUKS_HEADER = b'LUKS\xba\xbe'
LUKS_HEADER_L = 6
# See https://gitlab.com/cryptsetup/LUKS2-docs/-/blob/master/luks2_doc_wip.pdf
LUKS2_HEADER_OFFSETS = [0x4000, 0x8000, 0x10000, 0x20000, 0x40000, 0x80000, 0x100000, 0x200000, 0x400000]
LUKS2_HEADER2 = b'SKUL\xba\xbe'
def wipe_luks_headers(device):
wipe_offsets = []
with open(device, 'rb') as f:
# f.seek(0)
data = f.read(LUKS_HEADER_L)
if data == LUKS_HEADER:
wipe_offsets.append(0)
for offset in LUKS2_HEADER_OFFSETS:
f.seek(offset)
data = f.read(LUKS_HEADER_L)
if data == LUKS2_HEADER2:
wipe_offsets.append(offset)
if wipe_offsets:
with open(device, 'wb') as f:
for offset in wipe_offsets:
f.seek(offset)
f.write(b'\x00\x00\x00\x00\x00\x00')
class Handler(object): class Handler(object):
def __init__(self, module): def __init__(self, module):
@@ -418,13 +446,11 @@ class CryptHandler(Handler):
raise ValueError('Error while obtaining LUKS name for %s: %s' raise ValueError('Error while obtaining LUKS name for %s: %s'
% (device, result[STDERR])) % (device, result[STDERR]))
m = LUKS_NAME_REGEX.search(result[STDOUT]) for line in result[STDOUT].splitlines(False):
m = LUKS_NAME_REGEX.match(line)
try: if m:
name = m.group(1) return m.group(1)
except AttributeError: return None
name = None
return name
def get_container_device_by_name(self, name): def get_container_device_by_name(self, name):
''' obtain device name based on the LUKS container name ''' obtain device name based on the LUKS container name
@@ -515,9 +541,17 @@ class CryptHandler(Handler):
self.run_luks_close(name) self.run_luks_close(name)
result = self._run_command([wipefs_bin, '--all', device]) result = self._run_command([wipefs_bin, '--all', device])
if result[RETURN_CODE] != 0: if result[RETURN_CODE] != 0:
raise ValueError('Error while wiping luks container %s: %s' raise ValueError('Error while wiping LUKS container signatures for %s: %s'
% (device, result[STDERR])) % (device, result[STDERR]))
# For LUKS2, sometimes both `cryptsetup erase` and `wipefs` do **not**
# erase all LUKS signatures (they seem to miss the second header). That's
# why we do it ourselves here.
try:
wipe_luks_headers(device)
except Exception as exc:
raise ValueError('Error while wiping LUKS container signatures for %s: %s' % (device, exc))
def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile, def run_luks_add_key(self, device, keyfile, passphrase, new_keyfile,
new_passphrase, pbkdf): new_passphrase, pbkdf):
''' Add new key from a keyfile or passphrase to given 'device'; ''' Add new key from a keyfile or passphrase to given 'device';
@@ -785,6 +819,7 @@ def run_module():
module = AnsibleModule(argument_spec=module_args, module = AnsibleModule(argument_spec=module_args,
supports_check_mode=True, supports_check_mode=True,
mutually_exclusive=mutually_exclusive) mutually_exclusive=mutually_exclusive)
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
if module.params['device'] is not None: if module.params['device'] is not None:
try: try:

View File

@@ -258,11 +258,12 @@ info:
''' '''
import os import os
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.common.text.converters import to_native, to_text
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.openssh.backends.common import ( from ansible_collections.community.crypto.plugins.module_utils.openssh.backends.common import (
KeygenCommand, KeygenCommand,
OpensshModule, OpensshModule,
@@ -378,7 +379,7 @@ class Certificate(OpensshModule):
def _is_fully_valid(self): def _is_fully_valid(self):
return self._is_partially_valid() and all([ return self._is_partially_valid() and all([
self._compare_options(), self._compare_options() if self.original_data.type == 'user' else True,
self.original_data.key_id == self.identifier, self.original_data.key_id == self.identifier,
self.original_data.public_key == self._get_key_fingerprint(self.public_key), self.original_data.public_key == self._get_key_fingerprint(self.public_key),
self.original_data.signing_key == self._get_key_fingerprint(self.signing_key), self.original_data.signing_key == self._get_key_fingerprint(self.signing_key),

View File

@@ -178,7 +178,7 @@ public_key:
description: The public key of the generated SSH private key. description: The public key of the generated SSH private key.
returned: changed or success returned: changed or success
type: str type: str
sample: ssh-rsa AAAAB3Nza(...omitted...)veL4E3Xcw== test_key sample: ssh-rsa AAAAB3Nza(...omitted...)veL4E3Xcw==
comment: comment:
description: The comment of the generated key. description: The comment of the generated key.
returned: changed or success returned: changed or success

View File

@@ -177,7 +177,7 @@ subject:
returned: changed or success returned: changed or success
type: list type: list
elements: list elements: list
sample: "[('CN', 'www.ansible.com'), ('O', 'Ansible')]" sample: [['CN', 'www.ansible.com'], ['O', 'Ansible']]
subjectAltName: subjectAltName:
description: The alternative names this CSR is valid for description: The alternative names this CSR is valid for
returned: changed or success returned: changed or success

View File

@@ -85,7 +85,7 @@ basic_constraints:
returned: success returned: success
type: list type: list
elements: str elements: str
sample: "[CA:TRUE, pathlen:1]" sample: ['CA:TRUE', 'pathlen:1']
basic_constraints_critical: basic_constraints_critical:
description: Whether the C(basic_constraints) extension is critical. description: Whether the C(basic_constraints) extension is critical.
returned: success returned: success
@@ -95,7 +95,7 @@ extended_key_usage:
returned: success returned: success
type: list type: list
elements: str elements: str
sample: "[Biometric Info, DVCS, Time Stamping]" sample: [Biometric Info, DVCS, Time Stamping]
extended_key_usage_critical: extended_key_usage_critical:
description: Whether the C(extended_key_usage) extension is critical. description: Whether the C(extended_key_usage) extension is critical.
returned: success returned: success
@@ -114,12 +114,12 @@ extensions_by_oid:
returned: success returned: success
type: str type: str
sample: "MAMCAQU=" sample: "MAMCAQU="
sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}' sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
key_usage: key_usage:
description: Entries in the C(key_usage) extension, or C(none) if extension is not present. description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
returned: success returned: success
type: str type: str
sample: "[Key Agreement, Data Encipherment]" sample: [Key Agreement, Data Encipherment]
key_usage_critical: key_usage_critical:
description: Whether the C(key_usage) extension is critical. description: Whether the C(key_usage) extension is critical.
returned: success returned: success
@@ -129,7 +129,7 @@ subject_alt_name:
returned: success returned: success
type: list type: list
elements: str elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]" sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
subject_alt_name_critical: subject_alt_name_critical:
description: Whether the C(subject_alt_name) extension is critical. description: Whether the C(subject_alt_name) extension is critical.
returned: success returned: success
@@ -171,13 +171,13 @@ subject:
- Note that for repeated values, only the last one will be returned. - Note that for repeated values, only the last one will be returned.
returned: success returned: success
type: dict type: dict
sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}' sample: {"commonName": "www.example.com", "emailAddress": "test@example.com"}
subject_ordered: subject_ordered:
description: The CSR's subject as an ordered list of tuples. description: The CSR's subject as an ordered list of tuples.
returned: success returned: success
type: list type: list
elements: list elements: list
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]' sample: [["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]
public_key: public_key:
description: CSR's public key in PEM format description: CSR's public key in PEM format
returned: success returned: success
@@ -285,14 +285,14 @@ authority_cert_issuer:
returned: success and if the pyOpenSSL backend is I(not) used returned: success and if the pyOpenSSL backend is I(not) used
type: list type: list
elements: str elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]" sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
authority_cert_serial_number: authority_cert_serial_number:
description: description:
- The CSR's authority cert serial number. - The CSR's authority cert serial number.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used returned: success and if the pyOpenSSL backend is I(not) used
type: int type: int
sample: '12345' sample: 12345
''' '''

View File

@@ -66,7 +66,7 @@ subject:
returned: changed or success returned: changed or success
type: list type: list
elements: list elements: list
sample: "[('CN', 'www.ansible.com'), ('O', 'Ansible')]" sample: [['CN', 'www.ansible.com'], ['O', 'Ansible']]
subjectAltName: subjectAltName:
description: The alternative names this CSR is valid for description: The alternative names this CSR is valid for
returned: changed or success returned: changed or success

View File

@@ -128,11 +128,11 @@ import re
import tempfile import tempfile
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.io import ( from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists, load_file_if_exists,
write_file, write_file,

View File

@@ -239,11 +239,11 @@ import os
import stat import stat
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes, to_native from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.io import ( from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists, load_file_if_exists,
write_file, write_file,
@@ -276,7 +276,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:
@@ -542,6 +542,8 @@ class PkcsPyOpenSSL(Pkcs):
return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) if cert else None return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) if cert else None
def _dump_other_certificates(self, pkcs12): def _dump_other_certificates(self, pkcs12):
if pkcs12.get_ca_certificates() is None:
return []
return [ return [
crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert) crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert)
for other_cert in pkcs12.get_ca_certificates() for other_cert in pkcs12.get_ca_certificates()

View File

@@ -13,8 +13,9 @@ DOCUMENTATION = r'''
module: openssl_publickey module: openssl_publickey
short_description: Generate an OpenSSL public key from its private key. short_description: Generate an OpenSSL public key from its private key.
description: description:
- This module allows one to (re)generate OpenSSL public keys from their private keys. - This module allows one to (re)generate public keys from their private keys.
- Keys are generated in PEM or OpenSSH format. - Public keys are generated in PEM or OpenSSH format. Private keys must be OpenSSL PEM keys.
OpenSSH private keys are not supported, use the M(community.crypto.openssh_keypair) module to manage these.
- "The module can use the cryptography Python library, or the pyOpenSSL Python - "The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. When I(format) is C(OpenSSH), overridden with the I(select_crypto_backend) option. When I(format) is C(OpenSSH),
@@ -182,11 +183,11 @@ publickey:
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.io import ( from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file_if_exists, load_file_if_exists,
write_file, write_file,
@@ -217,7 +218,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -61,7 +61,7 @@ EXAMPLES = r'''
path: /etc/ssl/private/ansible.com.pem path: /etc/ssl/private/ansible.com.pem
- name: Create public key from private key - name: Create public key from private key
community.crypto.openssl_privatekey: community.crypto.openssl_publickey:
privatekey_path: /etc/ssl/private/ansible.com.pem privatekey_path: /etc/ssl/private/ansible.com.pem
path: /etc/ssl/ansible.com.pub path: /etc/ssl/ansible.com.pub

View File

@@ -96,9 +96,10 @@ signature:
import os import os
import traceback import traceback
from distutils.version import LooseVersion
import base64 import base64
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
MINIMAL_PYOPENSSL_VERSION = '0.11' MINIMAL_PYOPENSSL_VERSION = '0.11'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4' MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
@@ -107,7 +108,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -96,9 +96,10 @@ valid:
import os import os
import traceback import traceback
from distutils.version import LooseVersion
import base64 import base64
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
MINIMAL_PYOPENSSL_VERSION = '0.11' MINIMAL_PYOPENSSL_VERSION = '0.11'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4' MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'
@@ -107,7 +108,7 @@ try:
import OpenSSL import OpenSSL
from OpenSSL import crypto from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__) PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except ImportError: except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc() PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False PYOPENSSL_FOUND = False
else: else:

View File

@@ -129,7 +129,7 @@ basic_constraints:
returned: success returned: success
type: list type: list
elements: str elements: str
sample: "[CA:TRUE, pathlen:1]" sample: ["CA:TRUE", "pathlen:1"]
basic_constraints_critical: basic_constraints_critical:
description: Whether the C(basic_constraints) extension is critical. description: Whether the C(basic_constraints) extension is critical.
returned: success returned: success
@@ -139,7 +139,7 @@ extended_key_usage:
returned: success returned: success
type: list type: list
elements: str elements: str
sample: "[Biometric Info, DVCS, Time Stamping]" sample: [Biometric Info, DVCS, Time Stamping]
extended_key_usage_critical: extended_key_usage_critical:
description: Whether the C(extended_key_usage) extension is critical. description: Whether the C(extended_key_usage) extension is critical.
returned: success returned: success
@@ -158,12 +158,12 @@ extensions_by_oid:
returned: success returned: success
type: str type: str
sample: "MAMCAQU=" sample: "MAMCAQU="
sample: '{"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}' sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
key_usage: key_usage:
description: Entries in the C(key_usage) extension, or C(none) if extension is not present. description: Entries in the C(key_usage) extension, or C(none) if extension is not present.
returned: success returned: success
type: str type: str
sample: "[Key Agreement, Data Encipherment]" sample: [Key Agreement, Data Encipherment]
key_usage_critical: key_usage_critical:
description: Whether the C(key_usage) extension is critical. description: Whether the C(key_usage) extension is critical.
returned: success returned: success
@@ -173,7 +173,7 @@ subject_alt_name:
returned: success returned: success
type: list type: list
elements: str elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]" sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
subject_alt_name_critical: subject_alt_name_critical:
description: Whether the C(subject_alt_name) extension is critical. description: Whether the C(subject_alt_name) extension is critical.
returned: success returned: success
@@ -192,36 +192,36 @@ issuer:
- Note that for repeated values, only the last one will be returned. - Note that for repeated values, only the last one will be returned.
returned: success returned: success
type: dict type: dict
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
issuer_ordered: issuer_ordered:
description: The certificate's issuer as an ordered list of tuples. description: The certificate's issuer as an ordered list of tuples.
returned: success returned: success
type: list type: list
elements: list elements: list
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
subject: subject:
description: description:
- The certificate's subject as a dictionary. - The certificate's subject as a dictionary.
- Note that for repeated values, only the last one will be returned. - Note that for repeated values, only the last one will be returned.
returned: success returned: success
type: dict type: dict
sample: '{"commonName": "www.example.com", "emailAddress": "test@example.com"}' sample: {"commonName": "www.example.com", "emailAddress": "test@example.com"}
subject_ordered: subject_ordered:
description: The certificate's subject as an ordered list of tuples. description: The certificate's subject as an ordered list of tuples.
returned: success returned: success
type: list type: list
elements: list elements: list
sample: '[["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]' sample: [["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]
not_after: not_after:
description: C(notAfter) date as ASN.1 TIME. description: C(notAfter) date as ASN.1 TIME.
returned: success returned: success
type: str type: str
sample: 20190413202428Z sample: '20190413202428Z'
not_before: not_before:
description: C(notBefore) date as ASN.1 TIME. description: C(notBefore) date as ASN.1 TIME.
returned: success returned: success
type: str type: str
sample: 20190331202428Z sample: '20190331202428Z'
public_key: public_key:
description: Certificate's public key in PEM format. description: Certificate's public key in PEM format.
returned: success returned: success
@@ -359,14 +359,14 @@ authority_cert_issuer:
returned: success and if the pyOpenSSL backend is I(not) used returned: success and if the pyOpenSSL backend is I(not) used
type: list type: list
elements: str elements: str
sample: "[DNS:www.ansible.com, IP:1.2.3.4]" sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
authority_cert_serial_number: authority_cert_serial_number:
description: description:
- The certificate's authority cert serial number. - The certificate's authority cert serial number.
- Is C(none) if the C(AuthorityKeyIdentifier) extension is not present. - Is C(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success and if the pyOpenSSL backend is I(not) used returned: success and if the pyOpenSSL backend is I(not) used
type: int type: int
sample: '12345' sample: 12345
ocsp_uri: ocsp_uri:
description: The OCSP responder URI, if included in the certificate. Will be description: The OCSP responder URI, if included in the certificate. Will be
C(none) if no OCSP responder URI is included. C(none) if no OCSP responder URI is included.

View File

@@ -286,13 +286,13 @@ issuer:
- Note that for repeated values, only the last one will be returned. - Note that for repeated values, only the last one will be returned.
returned: success returned: success
type: dict type: dict
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
issuer_ordered: issuer_ordered:
description: The CRL's issuer as an ordered list of tuples. description: The CRL's issuer as an ordered list of tuples.
returned: success returned: success
type: list type: list
elements: list elements: list
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
last_update: last_update:
description: The point in time from which this CRL can be trusted as ASN.1 TIME. description: The point in time from which this CRL can be trusted as ASN.1 TIME.
returned: success returned: success
@@ -326,7 +326,7 @@ revoked_certificates:
description: The certificate's issuer. description: The certificate's issuer.
type: list type: list
elements: str elements: str
sample: '["DNS:ca.example.org"]' sample: ["DNS:ca.example.org"]
issuer_critical: issuer_critical:
description: Whether the certificate issuer extension is critical. description: Whether the certificate issuer extension is critical.
type: bool type: bool
@@ -367,11 +367,11 @@ import base64
import os import os
import traceback import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_text from ansible.module_utils.common.text.converters import to_native, to_text
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.io import ( from ansible_collections.community.crypto.plugins.module_utils.io import (
write_file, write_file,
) )
@@ -392,6 +392,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import ( from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_get_name, cryptography_get_name,
cryptography_key_needs_digest_for_signing,
cryptography_name_to_oid, cryptography_name_to_oid,
cryptography_oid_to_name, cryptography_oid_to_name,
cryptography_serial_number_of_cert, cryptography_serial_number_of_cert,
@@ -612,8 +613,12 @@ class CRL(OpenSSLObject):
return False return False
if self.next_update != self.crl.next_update and not self.ignore_timestamps: if self.next_update != self.crl.next_update and not self.ignore_timestamps:
return False return False
if self.digest.name != self.crl.signature_hash_algorithm.name: if cryptography_key_needs_digest_for_signing(self.privatekey):
return False if self.crl.signature_hash_algorithm is None or self.digest.name != self.crl.signature_hash_algorithm.name:
return False
else:
if self.crl.signature_hash_algorithm is not None:
return False
want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer] want_issuer = [(cryptography_name_to_oid(entry[0]), entry[1]) for entry in self.issuer]
if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]: if want_issuer != [(sub.oid, sub.value) for sub in self.crl.issuer]:
@@ -664,9 +669,7 @@ class CRL(OpenSSLObject):
revoked_cert = revoked_cert.revocation_date(entry['revocation_date']) revoked_cert = revoked_cert.revocation_date(entry['revocation_date'])
if entry['issuer'] is not None: if entry['issuer'] is not None:
revoked_cert = revoked_cert.add_extension( revoked_cert = revoked_cert.add_extension(
x509.CertificateIssuer([ x509.CertificateIssuer(entry['issuer']),
cryptography_get_name(name, 'issuer') for name in entry['issuer']
]),
entry['issuer_critical'] entry['issuer_critical']
) )
if entry['reason'] is not None: if entry['reason'] is not None:
@@ -681,7 +684,10 @@ class CRL(OpenSSLObject):
) )
crl = crl.add_revoked_certificate(revoked_cert.build(backend)) crl = crl.add_revoked_certificate(revoked_cert.build(backend))
self.crl = crl.sign(self.privatekey, self.digest, backend=backend) digest = None
if cryptography_key_needs_digest_for_signing(self.privatekey):
digest = self.digest
self.crl = crl.sign(self.privatekey, digest, backend=backend)
if self.format == 'pem': if self.format == 'pem':
return self.crl.public_bytes(Encoding.PEM) return self.crl.public_bytes(Encoding.PEM)
else: else:

View File

@@ -78,23 +78,23 @@ issuer:
- Note that for repeated values, only the last one will be returned. - Note that for repeated values, only the last one will be returned.
returned: success returned: success
type: dict type: dict
sample: '{"organizationName": "Ansible", "commonName": "ca.example.com"}' sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
issuer_ordered: issuer_ordered:
description: The CRL's issuer as an ordered list of tuples. description: The CRL's issuer as an ordered list of tuples.
returned: success returned: success
type: list type: list
elements: list elements: list
sample: '[["organizationName", "Ansible"], ["commonName": "ca.example.com"]]' sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
last_update: last_update:
description: The point in time from which this CRL can be trusted as ASN.1 TIME. description: The point in time from which this CRL can be trusted as ASN.1 TIME.
returned: success returned: success
type: str type: str
sample: 20190413202428Z sample: '20190413202428Z'
next_update: next_update:
description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME. description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME.
returned: success returned: success
type: str type: str
sample: 20190413202428Z sample: '20190413202428Z'
digest: digest:
description: The signature algorithm used to sign the CRL. description: The signature algorithm used to sign the CRL.
returned: success returned: success
@@ -113,12 +113,12 @@ revoked_certificates:
revocation_date: revocation_date:
description: The point in time the certificate was revoked as ASN.1 TIME. description: The point in time the certificate was revoked as ASN.1 TIME.
type: str type: str
sample: 20190413202428Z sample: '20190413202428Z'
issuer: issuer:
description: The certificate's issuer. description: The certificate's issuer.
type: list type: list
elements: str elements: str
sample: '["DNS:ca.example.org"]' sample: ["DNS:ca.example.org"]
issuer_critical: issuer_critical:
description: Whether the certificate issuer extension is critical. description: Whether the certificate issuer extension is critical.
type: bool type: bool
@@ -140,7 +140,7 @@ revoked_certificates:
The point in time it was known/suspected that the private key was compromised The point in time it was known/suspected that the private key was compromised
or that the certificate otherwise became invalid as ASN.1 TIME. or that the certificate otherwise became invalid as ASN.1 TIME.
type: str type: str
sample: 20190413202428Z sample: '20190413202428Z'
invalidity_date_critical: invalidity_date_critical:
description: Whether the invalidity date extension is critical. description: Whether the invalidity date extension is critical.
type: bool type: bool

View File

@@ -115,13 +115,12 @@ class AnsibleActionModule(object):
self.required_by = required_by self.required_by = required_by
self._diff = self.__action_plugin._play_context.diff self._diff = self.__action_plugin._play_context.diff
self._verbosity = self.__action_plugin._display.verbosity self._verbosity = self.__action_plugin._display.verbosity
self._string_conversion_action = C.STRING_CONVERSION_ACTION
self.aliases = {} self.aliases = {}
self._legal_inputs = [] self._legal_inputs = []
self._options_context = list() self._options_context = list()
self.params = copy.deepcopy(action_plugin._task.args) self.params = copy.deepcopy(self.__action_plugin._task.args)
self.no_log_values = set() self.no_log_values = set()
if HAS_ARGSPEC_VALIDATOR: if HAS_ARGSPEC_VALIDATOR:
self._validator = ArgumentSpecValidator( self._validator = ArgumentSpecValidator(
@@ -444,7 +443,7 @@ class AnsibleActionModule(object):
} }
# Ignore, warn, or error when converting to a string. # Ignore, warn, or error when converting to a string.
allow_conversion = opts.get(self._string_conversion_action, True) allow_conversion = opts.get(C.STRING_CONVERSION_ACTION, True)
try: try:
return check_type_str(value, allow_conversion) return check_type_str(value, allow_conversion)
except TypeError: except TypeError:
@@ -459,10 +458,10 @@ class AnsibleActionModule(object):
from_msg = '{0}: {1!r}'.format(param, value) from_msg = '{0}: {1!r}'.format(param, value)
to_msg = '{0}: {1!r}'.format(param, to_text(value)) to_msg = '{0}: {1!r}'.format(param, to_text(value))
if self._string_conversion_action == 'error': if C.STRING_CONVERSION_ACTION == 'error':
msg = common_msg.capitalize() msg = common_msg.capitalize()
raise TypeError(to_native(msg)) raise TypeError(to_native(msg))
elif self._string_conversion_action == 'warn': elif C.STRING_CONVERSION_ACTION == 'warn':
msg = ('The value "{0}" (type {1.__class__.__name__}) was converted to "{2}" (type string). ' msg = ('The value "{0}" (type {1.__class__.__name__}) was converted to "{2}" (type string). '
'If this does not look like what you expect, {3}').format(from_msg, value, to_msg, common_msg) 'If this does not look like what you expect, {3}').format(from_msg, value, to_msg, common_msg)
self.warn(to_native(msg)) self.warn(to_native(msg))

8
simplified_bsd.txt Normal file
View File

@@ -0,0 +1,8 @@
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,3 +1,4 @@
dependencies: dependencies:
- prepare_jinja2_compat
- setup_openssl - setup_openssl
- setup_remote_tmp_dir - setup_remote_tmp_dir

View File

@@ -0,0 +1,20 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- name: Generate CSR for {{ certificate.name }}
openssl_csr:
path: '{{ remote_tmp_dir }}/{{ certificate.name }}.csr'
privatekey_path: '{{ remote_tmp_dir }}/{{ certificate.name }}.key'
subject: '{{ certificate.subject }}'
useCommonNameForSAN: false
- name: Generate certificate for {{ certificate.name }}
x509_certificate:
path: '{{ remote_tmp_dir }}/{{ certificate.name }}.pem'
csr_path: '{{ remote_tmp_dir }}/{{ certificate.name }}.csr'
privatekey_path: '{{ remote_tmp_dir }}/{{ certificate.name }}.key'
provider: '{{ "selfsigned" if certificate.parent is not defined else "ownca" }}'
ownca_path: '{{ (remote_tmp_dir ~ "/" ~ certificate.parent ~ ".pem") if certificate.parent is defined else omit }}'
ownca_privatekey_path: '{{ (remote_tmp_dir ~ "/" ~ certificate.parent ~ ".key") if certificate.parent is defined else omit }}'

View File

@@ -0,0 +1,49 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- block:
- name: Create private keys
openssl_privatekey:
path: '{{ remote_tmp_dir }}/{{ item.name }}.key'
size: '{{ default_rsa_key_size_certifiates }}'
loop: '{{ certificates }}'
- name: Generate certificates
include_tasks: create-single-certificate.yml
loop: '{{ certificates }}'
loop_control:
loop_var: certificate
- name: Read certificates
slurp:
src: '{{ remote_tmp_dir }}/{{ item.name }}.pem'
loop: '{{ certificates }}'
register: certificates_read
- name: Store read certificates
set_fact:
read_certificates: >-
{{ certificates_read.results | map(attribute='content') | map('b64decode')
| zip(certificates | map(attribute='name'))
| list
| items2dict(key_name=1, value_name=0) }}
vars:
certificates:
- name: a-root
subject:
commonName: root common name
- name: b-intermediate
subject:
commonName: intermediate common name
parent: a-root
- name: c-intermediate
subject:
commonName: intermediate common name
parent: a-root
- name: d-leaf
subject:
commonName: leaf certificate
parent: b-intermediate

View File

@@ -0,0 +1,44 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- name: Case A => works
certificate_complete_chain:
input_chain: "{{ read_certificates['d-leaf'] }}"
intermediate_certificates:
- '{{ remote_tmp_dir }}/b-intermediate.pem'
root_certificates:
- '{{ remote_tmp_dir }}/a-root.pem'
- name: Case B => doesn't work, but this is expected
failed_when: no
register: caseb
certificate_complete_chain:
input_chain: "{{ read_certificates['d-leaf'] }}"
intermediate_certificates:
- '{{ remote_tmp_dir }}/c-intermediate.pem'
root_certificates:
- '{{ remote_tmp_dir }}/a-root.pem'
- name: Assert that case B failed
assert:
that: "'Cannot complete chain' in caseb.msg"
- name: Case C => works
certificate_complete_chain:
input_chain: "{{ read_certificates['d-leaf'] }}"
intermediate_certificates:
- '{{ remote_tmp_dir }}/c-intermediate.pem'
- '{{ remote_tmp_dir }}/b-intermediate.pem'
root_certificates:
- '{{ remote_tmp_dir }}/a-root.pem'
- name: Case D => works as well after PR 403
certificate_complete_chain:
input_chain: "{{ read_certificates['d-leaf'] }}"
intermediate_certificates:
- '{{ remote_tmp_dir }}/b-intermediate.pem'
- '{{ remote_tmp_dir }}/c-intermediate.pem'
root_certificates:
- '{{ remote_tmp_dir }}/a-root.pem'

View File

@@ -0,0 +1,144 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- block:
- name: Find root for cert 1 using directory
certificate_complete_chain:
input_chain: '{{ fullchain | trim }}'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots/'
register: cert1_root
- name: Verify root for cert 1
assert:
that:
- cert1_root.complete_chain | join('') == (fullchain ~ root)
- cert1_root.root == root
vars:
fullchain: "{{ lookup('file', 'cert1-fullchain.pem', rstrip=False) }}"
root: "{{ lookup('file', 'cert1-root.pem', rstrip=False) }}"
- block:
- name: Find rootchain for cert 1 using intermediate and root PEM
certificate_complete_chain:
input_chain: '{{ cert }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert1_rootchain
- name: Verify rootchain for cert 1
assert:
that:
- cert1_rootchain.complete_chain | join('') == (cert ~ chain ~ root)
- cert1_rootchain.chain[:-1] | join('') == chain
- cert1_rootchain.root == root
vars:
cert: "{{ lookup('file', 'cert1.pem', rstrip=False) }}"
chain: "{{ lookup('file', 'cert1-chain.pem', rstrip=False) }}"
root: "{{ lookup('file', 'cert1-root.pem', rstrip=False) }}"
- block:
- name: Find root for cert 2 using directory
certificate_complete_chain:
input_chain: "{{ fullchain | trim }}"
root_certificates:
- '{{ remote_tmp_dir }}/files/roots/'
register: cert2_root
- name: Verify root for cert 2
assert:
that:
- cert2_root.complete_chain | join('') == (fullchain ~ root)
- cert2_root.root == root
vars:
fullchain: "{{ lookup('file', 'cert2-fullchain.pem', rstrip=False) }}"
root: "{{ lookup('file', 'cert2-root.pem', rstrip=False) }}"
- block:
- name: Find rootchain for cert 2 using intermediate and root PEM
certificate_complete_chain:
input_chain: '{{ cert }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert2-chain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert2_rootchain
- name: Verify rootchain for cert 2
assert:
that:
- cert2_rootchain.complete_chain | join('') == (cert ~ chain ~ root)
- cert2_rootchain.chain[:-1] | join('') == chain
- cert2_rootchain.root == root
vars:
cert: "{{ lookup('file', 'cert2.pem', rstrip=False) }}"
chain: "{{ lookup('file', 'cert2-chain.pem', rstrip=False) }}"
root: "{{ lookup('file', 'cert2-root.pem', rstrip=False) }}"
- block:
- name: Find alternate rootchain for cert 2 using intermediate and root PEM
certificate_complete_chain:
input_chain: '{{ cert }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert2-altchain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert2_rootchain_alt
- name: Verify rootchain for cert 2
assert:
that:
- cert2_rootchain_alt.complete_chain | join('') == (cert ~ chain ~ root)
- cert2_rootchain_alt.chain[:-1] | join('') == chain
- cert2_rootchain_alt.root == root
vars:
cert: "{{ lookup('file', 'cert2.pem', rstrip=False) }}"
chain: "{{ lookup('file', 'cert2-altchain.pem', rstrip=False) }}"
root: "{{ lookup('file', 'cert2-altroot.pem', rstrip=False) }}"
- block:
- name: Find alternate rootchain for cert 2 when complete chain is already presented to the module
certificate_complete_chain:
input_chain: '{{ cert ~ chain ~ root }}'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert2_complete_chain
- name: Verify rootchain for cert 2
assert:
that:
- cert2_complete_chain.complete_chain | join('') == (cert ~ chain ~ root)
- cert2_complete_chain.chain == []
- cert2_complete_chain.root == root
vars:
cert: "{{ lookup('file', 'cert2.pem', rstrip=False) }}"
chain: "{{ lookup('file', 'cert2-altchain.pem', rstrip=False) }}"
root: "{{ lookup('file', 'cert2-altroot.pem', rstrip=False) }}"
- name: Check failure when no intermediate certificate can be found
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert2.pem", rstrip=True) }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert2_no_intermediate
ignore_errors: true
- name: Verify failure
assert:
that:
- cert2_no_intermediate is failed
- "cert2_no_intermediate.msg.startswith('Cannot complete chain. Stuck at certificate ')"
- name: Check failure when infinite loop is found
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert2-fullchain.pem", rstrip=True) }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
register: cert2_infinite_loop
ignore_errors: true
- name: Verify failure
assert:
that:
- cert2_infinite_loop is failed
- "cert2_infinite_loop.msg == 'Found cycle while building certificate chain'"

View File

@@ -4,6 +4,7 @@
#################################################################### ####################################################################
- block: - block:
- name: Make sure testhost directory exists - name: Make sure testhost directory exists
file: file:
path: '{{ remote_tmp_dir }}/files/' path: '{{ remote_tmp_dir }}/files/'
@@ -13,68 +14,14 @@
copy: copy:
src: '{{ role_path }}/files/' src: '{{ role_path }}/files/'
dest: '{{ remote_tmp_dir }}/files/' dest: '{{ remote_tmp_dir }}/files/'
- name: Find root for cert 1
certificate_complete_chain: - name: Run tests with copied certificates
input_chain: '{{ lookup("file", "cert1-fullchain.pem", rstrip=False) }}' import_tasks: existing.yml
root_certificates:
- '{{ remote_tmp_dir }}/files/roots/' - name: Create more certificates
register: cert1_root import_tasks: create.yml
- name: Verify root for cert 1
assert: - name: Run tests with created certificates
that: import_tasks: created.yml
- cert1_root.complete_chain | join('') == (lookup('file', 'cert1.pem', rstrip=False) ~ lookup('file', 'cert1-chain.pem', rstrip=False) ~ lookup('file', 'cert1-root.pem', rstrip=False))
- cert1_root.root == lookup('file', 'cert1-root.pem', rstrip=False)
- name: Find rootchain for cert 1
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert1.pem", rstrip=False) }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert1-chain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert1_rootchain
- name: Verify rootchain for cert 1
assert:
that:
- cert1_rootchain.complete_chain | join('') == (lookup('file', 'cert1.pem', rstrip=False) ~ lookup('file', 'cert1-chain.pem', rstrip=False) ~ lookup('file', 'cert1-root.pem', rstrip=False))
- cert1_rootchain.chain[:-1] | join('') == lookup('file', 'cert1-chain.pem', rstrip=False)
- cert1_rootchain.root == lookup('file', 'cert1-root.pem', rstrip=False)
- name: Find root for cert 2
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert2-fullchain.pem", rstrip=False) }}'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots/'
register: cert2_root
- name: Verify root for cert 2
assert:
that:
- cert2_root.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-chain.pem', rstrip=False) ~ lookup('file', 'cert2-root.pem', rstrip=False))
- cert2_root.root == lookup('file', 'cert2-root.pem', rstrip=False)
- name: Find rootchain for cert 2
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert2.pem", rstrip=False) }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert2-chain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert2_rootchain
- name: Verify rootchain for cert 2
assert:
that:
- cert2_rootchain.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-chain.pem', rstrip=False) ~ lookup('file', 'cert2-root.pem', rstrip=False))
- cert2_rootchain.chain[:-1] | join('') == lookup('file', 'cert2-chain.pem', rstrip=False)
- cert2_rootchain.root == lookup('file', 'cert2-root.pem', rstrip=False)
- name: Find alternate rootchain for cert 2
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert2.pem", rstrip=True) }}'
intermediate_certificates:
- '{{ remote_tmp_dir }}/files/cert2-altchain.pem'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots.pem'
register: cert2_rootchain_alt
- name: Verify rootchain for cert 2
assert:
that:
- cert2_rootchain_alt.complete_chain | join('') == (lookup('file', 'cert2.pem', rstrip=False) ~ lookup('file', 'cert2-altchain.pem', rstrip=False) ~ lookup('file', 'cert2-altroot.pem', rstrip=False))
- cert2_rootchain_alt.chain[:-1] | join('') == lookup('file', 'cert2-altchain.pem', rstrip=False)
- cert2_rootchain_alt.root == lookup('file', 'cert2-altroot.pem', rstrip=False)
when: cryptography_version.stdout is version('1.5', '>=') when: cryptography_version.stdout is version('1.5', '>=')

View File

@@ -6,6 +6,7 @@
keyfile: "{{ remote_tmp_dir }}/keyfile1" keyfile: "{{ remote_tmp_dir }}/keyfile1"
keysize: 256 keysize: 256
pbkdf: pbkdf:
algorithm: pbkdf2
iteration_count: 1000 iteration_count: 1000
become: yes become: yes
register: create_with_keysize register: create_with_keysize
@@ -16,6 +17,7 @@
keyfile: "{{ remote_tmp_dir }}/keyfile1" keyfile: "{{ remote_tmp_dir }}/keyfile1"
keysize: 256 keysize: 256
pbkdf: pbkdf:
algorithm: pbkdf2
iteration_count: 1000 iteration_count: 1000
become: yes become: yes
register: create_idem_with_keysize register: create_idem_with_keysize
@@ -26,6 +28,7 @@
keyfile: "{{ remote_tmp_dir }}/keyfile1" keyfile: "{{ remote_tmp_dir }}/keyfile1"
keysize: 512 keysize: 512
pbkdf: pbkdf:
algorithm: pbkdf2
iteration_count: 1000 iteration_count: 1000
become: yes become: yes
register: create_idem_with_diff_keysize register: create_idem_with_diff_keysize
@@ -36,6 +39,7 @@
keyfile: "{{ remote_tmp_dir }}/keyfile1" keyfile: "{{ remote_tmp_dir }}/keyfile1"
passphrase: "{{ cryptfile_passphrase1 }}" passphrase: "{{ cryptfile_passphrase1 }}"
pbkdf: pbkdf:
algorithm: pbkdf2
iteration_count: 1000 iteration_count: 1000
ignore_errors: yes ignore_errors: yes
become: yes become: yes

View File

@@ -86,6 +86,27 @@
regenerate: full_idempotence regenerate: full_idempotence
register: default_options register: default_options
- name: Generate host cert full_idempotence
openssh_cert:
type: host
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
valid_from: always
valid_to: forever
regenerate: full_idempotence
- name: Generate host cert full_idempotence again
openssh_cert:
type: host
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
valid_from: always
valid_to: forever
regenerate: full_idempotence
register: host_cert_full_idempotence
- name: Assert options results - name: Assert options results
assert: assert:
that: that:
@@ -95,6 +116,7 @@
- explicit_extension_after is not changed - explicit_extension_after is not changed
- explicit_extension_and_directive is changed - explicit_extension_and_directive is changed
- default_options is not changed - default_options is not changed
- host_cert_full_idempotence is not changed
- name: Remove certificate - name: Remove certificate
openssh_cert: openssh_cert:

View File

@@ -8,7 +8,7 @@
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
register: result register: result
- name: "({{ select_crypto_backend }}) Check whether subject behaves as expected" - name: "({{ select_crypto_backend }}) Check whether subject and extensions behaves as expected"
assert: assert:
that: that:
- result.subject.organizationalUnitName == 'ACME Department' - result.subject.organizationalUnitName == 'ACME Department'
@@ -16,6 +16,21 @@
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered" - "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- result.public_key_type == 'RSA' - result.public_key_type == 'RSA'
- result.public_key_data.size == default_rsa_key_size - result.public_key_data.size == default_rsa_key_size
# TLS Feature
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].critical == false
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].value == 'MAMCAQU='
# Key Usage
- result.extensions_by_oid['2.5.29.15'].critical == true
- result.extensions_by_oid['2.5.29.15'].value in ['AwMA/4A=', 'AwMH/4A=']
# Subject Alternative Names
- result.extensions_by_oid['2.5.29.17'].critical == false
- result.extensions_by_oid['2.5.29.17'].value == 'MGCCD3d3dy5hbnNpYmxlLmNvbYcEAQIDBIcQAAAAAAAAAAAAAAAAAAAAAYEQdGVzdEBleGFtcGxlLm9yZ4YjaHR0cHM6Ly9leGFtcGxlLm9yZy90ZXN0L2luZGV4Lmh0bWw='
# Basic Constraints
- result.extensions_by_oid['2.5.29.19'].critical == true
- result.extensions_by_oid['2.5.29.19'].value == 'MAYBAf8CARc='
# Extended Key Usage
- result.extensions_by_oid['2.5.29.37'].critical == false
- result.extensions_by_oid['2.5.29.37'].value == 'MHQGCCsGAQUFBwMBBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUFBwMJBgRVHSUABggrBgEFBQcBAwYIKwYBBQUHAwoGCCsGAQUFBwMHBggrBgEFBQcBAg=='
- name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier" - name: "({{ select_crypto_backend }}) Check SubjectKeyIdentifier and AuthorityKeyIdentifier"
assert: assert:
@@ -24,6 +39,10 @@
- result.authority_key_identifier == "44:55:66:77" - result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer == expected_authority_cert_issuer - result.authority_cert_issuer == expected_authority_cert_issuer
- result.authority_cert_serial_number == 12345 - result.authority_cert_serial_number == 12345
# Subject Key Identifier
- result.extensions_by_oid['2.5.29.14'].critical == false
# Authority Key Identifier
- result.extensions_by_oid['2.5.29.35'].critical == false
vars: vars:
expected_authority_cert_issuer: expected_authority_cert_issuer:
- "DNS:ca.example.org" - "DNS:ca.example.org"

View File

@@ -45,6 +45,18 @@
return_content: true return_content: true
register: p12_standard_idempotency register: p12_standard_idempotency
- name: "({{ select_crypto_backend }}) Generate PKCS#12 file again, idempotency (empty other_certificates)"
openssl_pkcs12:
select_crypto_backend: '{{ select_crypto_backend }}'
path: '{{ remote_tmp_dir }}/ansible.p12'
friendly_name: abracadabra
privatekey_path: '{{ remote_tmp_dir }}/ansible_pkey1.pem'
certificate_path: '{{ remote_tmp_dir }}/ansible1.crt'
state: present
return_content: true
other_certificates: []
register: p12_standard_idempotency_no_certs
- name: "({{ select_crypto_backend }}) Read ansible.p12" - name: "({{ select_crypto_backend }}) Read ansible.p12"
slurp: slurp:
src: '{{ remote_tmp_dir }}/ansible.p12' src: '{{ remote_tmp_dir }}/ansible.p12'

View File

@@ -25,6 +25,7 @@
- p12_dumped is changed - p12_dumped is changed
- p12_standard_idempotency is not changed - p12_standard_idempotency is not changed
- p12_standard_idempotency_check is not changed - p12_standard_idempotency_check is not changed
- p12_standard_idempotency_no_certs is not changed
- p12_multiple_certs_idempotency is not changed - p12_multiple_certs_idempotency is not changed
- p12_dumped_idempotency is not changed - p12_dumped_idempotency is not changed
- p12_dumped_check_mode is not changed - p12_dumped_check_mode is not changed
@@ -83,4 +84,4 @@
- p12_empty is changed - p12_empty is changed
- p12_empty_idem is not changed - p12_empty_idem is not changed
- p12_empty_concat_idem is not changed - p12_empty_concat_idem is not changed
- empty_contents == (empty_expected_pyopenssl if select_crypto_backend == 'pyopenssl' else empty_expected_cryptography) - (empty_contents == empty_expected_cryptography) or (empty_contents == empty_expected_pyopenssl and select_crypto_backend == 'pyopenssl')

View File

@@ -303,11 +303,18 @@
size: '{{ default_rsa_key_size }}' size: '{{ default_rsa_key_size }}'
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
register: privatekey_mode_1 register: privatekey_mode_1
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode" - name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
stat: stat:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem' path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
register: privatekey_mode_1_stat register: privatekey_mode_1_stat
- name: "({{ select_crypto_backend }}) Collect file information"
community.internal_test_tools.files_collect:
files:
- path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
register: privatekey_mode_1_fileinfo
- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, idempotency)" - name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, idempotency)"
openssl_privatekey: openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem' path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
@@ -316,13 +323,6 @@
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
register: privatekey_mode_2 register: privatekey_mode_2
- name: Make sure that mtime actually changes.
# The "privatekey_mode_1_stat.stat.mtime != privatekey_mode_3_stat.stat.mtime" test should be
# changed to compare content instead of mtime. On macOS 10.15, mtime resolution is one second,
# and the machine (VM) is fast enough that both modifications can happen in the same second.
pause:
seconds: 1
- name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, force)" - name: "({{ select_crypto_backend }}) Generate privatekey_mode (mode 0400, force)"
openssl_privatekey: openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem' path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
@@ -331,11 +331,17 @@
size: '{{ default_rsa_key_size }}' size: '{{ default_rsa_key_size }}'
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
register: privatekey_mode_3 register: privatekey_mode_3
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode" - name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
stat: stat:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem' path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
register: privatekey_mode_3_stat register: privatekey_mode_3_stat
- name: "({{ select_crypto_backend }}) Make sure that file changed"
community.internal_test_tools.files_diff:
state: '{{ privatekey_mode_1_fileinfo }}'
register: privatekey_mode_3_file_change
- block: - block:
- name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format" - name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format"
openssl_privatekey: openssl_privatekey:

View File

@@ -186,7 +186,7 @@
- privatekey_mode_2 is not changed - privatekey_mode_2 is not changed
- privatekey_mode_3 is changed - privatekey_mode_3 is changed
- privatekey_mode_3_stat.stat.mode == '0400' - privatekey_mode_3_stat.stat.mode == '0400'
- privatekey_mode_1_stat.stat.mtime != privatekey_mode_3_stat.stat.mtime - privatekey_mode_3_file_change is changed
- name: "({{ select_crypto_backend }}) Validate format 1" - name: "({{ select_crypto_backend }}) Validate format 1"
assert: assert:

View File

@@ -32,11 +32,15 @@ system_python_version_data:
- '3.8' - '3.8'
'11.1': '11.1':
- '3.9' - '3.9'
'12.0':
- '3.10'
FreeBSD: FreeBSD:
'12.1': '12.1':
- '3.6' - '3.6'
'12.2': '12.2':
- '3.7' - '3.7'
'12.3':
- '3.8'
'13.0': '13.0':
- '3.7' - '3.7'
RedHat: RedHat:
@@ -55,3 +59,6 @@ cannot_upgrade_cryptography:
- '3.8' # on the VMs in CI, system packages are used for this version as well - '3.8' # on the VMs in CI, system packages are used for this version as well
'13.0': '13.0':
- '3.8' # on the VMs in CI, system packages are used for this version as well - '3.8' # on the VMs in CI, system packages are used for this version as well
Ubuntu:
'18':
- '3.9' # this is the default container for ansible-core 2.12; upgrading cryptography wrecks pyOpenSSL

View File

@@ -1,7 +1,18 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: create ~/tmp
file:
path: '~/tmp'
state: directory
- name: create temporary directory - name: create temporary directory
tempfile: tempfile:
state: directory state: directory
suffix: .test suffix: .test
path: '~/tmp'
register: remote_tmp_dir register: remote_tmp_dir
notify: notify:
- delete temporary directory - delete temporary directory

View File

@@ -1,2 +1,3 @@
dependencies: dependencies:
- setup_ssh_keygen - setup_ssh_keygen
- prepare_jinja2_compat

View File

@@ -5,13 +5,22 @@
#################################################################### ####################################################################
- name: Start an ssh agent to use for tests - name: Start an ssh agent to use for tests
shell: eval $(ssh-agent)>/dev/null&&echo "${SSH_AGENT_PID};${SSH_AUTH_SOCK}" shell: ssh-agent -c | grep "^setenv"
register: openssh_agent_env_vars register: openssh_agent_stdout
- name: Convert output to dictionary
set_fact:
openssh_agent_env: >-
{{
openssh_agent_stdout.stdout_lines | map('regex_replace', '^setenv ([^ ]+) ([^ ]+);', '\1')
| zip(openssh_agent_stdout.stdout_lines | map('regex_replace', '^setenv ([^ ]+) ([^ ]+);', '\2'))
| list | items2dict(key_name=0, value_name=1)
}}
- name: Register ssh agent facts - name: Register ssh agent facts
set_fact: set_fact:
openssh_agent_pid: "{{ openssh_agent_env_vars.stdout.split(';')[0] }}" openssh_agent_pid: "{{ openssh_agent_env.SSH_AGENT_PID }}"
openssh_agent_sock: "{{ openssh_agent_env_vars.stdout.split(';')[1] }}" openssh_agent_sock: "{{ openssh_agent_env.SSH_AUTH_SOCK }}"
- name: stat agent socket - name: stat agent socket
stat: stat:

View File

@@ -14,14 +14,20 @@
- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR - name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR
openssl_csr: openssl_csr:
path: '{{ remote_tmp_dir }}/ca_csr.csr' path: '{{ item.path }}'
privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem' privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
subject: subject: '{{ item.subject }}'
commonName: Example CA
useCommonNameForSAN: no useCommonNameForSAN: no
basic_constraints: basic_constraints:
- 'CA:TRUE' - 'CA:TRUE'
basic_constraints_critical: yes basic_constraints_critical: yes
loop:
- path: '{{ remote_tmp_dir }}/ca_csr.csr'
subject:
commonName: Example CA
- path: '{{ remote_tmp_dir }}/ca_csr2.csr'
subject:
commonName: Example CA 2
- name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR (privatekey passphrase) - name: (OwnCA, {{select_crypto_backend}}) Generate CA CSR (privatekey passphrase)
openssl_csr: openssl_csr:
@@ -62,6 +68,15 @@
- result_check_mode is changed - result_check_mode is changed
- result is changed - result is changed
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate with different commonName
x509_certificate:
path: '{{ remote_tmp_dir }}/ca_cert2.pem'
csr_path: '{{ remote_tmp_dir }}/ca_csr2.csr'
privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: selfsigned
selfsigned_digest: sha256
select_crypto_backend: '{{ select_crypto_backend }}'
- name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase) - name: (OwnCA, {{select_crypto_backend}}) Generate selfsigned CA certificate (privatekey passphrase)
x509_certificate: x509_certificate:
path: '{{ remote_tmp_dir }}/ca_cert_pw.pem' path: '{{ remote_tmp_dir }}/ca_cert_pw.pem'
@@ -110,6 +125,54 @@
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
check_mode: yes check_mode: yes
- name: (OwnCA, {{select_crypto_backend}}) Copy ownca certificate to new file to check regeneration
copy:
src: '{{ remote_tmp_dir }}/ownca_cert.pem'
dest: '{{ item }}'
remote_src: true
loop:
- '{{ remote_tmp_dir }}/ownca_cert_ca_cn.pem'
- '{{ remote_tmp_dir }}/ownca_cert_ca_key.pem'
- name: (OwnCA, {{select_crypto_backend}}) Regenerate ownca certificate with different CA subject
x509_certificate:
path: '{{ remote_tmp_dir }}/ownca_cert_ca_cn.pem'
csr_path: '{{ remote_tmp_dir }}/csr.csr'
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
ownca_path: '{{ remote_tmp_dir }}/ca_cert2.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca
ownca_digest: sha256
select_crypto_backend: '{{ select_crypto_backend }}'
return_content: yes
register: ownca_certificate_ca_subject_changed
- name: (OwnCA, {{select_crypto_backend}}) Regenerate ownca certificate with different CA key
x509_certificate:
path: '{{ remote_tmp_dir }}/ownca_cert_ca_key.pem'
csr_path: '{{ remote_tmp_dir }}/csr.csr'
privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem'
ownca_path: '{{ remote_tmp_dir }}/ca_cert_pw.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey_pw.pem'
ownca_privatekey_passphrase: hunter2
provider: ownca
ownca_digest: sha256
select_crypto_backend: '{{ select_crypto_backend }}'
return_content: yes
register: ownca_certificate_ca_key_changed
- name: (OwnCA, {{select_crypto_backend}}) Get certificate information
community.crypto.x509_certificate_info:
path: '{{ remote_tmp_dir }}/ownca_cert.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result
- name: (OwnCA, {{select_crypto_backend}}) Get private key information
community.crypto.openssl_privatekey_info:
path: '{{ remote_tmp_dir }}/privatekey.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
register: result_privatekey
- name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate - name: (OwnCA, {{select_crypto_backend}}) Check ownca certificate
x509_certificate: x509_certificate:
path: '{{ remote_tmp_dir }}/ownca_cert.pem' path: '{{ remote_tmp_dir }}/ownca_cert.pem'
@@ -285,7 +348,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem' path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
backup: yes backup: yes
@@ -296,7 +359,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem' path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
backup: yes backup: yes
@@ -307,7 +370,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem' path: '{{ remote_tmp_dir }}/ownca_cert_backup.pem'
csr_path: '{{ remote_tmp_dir }}/csr.csr' csr_path: '{{ remote_tmp_dir }}/csr.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
backup: yes backup: yes
@@ -335,7 +398,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem' path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_subject_key_identifier: always_create ownca_create_subject_key_identifier: always_create
@@ -348,7 +411,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem' path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_subject_key_identifier: always_create ownca_create_subject_key_identifier: always_create
@@ -361,7 +424,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem' path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_subject_key_identifier: never_create ownca_create_subject_key_identifier: never_create
@@ -374,7 +437,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem' path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_subject_key_identifier: never_create ownca_create_subject_key_identifier: never_create
@@ -387,7 +450,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem' path: '{{ remote_tmp_dir }}/ownca_cert_ski.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_subject_key_identifier: always_create ownca_create_subject_key_identifier: always_create
@@ -400,7 +463,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem' path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_authority_key_identifier: yes ownca_create_authority_key_identifier: yes
@@ -413,7 +476,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem' path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_authority_key_identifier: yes ownca_create_authority_key_identifier: yes
@@ -426,7 +489,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem' path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_authority_key_identifier: no ownca_create_authority_key_identifier: no
@@ -439,7 +502,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem' path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_authority_key_identifier: no ownca_create_authority_key_identifier: no
@@ -452,7 +515,7 @@
path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem' path: '{{ remote_tmp_dir }}/ownca_cert_aki.pem'
csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr' csr_path: '{{ remote_tmp_dir }}/csr_ecc.csr'
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem' ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/privatekey.pem' ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
provider: ownca provider: ownca
ownca_digest: sha256 ownca_digest: sha256
ownca_create_authority_key_identifier: yes ownca_create_authority_key_identifier: yes

View File

@@ -31,6 +31,14 @@
- ownca_certificate.notBefore == ownca_certificate_idempotence.notBefore - ownca_certificate.notBefore == ownca_certificate_idempotence.notBefore
- ownca_certificate.notAfter == ownca_certificate_idempotence.notAfter - ownca_certificate.notAfter == ownca_certificate_idempotence.notAfter
- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca certificate regeneration
assert:
that:
- ownca_certificate_ca_subject_changed is changed
# ownca_certificate_ca_key_changed is not changed for the pyopenssl backend,
# see https://github.com/ansible-collections/community.crypto/pull/406
- ownca_certificate_ca_key_changed is changed or select_crypto_backend == 'pyopenssl'
- name: (OwnCA validation, {{select_crypto_backend}}) Read certificate - name: (OwnCA validation, {{select_crypto_backend}}) Read certificate
slurp: slurp:
src: '{{ remote_tmp_dir }}/ownca_cert.pem' src: '{{ remote_tmp_dir }}/ownca_cert.pem'

View File

@@ -8,7 +8,7 @@
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
register: result register: result
- name: Check whether issuer and subject behave as expected - name: Check whether issuer and subject and extensions behave as expected
assert: assert:
that: that:
- result.issuer.organizationalUnitName == 'ACME Department' - result.issuer.organizationalUnitName == 'ACME Department'
@@ -19,6 +19,28 @@
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered" - "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- result.public_key_type == 'RSA' - result.public_key_type == 'RSA'
- result.public_key_data.size == (default_rsa_key_size_certifiates | int) - result.public_key_data.size == (default_rsa_key_size_certifiates | int)
- "result.subject_alt_name == [
'DNS:www.ansible.com',
'IP:1.2.3.4',
'IP:::1',
'email:test@example.org',
'URI:https://example.org/test/index.html'
]"
# TLS Feature
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].critical == false
- result.extensions_by_oid['1.3.6.1.5.5.7.1.24'].value == 'MAMCAQU='
# Key Usage
- result.extensions_by_oid['2.5.29.15'].critical == true
- result.extensions_by_oid['2.5.29.15'].value in ['AwMA/4A=', 'AwMH/4A=']
# Subject Alternative Names
- result.extensions_by_oid['2.5.29.17'].critical == false
- result.extensions_by_oid['2.5.29.17'].value == 'MGCCD3d3dy5hbnNpYmxlLmNvbYcEAQIDBIcQAAAAAAAAAAAAAAAAAAAAAYEQdGVzdEBleGFtcGxlLm9yZ4YjaHR0cHM6Ly9leGFtcGxlLm9yZy90ZXN0L2luZGV4Lmh0bWw='
# Basic Constraints
- result.extensions_by_oid['2.5.29.19'].critical == true
- result.extensions_by_oid['2.5.29.19'].value == 'MAYBAf8CARc='
# Extended Key Usage
- result.extensions_by_oid['2.5.29.37'].critical == false
- result.extensions_by_oid['2.5.29.37'].value == 'MHQGCCsGAQUFBwMBBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUFBwMDBggrBgEFBQcDBAYIKwYBBQUHAwgGCCsGAQUFBwMJBgRVHSUABggrBgEFBQcBAwYIKwYBBQUHAwoGCCsGAQUFBwMHBggrBgEFBQcBAg=='
- name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier - name: Check SubjectKeyIdentifier and AuthorityKeyIdentifier
assert: assert:
@@ -27,6 +49,10 @@
- result.authority_key_identifier == "44:55:66:77" - result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer == expected_authority_cert_issuer - result.authority_cert_issuer == expected_authority_cert_issuer
- result.authority_cert_serial_number == 12345 - result.authority_cert_serial_number == 12345
# Subject Key Identifier
- result.extensions_by_oid['2.5.29.14'].critical == false
# Authority Key Identifier
- result.extensions_by_oid['2.5.29.35'].critical == false
vars: vars:
expected_authority_cert_issuer: expected_authority_cert_issuer:
- "DNS:ca.example.org" - "DNS:ca.example.org"
@@ -122,10 +148,39 @@
path: '{{ remote_tmp_dir }}/packed-cert-1.pem' path: '{{ remote_tmp_dir }}/packed-cert-1.pem'
select_crypto_backend: '{{ select_crypto_backend }}' select_crypto_backend: '{{ select_crypto_backend }}'
register: result register: result
- assert: - name: Check extensions
assert:
that: that:
- "'ocsp_uri' in result" - "'ocsp_uri' in result"
- "result.ocsp_uri == 'http://ocsp.int-x3.letsencrypt.org'" - "result.ocsp_uri == 'http://ocsp.int-x3.letsencrypt.org'"
- result.extensions_by_oid | length == 9
# Precert Signed Certificate Timestamps
- result.extensions_by_oid['1.3.6.1.4.1.11129.2.4.2'].critical == false
- result.extensions_by_oid['1.3.6.1.4.1.11129.2.4.2'].value == 'BIHyAPAAdgDBFkrgp3LS1DktyArBB3DU8MSb3pkaSEDB+gdRZPYzYAAAAWTdAoU6AAAEAwBHMEUCIG5WpfKF536KKa9fnVlYbwcfrKh09Hi2MSRwU2kad49UAiEA4RUKjJOgw11IHFNdit+sy1RcCU3QCSOEQYrJ1/oPltAAdgApPFGWVMg5ZbqqUPxYB9S3b79Yeily3KTDDPTlRUf0eAAAAWTdAoc+AAAEAwBHMEUCIQCJjo75K4rVDSiWQe3XFLY6MiG3zcHQrKb0YhM17r1UKAIgGa8qMoN03DLp+Rm9nRJ9XLbTJz1vbuu9PyXUY741P8E='
# Authority Information Access
- result.extensions_by_oid['1.3.6.1.5.5.7.1.1'].critical == false
- result.extensions_by_oid['1.3.6.1.5.5.7.1.1'].value == 'MGEwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYIKwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcv'
# Subject Key Identifier
- result.extensions_by_oid['2.5.29.14'].critical == false
- result.extensions_by_oid['2.5.29.14'].value == 'BBRtcOI/yg62Ehbu5vQzxMUUdBOYMw=='
# Key Usage (The certificate has 'AwIFoA==', while de-serializing and re-serializing yields 'AwIAoA=='!)
- result.extensions_by_oid['2.5.29.15'].critical == true
- result.extensions_by_oid['2.5.29.15'].value in ['AwIFoA==', 'AwIAoA==']
# Subject Alternative Names
- result.extensions_by_oid['2.5.29.17'].critical == false
- result.extensions_by_oid['2.5.29.17'].value == 'MIIB5IIbY2VydC5pbnQteDEubGV0c2VuY3J5cHQub3JnghtjZXJ0LmludC14Mi5sZXRzZW5jcnlwdC5vcmeCG2NlcnQuaW50LXgzLmxldHNlbmNyeXB0Lm9yZ4IbY2VydC5pbnQteDQubGV0c2VuY3J5cHQub3JnghxjZXJ0LnJvb3QteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0YWdpbmcteDEubGV0c2VuY3J5cHQub3Jngh9jZXJ0LnN0Zy1pbnQteDEubGV0c2VuY3J5cHQub3JngiBjZXJ0LnN0Zy1yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ISY3AubGV0c2VuY3J5cHQub3JnghpjcC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZ4ITY3BzLmxldHNlbmNyeXB0Lm9yZ4IbY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3Jnghtjcmwucm9vdC14MS5sZXRzZW5jcnlwdC5vcmeCD2xldHNlbmNyeXB0Lm9yZ4IWb3JpZ2luLmxldHNlbmNyeXB0Lm9yZ4IXb3JpZ2luMi5sZXRzZW5jcnlwdC5vcmeCFnN0YXR1cy5sZXRzZW5jcnlwdC5vcmeCE3d3dy5sZXRzZW5jcnlwdC5vcmc='
# Basic Constraints
- result.extensions_by_oid['2.5.29.19'].critical == true
- result.extensions_by_oid['2.5.29.19'].value == 'MAA='
# Certificate Policies
- result.extensions_by_oid['2.5.29.32'].critical == false
- result.extensions_by_oid['2.5.29.32'].value == 'MIHzMAgGBmeBDAECATCB5gYLKwYBBAGC3xMBAQEwgdYwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3JnMIGrBggrBgEFBQcCAjCBngyBm1RoaXMgQ2VydGlmaWNhdGUgbWF5IG9ubHkgYmUgcmVsaWVkIHVwb24gYnkgUmVseWluZyBQYXJ0aWVzIGFuZCBvbmx5IGluIGFjY29yZGFuY2Ugd2l0aCB0aGUgQ2VydGlmaWNhdGUgUG9saWN5IGZvdW5kIGF0IGh0dHBzOi8vbGV0c2VuY3J5cHQub3JnL3JlcG9zaXRvcnkv'
# Authority Key Identifier
- result.extensions_by_oid['2.5.29.35'].critical == false
- result.extensions_by_oid['2.5.29.35'].value == 'MBaAFKhKamMEfd265tE5t6ZFZe/zqOyh'
# Extended Key Usage
- result.extensions_by_oid['2.5.29.37'].critical == false
- result.extensions_by_oid['2.5.29.37'].value == 'MBQGCCsGAQUFBwMBBggrBgEFBQcDAg=='
- name: Check fingerprints - name: Check fingerprints
assert: assert:
that: that:

View File

@@ -456,3 +456,90 @@
path: '{{ remote_tmp_dir }}/ca-crl2.crl' path: '{{ remote_tmp_dir }}/ca-crl2.crl'
list_revoked_certificates: false list_revoked_certificates: false
register: crl_2_info_1 register: crl_2_info_1
- name: Create CRL 3
x509_crl:
path: '{{ remote_tmp_dir }}/ca-crl3.crl'
privatekey_path: '{{ remote_tmp_dir }}/ca.key'
issuer:
CN: Ansible
last_update: +0d
next_update: +0d
revoked_certificates:
- serial_number: 1234
revocation_date: 20191001000000Z
issuer:
- "DNS:ca.example.org"
issuer_critical: true
register: crl_3
- name: Retrieve CRL 3 infos
x509_crl_info:
path: '{{ remote_tmp_dir }}/ca-crl3.crl'
list_revoked_certificates: true
register: crl_3_info
- name: Ed25519 and Ed448 tests (for cryptography >= 2.6)
block:
- name: Generate private keys
openssl_privatekey:
path: '{{ remote_tmp_dir }}/ca-{{ item }}.key'
type: '{{ item }}'
loop:
- Ed25519
- Ed448
register: ed25519_ed448_privatekey
ignore_errors: yes
- when: ed25519_ed448_privatekey is not failed
block:
- name: Create CRL
x509_crl:
path: '{{ remote_tmp_dir }}/ca-crl-{{ item }}.crl'
privatekey_path: '{{ remote_tmp_dir }}/ca-{{ item }}.key'
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- path: '{{ remote_tmp_dir }}/cert-1.pem'
revocation_date: 20191013000000Z
- path: '{{ remote_tmp_dir }}/cert-2.pem'
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
register: ed25519_ed448_crl
loop:
- Ed25519
- Ed448
ignore_errors: yes
- name: Create CRL (idempotence)
x509_crl:
path: '{{ remote_tmp_dir }}/ca-crl-{{ item }}.crl'
privatekey_path: '{{ remote_tmp_dir }}/ca-{{ item }}.key'
issuer:
CN: Ansible
last_update: 20191013000000Z
next_update: 20191113000000Z
revoked_certificates:
- path: '{{ remote_tmp_dir }}/cert-1.pem'
revocation_date: 20191013000000Z
- path: '{{ remote_tmp_dir }}/cert-2.pem'
revocation_date: 20191013000000Z
reason: key_compromise
reason_critical: yes
invalidity_date: 20191012000000Z
- serial_number: 1234
revocation_date: 20191001000000Z
register: ed25519_ed448_crl_idempotence
loop:
- Ed25519
- Ed448
ignore_errors: yes
when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=')

View File

@@ -90,3 +90,31 @@
assert: assert:
that: that:
- "'revoked_certificates' not in crl_2_info_1" - "'revoked_certificates' not in crl_2_info_1"
- name: Validate CRL 3 info
assert:
that:
- crl_3.revoked_certificates == crl_3_info.revoked_certificates
- crl_3.revoked_certificates[0].issuer == [
"DNS:ca.example.org",
]
- name: Verify Ed25519 and Ed448 tests (for cryptography >= 2.6, < 2.8)
assert:
that:
- ed25519_ed448_crl.results[0] is failed
- ed25519_ed448_crl.results[1] is failed
- ed25519_ed448_crl_idempotence.results[0] is failed
- ed25519_ed448_crl_idempotence.results[1] is failed
when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.6', '>=') and cryptography_version.stdout is version('2.8', '<') and ed25519_ed448_privatekey is not failed
- name: Verify Ed25519 and Ed448 tests (for cryptography >= 2.8)
assert:
that:
- ed25519_ed448_crl is succeeded
- ed25519_ed448_crl.results[0] is changed
- ed25519_ed448_crl.results[1] is changed
- ed25519_ed448_crl_idempotence is succeeded
- ed25519_ed448_crl_idempotence.results[0] is not changed
- ed25519_ed448_crl_idempotence.results[1] is not changed
when: select_crypto_backend == 'cryptography' and cryptography_version.stdout is version('2.8', '>=') and ed25519_ed448_privatekey is not failed

View File

@@ -1,3 +1,4 @@
integration_tests_dependencies: integration_tests_dependencies:
- community.general - community.general
- community.internal_test_tools
unit_tests_dependencies: [] unit_tests_dependencies: []

View File

@@ -5,6 +5,6 @@
], ],
"output": "path-line-column-message", "output": "path-line-column-message",
"requirements": [ "requirements": [
"antsibull" "antsibull-docs"
] ]
} }

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
# Copyright (c) Ansible Project # Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""Check extra collection docs with antsibull-lint.""" """Check extra collection docs with antsibull-docs."""
from __future__ import (absolute_import, division, print_function) from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
@@ -14,7 +14,7 @@ def main():
"""Main entry point.""" """Main entry point."""
if not os.path.isdir(os.path.join('docs', 'docsite')): if not os.path.isdir(os.path.join('docs', 'docsite')):
return return
p = subprocess.run(['antsibull-lint', 'collection-docs', '.'], check=False) p = subprocess.run(['antsibull-docs', 'lint-collection-docs', '.'], check=False)
if p.returncode not in (0, 3): if p.returncode not in (0, 3):
print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode)) print('{0}:0:0: unexpected return code {1}'.format(sys.argv[0], p.returncode))

View File

@@ -1,3 +1,9 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
plugins/module_utils/acme/__init__.py empty-init plugins/module_utils/acme/__init__.py empty-init
plugins/module_utils/compat/ipaddress.py future-import-boilerplate plugins/module_utils/compat/ipaddress.py future-import-boilerplate
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate plugins/module_utils/compat/ipaddress.py metaclass-boilerplate

View File

@@ -1,3 +1,9 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
plugins/module_utils/acme/__init__.py empty-init plugins/module_utils/acme/__init__.py empty-init
plugins/module_utils/compat/ipaddress.py future-import-boilerplate plugins/module_utils/compat/ipaddress.py future-import-boilerplate
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate plugins/module_utils/compat/ipaddress.py metaclass-boilerplate

View File

@@ -1,3 +1,4 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/module_utils/acme/__init__.py empty-init plugins/module_utils/acme/__init__.py empty-init
plugins/module_utils/compat/ipaddress.py future-import-boilerplate plugins/module_utils/compat/ipaddress.py future-import-boilerplate
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate plugins/module_utils/compat/ipaddress.py metaclass-boilerplate

View File

@@ -1,7 +1,7 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
plugins/module_utils/acme/__init__.py empty-init plugins/module_utils/acme/__init__.py empty-init
plugins/module_utils/compat/ipaddress.py future-import-boilerplate plugins/module_utils/compat/ipaddress.py future-import-boilerplate
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate plugins/module_utils/compat/ipaddress.py metaclass-boilerplate
plugins/module_utils/compat/ipaddress.py no-assert plugins/module_utils/compat/ipaddress.py no-assert
plugins/module_utils/compat/ipaddress.py no-unicode-literals plugins/module_utils/compat/ipaddress.py no-unicode-literals
plugins/module_utils/crypto/__init__.py empty-init plugins/module_utils/crypto/__init__.py empty-init
plugins/modules/acme_account_info.py validate-modules:return-syntax-error

View File

@@ -1,3 +1,9 @@
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
.azure-pipelines/scripts/publish-codecov.py compile-2.6!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py compile-2.7!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
plugins/module_utils/acme/__init__.py empty-init plugins/module_utils/acme/__init__.py empty-init
plugins/module_utils/compat/ipaddress.py future-import-boilerplate plugins/module_utils/compat/ipaddress.py future-import-boilerplate
plugins/module_utils/compat/ipaddress.py metaclass-boilerplate plugins/module_utils/compat/ipaddress.py metaclass-boilerplate

View File

@@ -1,4 +1,4 @@
-----BEGIN NEW CERTIFICATE REQUEST----- -----BEGIN CERTIFICATE REQUEST-----
MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs
4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE 4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE
@@ -6,4 +6,4 @@ MCMGA1UdEQQcMBqCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZzAMBgNVHRMBAf8E
AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl
FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr
URnCJfTLr2T3 URnCJfTLr2T3
-----END NEW CERTIFICATE REQUEST----- -----END CERTIFICATE REQUEST-----

View File

@@ -0,0 +1,12 @@
cryptography 35.0.0 does not support the 'NEW' in there; so to fix tests we removed it.
Once cryptography is fixed we should revert to the old version.
-----BEGIN NEW CERTIFICATE REQUEST-----
MIIBJTCBzQIBADAWMRQwEgYDVQQDEwthbnNpYmxlLmNvbTBZMBMGByqGSM49AgEG
CCqGSM49AwEHA0IABACc9MgAFwMBJjoU0ZI18cIHnW1juoKG2DN5VrM60uvBvEEs
4V0egJkNyM2Q4pp001zu14VcpQ0/Ei8xOOPxKZugVTBTBgkqhkiG9w0BCQ4xRjBE
MCMGA1UdEQQcMBqCC2V4YW1wbGUuY29tggtleGFtcGxlLm9yZzAMBgNVHRMBAf8E
AjAAMA8GA1UdDwEB/wQFAwMHgAAwCgYIKoZIzj0EAwIDRwAwRAIgcDyoRmwFVBDl
FvbFZtiSd5wmJU1ltM6JtcfnLWnjY54CICruOByrropFUkOKKb4xXOYsgaDT93Wr
URnCJfTLr2T3
-----END NEW CERTIFICATE REQUEST-----

View File

@@ -115,12 +115,14 @@ def test_format_error_problem(problem, subproblem_prefix, result):
def create_regular_response(response_text): def create_regular_response(response_text):
response = MagicMock() response = MagicMock()
response.read = MagicMock(return_value=response_text.encode('utf-8')) response.read = MagicMock(return_value=response_text.encode('utf-8'))
response.closed = False
return response return response
def create_error_response(): def create_error_response():
response = MagicMock() response = MagicMock()
response.read = MagicMock(side_effect=AttributeError('read')) response.read = MagicMock(side_effect=AttributeError('read'))
response.closed = True
return response return response

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