Compare commits

...

75 Commits

Author SHA1 Message Date
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
Felix Fontein
d784e0a52b Release 1.9.4. 2021-09-28 17:17:41 +02:00
Felix Fontein
d73a2942a2 Prepare 1.9.4 release. 2021-09-28 16:53:56 +02:00
Felix Fontein
8af4847373 Update CI matrix to include ansible-core's stable-2.12 branch (#286)
* Update CI matrix to include ansible-core's stable-2.12 branch.

* Adjust README.

* Fix stage names.
2021-09-28 15:35:26 +02:00
Felix Fontein
44f7367e21 Extend CI (#283)
* Run all tests on all targets. Remove hack in setup_acme.

* Fix some failing tests.

* OpenSSH tests do not work yet with default image on Ansible 2.9. Let's skip them on the cloud target.

* Make tests pass again.

* Make sure to install *latest* versions of cryptography and pyOpenSSL when not installing system packages, whenever possible.

ci_complete

* Update/fix aliases files.
2021-09-25 17:21:06 +02:00
Felix Fontein
0733b0d521 Prepare ansible-core devel branch version bump that is planned for later today. 2021-09-24 18:45:50 +02:00
Ajpantuso
771a9eebcf Initial commit (#285) 2021-09-24 06:59:52 +02:00
Felix Fontein
0fdede5d7a Fix CI (1/2) (#284)
* New default docker image no longer contains bcrypt.

* Install cryptography for ACME tests.

* Add constraints.
2021-09-23 21:56:03 +02:00
Felix Fontein
56b2130c6e openssl_privatekey_pipe is an action plugin. (#267) 2021-09-21 07:29:26 +02:00
Felix Fontein
6c018b94da Improve CI (#281)
* Install PyOpenSSL and cryptography from PyPi if target Python != system Python.

* Work around some CentOS6, 7, Ubuntu 16.04 problems. Improve jinja2 compatibility handling.

* Skip tasks that require properties that aren't always there.

* Only install OpenSSL when not present.

* Improve output.

* Improve get_certificate integration test graceful failing.

* Fix tests.

* Fix assert.

* OpenSSL peculiarities.

* Fix condition.
2021-09-18 15:21:40 +02:00
Felix Fontein
63f4598737 acme_challenge_cert_helper: fail better to avoid crashes in Ansible (#282)
* Prevent acme_challenge_cert_helper triggering a bug in Ansible.

* Add changelog fragment.
2021-09-17 19:35:43 +02:00
Felix Fontein
598cdf0a21 Older openssl versions (1.0.1/1.0.2) do not seem to support '-' for /dev/stdin. (#279) 2021-09-15 20:42:52 +02:00
Ajpantuso
eea7bfc6bf openssh_cert - adding signature_algorithm option (#277)
* Initial Commit

* Update supported OpenSSH versions for RSA SHA-2 signed certs

* Updating 'regenerate' documentation
2021-09-15 08:53:53 +02:00
Felix Fontein
8521c96e8a Next expected release is 1.10.0. 2021-09-14 12:33:06 +02:00
135 changed files with 2657 additions and 525 deletions

View File

@@ -19,6 +19,11 @@ schedules:
branches:
include:
- main
- cron: 0 12 * * 0
displayName: Weekly (old stable branches)
always: true
branches:
include:
- stable-*
variables:
@@ -55,6 +60,17 @@ stages:
test: 'devel/sanity/extra'
- name: Units
test: 'devel/units/1'
- stage: Ansible_2_12
displayName: Sanity & Units 2.12
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.12/sanity/1'
- name: Units
test: '2.12/units/1'
- stage: Ansible_2_11
displayName: Sanity & Units 2.11
dependsOn: []
@@ -97,16 +113,12 @@ stages:
parameters:
testFormat: devel/linux/{0}/1
targets:
- name: CentOS 6
test: centos6
- name: CentOS 7
test: centos7
- name: CentOS 8
test: centos8
- name: Fedora 33
test: fedora33
- name: Fedora 34
test: fedora34
- name: Fedora 35
test: fedora35
- name: openSUSE 15 py2
test: opensuse15py2
- name: openSUSE 15 py3
@@ -115,6 +127,22 @@ stages:
test: ubuntu1804
- name: Ubuntu 20.04
test: ubuntu2004
- stage: Docker_2_12
displayName: Docker 2.12
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.12/linux/{0}/1
targets:
- name: CentOS 6
test: centos6
- name: Fedora 33
test: fedora33
- name: openSUSE 15 py3
test: opensuse15
- name: Ubuntu 20.04
test: ubuntu2004
- stage: Docker_2_11
displayName: Docker 2.11
dependsOn: []
@@ -125,18 +153,12 @@ stages:
targets:
- name: CentOS 7
test: centos7
- name: CentOS 8
test: centos8
- name: Fedora 32
test: fedora32
- name: openSUSE 15 py2
test: opensuse15py2
- name: openSUSE 15 py3
test: opensuse15
- name: Ubuntu 18.04
test: ubuntu1804
- name: Ubuntu 20.04
test: ubuntu2004
- stage: Docker_2_10
displayName: Docker 2.10
dependsOn: []
@@ -147,22 +169,10 @@ stages:
targets:
- name: CentOS 6
test: centos6
- name: CentOS 7
test: centos7
- name: CentOS 8
test: centos8
- name: Fedora 31
test: fedora31
- name: Fedora 32
test: fedora32
- name: openSUSE 15 py2
test: opensuse15py2
- name: openSUSE 15 py3
test: opensuse15
- name: Ubuntu 16.04
test: ubuntu1604
- name: Ubuntu 18.04
test: ubuntu1804
- stage: Docker_2_9
displayName: Docker 2.9
dependsOn: []
@@ -175,16 +185,8 @@ stages:
test: centos6
- name: CentOS 7
test: centos7
- name: CentOS 8
test: centos8
- name: Fedora 30
test: fedora30
- name: Fedora 31
test: fedora31
- name: openSUSE 15 py2
test: opensuse15py2
- name: openSUSE 15 py3
test: opensuse15
- name: Ubuntu 16.04
test: ubuntu1604
- name: Ubuntu 18.04
@@ -199,14 +201,28 @@ stages:
parameters:
testFormat: devel/{0}/1
targets:
- name: macOS 11.1
test: macos/11.1
- name: macOS 12.0
test: macos/12.0
- name: RHEL 7.9
test: rhel/7.9
- name: RHEL 8.5
test: rhel/8.5
- name: FreeBSD 12.3
test: freebsd/12.3
- name: FreeBSD 13.0
test: freebsd/13.0
- stage: Remote_2_12
displayName: Remote 2.12
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.12/{0}/1
targets:
- name: macOS 11.1
test: macos/11.1
- name: RHEL 8.4
test: rhel/8.4
- name: FreeBSD 12.2
test: freebsd/12.2
- name: FreeBSD 13.0
test: freebsd/13.0
- stage: Remote_2_11
@@ -221,8 +237,6 @@ stages:
test: rhel/7.9
- name: RHEL 8.3
test: rhel/8.3
- name: macOS 11.1
test: macos/11.1
- name: FreeBSD 12.2
test: freebsd/12.2
- stage: Remote_2_10
@@ -233,8 +247,6 @@ stages:
parameters:
testFormat: 2.10/{0}/1
targets:
- name: RHEL 7.8
test: rhel/7.8
- name: OS X 10.11
test: osx/10.11
- name: macOS 10.15
@@ -261,7 +273,6 @@ stages:
nameFormat: Python {0}
testFormat: devel/cloud/{0}/1
targets:
- test: 2.6
- test: 2.7
- test: 3.5
- test: 3.6
@@ -269,6 +280,17 @@ stages:
- test: 3.8
- test: 3.9
- test: "3.10"
- stage: Cloud_2_12
displayName: Cloud 2.12
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.12/cloud/{0}/1
targets:
- test: 2.6
- test: 3.9
- stage: Cloud_2_11
displayName: Cloud 2.11
dependsOn: []
@@ -306,20 +328,24 @@ stages:
condition: succeededOrFailed()
dependsOn:
- Ansible_devel
- Ansible_2_12
- Ansible_2_11
- Ansible_2_10
- Ansible_2_9
- Remote_devel
- Docker_devel
- Cloud_devel
- Remote_2_12
- Remote_2_11
- Docker_2_11
- Cloud_2_11
- Remote_2_10
- Docker_2_10
- Cloud_2_10
- Remote_2_9
- Docker_devel
- Docker_2_12
- Docker_2_11
- Docker_2_10
- Docker_2_9
- Cloud_devel
- Cloud_2_12
- Cloud_2_11
- Cloud_2_10
- Cloud_2_9
jobs:
- template: templates/coverage.yml

View File

@@ -11,7 +11,7 @@ mkdir "${agent_temp_directory}/coverage/"
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
# 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

@@ -12,4 +12,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
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"
displayName: Publish to Azure Pipelines
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
condition: gt(variables.coverageFileCount, 0)
continueOnError: true

View File

@@ -5,6 +5,140 @@ Community Crypto Release Notes
.. contents:: Topics
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
======
Release Summary
---------------
Regular bugfix release.
Bugfixes
--------
- acme_* modules - fix commands composed for OpenSSL backend to retrieve information on CSRs and certificates from stdin to use ``/dev/stdin`` instead of ``-``. This is needed for OpenSSL 1.0.1 and 1.0.2, apparently (https://github.com/ansible-collections/community.crypto/pull/279).
- acme_challenge_cert_helper - only return exception when cryptography is not installed, not when a too old version of it is installed. This prevents Ansible's callback to crash (https://github.com/ansible-collections/community.crypto/pull/281).
v1.9.3
======

View File

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

View File

@@ -528,6 +528,63 @@ releases:
changes:
release_summary: Accidental 1.9.1 release. Identical to 1.9.0.
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.2:
changes:
release_summary: Bugfix release to fix the changelog. No other change compared
@@ -547,3 +604,97 @@ releases:
- 1.9.3.yml
- 271-openssl_csr-utf8.yml
release_date: '2021-09-14'
1.9.4:
changes:
bugfixes:
- acme_* modules - fix commands composed for OpenSSL backend to retrieve information
on CSRs and certificates from stdin to use ``/dev/stdin`` instead of ``-``.
This is needed for OpenSSL 1.0.1 and 1.0.2, apparently (https://github.com/ansible-collections/community.crypto/pull/279).
- acme_challenge_cert_helper - only return exception when cryptography is not
installed, not when a too old version of it is installed. This prevents Ansible's
callback to crash (https://github.com/ansible-collections/community.crypto/pull/281).
release_summary: Regular bugfix release.
fragments:
- 1.9.4.yml
- 279-acme-openssl.yml
- 282-acme_challenge_cert_helper-error.yml
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
community.crypto.x509_certificate_pipe:
csr_content: "{{ ca_csr.csr }}"
csr_content: "{{ csr.csr }}"
provider: ownca
ownca_path: /path/to/ca-certificate.pem
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
community.crypto.x509_certificate_pipe:
content: "{{ (certificate.content | b64decode) if certificate_exists.stat.exists else omit }}"
csr_content: "{{ ca_csr.csr }}"
csr_content: "{{ csr.csr }}"
provider: ownca
ownca_path: /path/to/ca-certificate.pem
ownca_privatekey_path: /path/to/ca-certificate.key

View File

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

View File

@@ -457,8 +457,8 @@ options:
- Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ 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.
- 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.
type: str
default: +0s
@@ -470,8 +470,8 @@ options:
- Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ 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.
- 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.
- 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.
@@ -548,8 +548,8 @@ options:
- Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ 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.
- 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.
type: str
default: +0s
@@ -562,8 +562,8 @@ options:
- Time will always be interpreted as UTC.
- Valid format is C([+-]timespec | ASN.1 TIME) where timespec can be an integer
+ 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.
- 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.
- 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.

View File

@@ -100,7 +100,7 @@ options:
description:
- 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.
- 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
selected one for generation.
- 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 licenses/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
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.urls import fetch_url
from ansible.module_utils.six import PY3
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
OpenSSLCLIBackend,
@@ -228,9 +229,14 @@ class ACMEClient(object):
resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST')
_assert_fetch_url_success(self.module, resp, info)
result = {}
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()
except AttributeError:
except (AttributeError, TypeError):
content = info.pop('body', None)
if content or not parse_json_result:
@@ -284,8 +290,12 @@ class ACMEClient(object):
_assert_fetch_url_success(self.module, resp, info)
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()
except AttributeError:
except (AttributeError, TypeError):
content = info.pop('body', None)
# Process result

View File

@@ -14,7 +14,9 @@ import datetime
import os
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 (
CryptoBackend,
@@ -41,6 +43,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
cryptography_name_to_oid,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
extract_first_pem,
)
try:
import cryptography
import cryptography.hazmat.backends
@@ -53,7 +59,6 @@ try:
import cryptography.hazmat.primitives.serialization
import cryptography.x509
import cryptography.x509.oid
from distutils.version import LooseVersion
CRYPTOGRAPHY_VERSION = cryptography.__version__
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5'))
if HAS_CURRENT_CRYPTOGRAPHY:
@@ -357,6 +362,9 @@ class CryptographyBackend(CryptoBackend):
if cert_content is None:
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:
cert = cryptography.x509.load_pem_x509_certificate(cert_content, _cryptography_backend)
except Exception as e:

View File

@@ -230,7 +230,7 @@ class OpenSSLCLIBackend(CryptoBackend):
filename = csr_filename
data = None
if csr_content is not None:
filename = '-'
filename = '/dev/stdin'
data = csr_content.encode('utf-8')
openssl_csr_cmd = [self.openssl_binary, "req", "-in", filename, "-noout", "-text"]
@@ -267,7 +267,7 @@ class OpenSSLCLIBackend(CryptoBackend):
filename = cert_filename
data = None
if cert_content is not None:
filename = '-'
filename = '/dev/stdin'
data = cert_content.encode('utf-8')
cert_filename_suffix = ''
elif cert_filename is not None:

View File

@@ -7,8 +7,8 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import binary_type
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=''):
@@ -52,8 +52,12 @@ class ACMEProtocolException(ModuleFailException):
# 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:
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()
except AttributeError:
except (AttributeError, TypeError):
content = info.pop('body', None)
# Make sure that content_json is None or a dictionary

View File

@@ -20,6 +20,10 @@ from __future__ import absolute_import, division, print_function
__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):
# Set to 80 on the recommendation of
# https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values

View File

@@ -20,7 +20,7 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
from distutils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
try:
import OpenSSL # noqa

View File

@@ -26,15 +26,41 @@ import re
from ansible.module_utils.common.text.converters import to_text, to_bytes
from ._asn1 import serialize_asn1_string_as_der
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
try:
import cryptography
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.asymmetric import padding
import ipaddress
except ImportError:
# Error handled in the calling module.
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:
# This is a separate try/except since this is only present in cryptography 2.5 or newer
from cryptography.hazmat.primitives.serialization.pkcs12 import (
@@ -44,9 +70,23 @@ except ImportError:
# Error handled in the calling module.
_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 (
CRYPTOGRAPHY_HAS_DSA_SIGN,
CRYPTOGRAPHY_HAS_EC_SIGN,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED25519_SIGN,
CRYPTOGRAPHY_HAS_ED448,
CRYPTOGRAPHY_HAS_ED448_SIGN,
CRYPTOGRAPHY_HAS_RSA_SIGN,
OpenSSLObjectError,
)
@@ -64,60 +104,114 @@ DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
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()
backend = cert._backend
x509_obj = cert._x509
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 = 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
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()
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._ffi.gc(
extensions,
lambda ext: backend._lib.sk_X509_EXTENSION_pop_free(
ext,
backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free")
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req)
extensions = backend._ffi.gc(
extensions,
lambda ext: backend._lib.sk_X509_EXTENSION_pop_free(
ext,
backend._ffi.addressof(backend._lib._original_lib, "X509_EXTENSION_free")
)
)
)
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
ext = backend._lib.sk_X509_EXTENSION_value(extensions, 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
# 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(csr.extensions)
for i in range(backend._lib.sk_X509_EXTENSION_num(extensions)):
ext = backend._lib.sk_X509_EXTENSION_value(extensions, 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 CSR.
for ext in csr.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()),
)
return result
@@ -280,11 +374,11 @@ def _dn_escape_value(value):
'''
Escape Distinguished Name's attribute value.
'''
value = value.replace('\\', '\\\\')
for ch in [',', '#', '+', '<', '>', ';', '"', '=', '/']:
value = value.replace(ch, '\\%s' % ch)
if value.startswith(' '):
value = r'\ ' + value[1:]
value = value.replace(u'\\', u'\\\\')
for ch in [u',', u'#', u'+', u'<', u'>', u';', u'"', u'=', u'/']:
value = value.replace(ch, u'\\%s' % ch)
if value.startswith(u' '):
value = u'\\ ' + value[1:]
return value
@@ -294,24 +388,24 @@ def cryptography_decode_name(name):
Raises an OpenSSLObjectError if the name is not supported.
'''
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.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
return 'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
return 'IP:{0}'.format(name.value.compressed)
return u'IP:{0}/{1}'.format(name.value.network_address.compressed, name.value.prefixlen)
return u'IP:{0}'.format(name.value.compressed)
if isinstance(name, x509.RFC822Name):
return 'email:{0}'.format(name.value)
return u'email:{0}'.format(name.value)
if isinstance(name, x509.UniformResourceIdentifier):
return 'URI:{0}'.format(name.value)
return u'URI:{0}'.format(name.value)
if isinstance(name, x509.DirectoryName):
return 'dirName:' + ''.join([
'/{0}={1}'.format(cryptography_oid_to_name(attribute.oid, short=True), _dn_escape_value(attribute.value))
return u'dirName:' + u''.join([
u'/{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
for attribute in name.value
])
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):
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))
@@ -442,17 +536,111 @@ def cryptography_serial_number_of_cert(cert):
def parse_pkcs12(pkcs12_bytes, passphrase=None):
'''Returns a tuple (private_key, certificate, additional_certificates, friendly_name).
'''
if _load_key_and_certificates is None:
raise ValueError('load_key_and_certificates() not 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 _load_pkcs12 is None and _load_key_and_certificates is None:
raise ValueError('neither load_pkcs12() nor load_key_and_certificates() present in the current cryptography version')
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
if certificate:
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
maybe_name = certificate._backend._lib.X509_alias_get0(
certificate._x509, certificate._backend._ffi.NULL)
if maybe_name != certificate._backend._ffi.NULL:
friendly_name = certificate._backend._ffi.string(maybe_name)
backend = default_backend()
# This code basically does what load_key_and_certificates() does, but without error-checking.
# 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
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 traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
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.basic import (

View File

@@ -15,12 +15,12 @@ import datetime
import re
import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
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_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate,
get_fingerprint_of_bytes,

View File

@@ -10,11 +10,12 @@ __metaclass__ = type
import os
from distutils.version import LooseVersion
from random import randrange
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 (
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 (
cryptography_compare_public_keys,
cryptography_key_needs_digest_for_signing,
cryptography_serial_number_of_cert,
cryptography_verify_certificate_signature,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -106,6 +109,9 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
except OpenSSLBadPassphraseError as 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 self.digest is None:
raise CertificateError(
@@ -172,6 +178,16 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
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
if self.create_authority_key_identifier:
try:
@@ -184,7 +200,6 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
except cryptography.x509.ExtensionNotFound:
expected_ext = x509.AuthorityKeyIdentifier.from_issuer_public_key(self.ca_cert.public_key())
self._ensure_existing_certificate_loaded()
try:
ext = self.existing_certificate.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
if ext.value != expected_ext:
@@ -296,6 +311,18 @@ class OwnCACertificateBackendPyOpenSSL(CertificateBackend):
"""Return bytes for 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):
result = super(OwnCACertificateBackendPyOpenSSL, self).dump(include_certificate)
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 (
cryptography_key_needs_digest_for_signing,
cryptography_serial_number_of_cert,
cryptography_verify_certificate_signature,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -134,6 +135,18 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
"""Return bytes for self.cert."""
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):
result = super(SelfSignedCertificateBackendCryptography, self).dump(include_certificate)

View File

@@ -9,10 +9,10 @@ __metaclass__ = type
import traceback
from distutils.version import LooseVersion
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 (
cryptography_oid_to_name,
)

View File

@@ -12,12 +12,12 @@ import abc
import binascii
import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib
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 (
OpenSSLObjectError,
OpenSSLBadPassphraseError,

View File

@@ -13,12 +13,12 @@ import abc
import binascii
import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
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_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate_request,
get_fingerprint_of_bytes,

View File

@@ -12,12 +12,12 @@ import abc
import base64
import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib
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 (
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL,

View File

@@ -12,12 +12,12 @@ __metaclass__ = type
import abc
import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib
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 (
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,

View File

@@ -10,12 +10,12 @@ __metaclass__ = type
import abc
import traceback
from distutils.version import LooseVersion
from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib
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 (
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X448,

View File

@@ -72,3 +72,13 @@ def split_pem_list(text, keep_inbetween=False):
result.append(''.join(current))
current = [] if keep_inbetween else None
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,10 +21,12 @@ __metaclass__ = type
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 ._objects import OID_LOOKUP
try:
import OpenSSL
except ImportError:
@@ -87,18 +89,25 @@ def pyopenssl_get_extensions_from_cert(cert):
critical=bool(ext.get_critical()),
value=base64.b64encode(ext.get_data()),
)
oid = obj2txt(
OpenSSL._util.lib,
OpenSSL._util.ffi,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
)
# This could also be done a bit simpler:
#
# 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
# similarly to how cryptography does it.
try:
oid = obj2txt(
OpenSSL._util.lib,
OpenSSL._util.ffi,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
)
# This could also be done a bit simpler:
#
# 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
# 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
return result
@@ -113,18 +122,25 @@ def pyopenssl_get_extensions_from_csr(csr):
critical=bool(ext.get_critical()),
value=base64.b64encode(ext.get_data()),
)
oid = obj2txt(
OpenSSL._util.lib,
OpenSSL._util.ffi,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
)
# This could also be done a bit simpler:
#
# 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
# similarly to how cryptography does it.
try:
oid = obj2txt(
OpenSSL._util.lib,
OpenSSL._util.ffi,
OpenSSL._util.lib.X509_EXTENSION_get_object(ext._extension)
)
# This could also be done a bit simpler:
#
# 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
# 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
return result

View File

@@ -160,7 +160,8 @@ class KeygenCommand(object):
self._run_command = module.run_command
def generate_certificate(self, certificate_path, identifier, options, pkcs11_provider, principals,
serial_number, signing_key_path, type, time_parameters, use_agent, **kwargs):
serial_number, signature_algorithm, signing_key_path, type,
time_parameters, use_agent, **kwargs):
args = [self._bin_path, '-s', signing_key_path, '-P', '', '-I', identifier]
if options:
@@ -178,6 +179,8 @@ class KeygenCommand(object):
args.extend(['-U'])
if time_parameters.validity_string:
args.extend(['-V', time_parameters.validity_string])
if signature_algorithm:
args.extend(['-t', signature_algorithm])
args.append(certificate_path)
return self._run_command(args, **kwargs)

View File

@@ -21,12 +21,13 @@ __metaclass__ = type
import abc
import os
from distutils.version import LooseVersion
from ansible.module_utils import six
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_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptography import (
HAS_OPENSSH_SUPPORT,
HAS_OPENSSH_PRIVATE_FORMAT,

View File

@@ -555,6 +555,11 @@ class OpensshCertificate(object):
def signing_key(self):
return to_text(self._cert_info.signing_key_fingerprint())
@property
def signature_type(self):
signature_data = OpensshParser.signature_data(self.signature)
return to_text(signature_data['signature_type'])
@staticmethod
def _parse_cert_info(pub_key_type, parser):
cert_info = get_cert_info_object(pub_key_type)

View File

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

View File

@@ -201,8 +201,9 @@ class OpensshParser(object):
signature_blob = parser.string()
blob_parser = cls(signature_blob)
if signature_type == b'ssh-rsa':
if signature_type in (b'ssh-rsa', b'rsa-sha2-256', b'rsa-sha2-512'):
# https://datatracker.ietf.org/doc/html/rfc4253#section-6.6
# https://datatracker.ietf.org/doc/html/rfc8332#section-3
signature_data['s'] = cls._big_int(signature_blob, "big")
elif signature_type == b'ssh-dss':
# https://datatracker.ietf.org/doc/html/rfc4253#section-6.6

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
community.crypto.acme_account_info:
account_key_src: /etc/pki/cert/private/account.key
register: account_data
register: account_data
- name: Verify that account exists
assert:
that:
@@ -70,7 +70,7 @@ EXAMPLES = '''
acme_account_info:
account_key_content: "{{ acme_account_key }}"
account_uri: "{{ acme_account_uri }}"
register: account_data
register: account_data
- name: Verify that account exists
assert:
that:

View File

@@ -308,7 +308,7 @@ EXAMPLES = r'''
# - copy:
# 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'] }}"
# 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:
#

View File

@@ -145,6 +145,8 @@ import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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.io import (
@@ -164,7 +166,6 @@ try:
import cryptography.x509
import cryptography.x509.oid
import ipaddress
from distutils.version import LooseVersion
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.3'))
_cryptography_backend = cryptography.hazmat.backends.default_backend()
except ImportError as dummy:
@@ -202,7 +203,10 @@ def main():
),
)
if not HAS_CRYPTOGRAPHY:
module.fail_json(msg=missing_required_lib('cryptography >= 1.3'), exception=CRYPTOGRAPHY_IMP_ERR)
# Some callbacks die when exception is provided with value None
if CRYPTOGRAPHY_IMP_ERR:
module.fail_json(msg=missing_required_lib('cryptography >= 1.3'), exception=CRYPTOGRAPHY_IMP_ERR)
module.fail_json(msg=missing_required_lib('cryptography >= 1.3'))
try:
# Get parameters

View File

@@ -124,6 +124,8 @@ import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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 (
split_pem_list,
)
@@ -140,7 +142,6 @@ try:
import cryptography.hazmat.primitives.asymmetric.utils
import cryptography.x509
import cryptography.x509.oid
from distutils.version import LooseVersion
HAS_CRYPTOGRAPHY = (LooseVersion(cryptography.__version__) >= LooseVersion('1.5'))
_cryptography_backend = cryptography.hazmat.backends.default_backend()
except ImportError as dummy:
@@ -236,13 +237,17 @@ class CertificateSet(object):
def __init__(self, module):
self.module = module
self.certificates = set()
self.certificate_by_issuer = dict()
self.certificates_by_issuer = dict()
self.certificate_by_cert = dict()
def _load_file(self, path):
certs = load_PEM_list(self.module, path, fail_on_error=False)
for cert in certs:
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):
'''
@@ -260,8 +265,8 @@ class CertificateSet(object):
'''
Search for the parent (issuer) of a certificate. Return ``None`` if none was found.
'''
potential_parent = self.certificate_by_issuer.get(cert.cert.issuer)
if potential_parent is not None:
potential_parents = self.certificates_by_issuer.get(cert.cert.issuer, [])
for potential_parent in potential_parents:
if is_parent(self.module, cert, potential_parent):
return potential_parent
return None
@@ -274,6 +279,16 @@ def format_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():
module = AnsibleModule(
argument_spec=dict(
@@ -312,13 +327,19 @@ def main():
# Try to complete chain
current = chain[-1]
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:
root = roots.find_parent(current)
if root:
check_cycle(module, occured_certificates, root)
completed.append(root)
break
intermediate = intermediates.find_parent(current)
if intermediate:
check_cycle(module, occured_certificates, intermediate)
completed.append(intermediate)
current = intermediate
else:

View File

@@ -519,11 +519,11 @@ import re
import time
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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 (
write_file,
)

View File

@@ -167,7 +167,6 @@ import base64
import datetime
import traceback
from distutils.version import LooseVersion
from os.path import isfile
from socket import create_connection, setdefaulttimeout, socket
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.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 (
cryptography_oid_to_name,
cryptography_get_extensions_from_cert,

View File

@@ -356,6 +356,34 @@ LUKS_NAME_REGEX = re.compile(r'\s*crypt\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):
def __init__(self, module):
@@ -515,9 +543,17 @@ class CryptHandler(Handler):
self.run_luks_close(name)
result = self._run_command([wipefs_bin, '--all', device])
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]))
# 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,
new_passphrase, pbkdf):
''' Add new key from a keyfile or passphrase to given 'device';
@@ -785,6 +821,7 @@ def run_module():
module = AnsibleModule(argument_spec=module_args,
supports_check_mode=True,
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:
try:

View File

@@ -49,7 +49,7 @@ options:
- When C(fail) the task will fail if a certificate already exists at I(path) and does not
match the module's options.
- When C(partial_idempotence) an existing certificate will be regenerated based on
I(serial), I(type), I(valid_from), I(valid_to), I(valid_at), and I(principals).
I(serial), I(signature_algorithm), I(type), I(valid_from), I(valid_to), I(valid_at), and I(principals).
- When C(full_idempotence) I(identifier), I(options), I(public_key), and I(signing_key)
are also considered when compared against an existing certificate.
- C(always) is equivalent to I(force=true).
@@ -62,6 +62,26 @@ options:
- always
default: partial_idempotence
version_added: 1.8.0
signature_algorithm:
description:
- As of OpenSSH 8.2 the SHA-1 signature algorithm for RSA keys has been disabled and C(ssh) will refuse
host certificates signed with the SHA-1 algorithm. OpenSSH 8.1 made C(rsa-sha2-512) the default algorithm
when acting as a CA and signing certificates with a RSA key. However, for OpenSSH versions less than 8.1
the SHA-2 signature algorithms, C(rsa-sha2-256) or C(rsa-sha2-512), must be specified using this option
if compatibility with newer C(ssh) clients is required. Conversely if hosts using OpenSSH version 8.2
or greater must remain compatible with C(ssh) clients using OpenSSH less than 7.2, then C(ssh-rsa)
can be used when generating host certificates (a corresponding change to the sshd_config to add C(ssh-rsa)
to the C(CASignatureAlgorithms) keyword is also required).
- Using any value for this option with a non-RSA I(signing_key) will cause this module to fail.
- "Note: OpenSSH versions prior to 7.2 do not support SHA-2 signature algorithms for RSA keys and OpenSSH
versions prior to 7.3 do not support SHA-2 signature algorithms for certificates."
- See U(https://www.openssh.com/txt/release-8.2) for more information.
type: str
choices:
- ssh-rsa
- rsa-sha2-256
- rsa-sha2-512
version_added: 1.10.0
signing_key:
description:
- The path to the private openssh key that is used for signing the public key in order to generate the certificate.
@@ -238,11 +258,12 @@ info:
'''
import os
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule
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 (
KeygenCommand,
OpensshModule,
@@ -269,6 +290,7 @@ class Certificate(OpensshModule):
self.public_key = self.module.params['public_key']
self.regenerate = self.module.params['regenerate'] if not self.module.params['force'] else 'always'
self.serial_number = self.module.params['serial_number']
self.signature_algorithm = self.module.params['signature_algorithm']
self.signing_key = self.module.params['signing_key']
self.state = self.module.params['state']
self.type = self.module.params['type']
@@ -357,7 +379,7 @@ class Certificate(OpensshModule):
def _is_fully_valid(self):
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.public_key == self._get_key_fingerprint(self.public_key),
self.original_data.signing_key == self._get_key_fingerprint(self.signing_key),
@@ -366,6 +388,7 @@ class Certificate(OpensshModule):
def _is_partially_valid(self):
return all([
set(self.original_data.principals) == set(self.principals),
self.original_data.signature_type == self.signature_algorithm if self.signature_algorithm else True,
self.original_data.serial == self.serial_number if self.serial_number is not None else True,
self.original_data.type == self.type,
self._compare_time_parameters(),
@@ -425,7 +448,7 @@ class Certificate(OpensshModule):
self.ssh_keygen.generate_certificate(
key_copy, self.identifier, self.options, self.pkcs11_provider, self.principals, self.serial_number,
self.signing_key, self.type, self.time_parameters, self.use_agent,
self.signature_algorithm, self.signing_key, self.type, self.time_parameters, self.use_agent,
environ_update=dict(TZ="UTC"), check_rc=True
)
@@ -485,6 +508,8 @@ def get_cert_dict(data):
result = data.to_dict()
result.pop('nonce')
result['signature_algorithm'] = data.signature_type
return result
@@ -503,6 +528,7 @@ def main():
default='partial_idempotence',
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
),
signature_algorithm=dict(type='str', choices=['ssh-rsa', 'rsa-sha2-256', 'rsa-sha2-512']),
signing_key=dict(type='path'),
serial_number=dict(type='int'),
state=dict(type='str', default='present', choices=['absent', 'present']),

View File

@@ -128,11 +128,11 @@ import re
import tempfile
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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 (
load_file_if_exists,
write_file,

View File

@@ -239,11 +239,11 @@ import os
import stat
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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 (
load_file_if_exists,
write_file,

View File

@@ -182,11 +182,11 @@ publickey:
import os
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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 (
load_file_if_exists,
write_file,

View File

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

View File

@@ -96,9 +96,10 @@ signature:
import os
import traceback
from distutils.version import LooseVersion
import base64
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
MINIMAL_PYOPENSSL_VERSION = '0.11'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'

View File

@@ -96,9 +96,10 @@ valid:
import os
import traceback
from distutils.version import LooseVersion
import base64
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
MINIMAL_PYOPENSSL_VERSION = '0.11'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.4'

View File

@@ -367,11 +367,11 @@ import base64
import os
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
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 (
write_file,
)

View File

@@ -1,2 +1,14 @@
shippable/cloud/group1
cloud/acme
# Since skipping below fails miserably with ansible-core 2.11 and earlier, we have to skip all POSIX tests...
# (https://github.com/ansible/ansible/issues/75711)
# shippable/posix/group1
# Skip all VMs, since we cannot talk to the ACME simulator from these:
# (TODO: remove when ansible-core 2.12 is the earliest version we support)
# skip/aix
# skip/freebsd
# skip/macos
# skip/osx
# skip/rhel

View File

@@ -2,8 +2,8 @@
- name: Generate account keys
openssl_privatekey:
path: "{{ remote_tmp_dir }}/{{ item.name }}.pem"
passphrase: "{{ item.pass | default(omit, true) }}"
cipher: "{{ 'auto' if item.pass | default() else omit }}"
passphrase: "{{ item.pass | default(omit) | default(omit, true) }}"
cipher: "{{ 'auto' if (item.pass | default(false)) else omit }}"
type: ECC
curve: secp256r1
force: true
@@ -12,7 +12,7 @@
- name: Parse account keys (to ease debugging some test failures)
openssl_privatekey_info:
path: "{{ remote_tmp_dir }}/{{ item.name }}.pem"
passphrase: "{{ item.pass | default(omit, true) }}"
passphrase: "{{ item.pass | default(omit) | default(omit, true) }}"
return_private_key_data: true
loop: "{{ account_keys }}"

View File

@@ -1,2 +1,14 @@
shippable/cloud/group1
cloud/acme
# Since skipping below fails miserably with ansible-core 2.11 and earlier, we have to skip all POSIX tests...
# (https://github.com/ansible/ansible/issues/75711)
# shippable/posix/group1
# Skip all VMs, since we cannot talk to the ACME simulator from these:
# (TODO: remove when ansible-core 2.12 is the earliest version we support)
# skip/aix
# skip/freebsd
# skip/macos
# skip/osx
# skip/rhel

View File

@@ -1,2 +1,14 @@
shippable/cloud/group1
cloud/acme
# Since skipping below fails miserably with ansible-core 2.11 and earlier, we have to skip all POSIX tests...
# (https://github.com/ansible/ansible/issues/75711)
# shippable/posix/group1
# Skip all VMs, since we cannot talk to the ACME simulator from these:
# (TODO: remove when ansible-core 2.12 is the earliest version we support)
# skip/aix
# skip/freebsd
# skip/macos
# skip/osx
# skip/rhel

View File

@@ -1,3 +1,5 @@
dependencies:
- setup_acme
- setup_pyopenssl # needed for Ubuntu 16.04
- setup_remote_tmp_dir
- prepare_jinja2_compat

View File

@@ -264,92 +264,98 @@
set_fact:
cert_5_recreate_3: "{{ challenge_data is changed }}"
cert_5d_obtain_results: "{{ certificate_obtain_result }}"
- name: Obtain cert 6
include_tasks: obtain-cert.yml
vars:
certgen_title: Certificate 6
certificate_name: cert-6
key_type: rsa
rsa_bits: "{{ default_rsa_key_size }}"
subject_alt_name: "DNS:example.org"
subject_alt_name_critical: no
account_key: account-ec256
challenge: tls-alpn-01
modify_account: yes
deactivate_authzs: no
force: no
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
acme_expected_root_number: 0
select_chain:
# All intermediates have the same subject key identifier, so always
# the first chain will be found, and we need a second condition to
# make sure that the first condition actually works. (The second
# condition has been tested above.)
- test_certificates: first
subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}"
- test_certificates: last
issuer: "{{ acme_roots[1].subject }}"
use_csr_content: true
- name: Store obtain results for cert 6
set_fact:
cert_6_obtain_results: "{{ certificate_obtain_result }}"
cert_6_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 7
include_tasks: obtain-cert.yml
vars:
certgen_title: Certificate 7
certificate_name: cert-7
key_type: rsa
rsa_bits: "{{ default_rsa_key_size }}"
subject_alt_name:
- "IP:127.0.0.1"
# - "IP:::1"
subject_alt_name_critical: no
account_key: account-ec256
challenge: http-01
modify_account: yes
deactivate_authzs: no
force: no
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
acme_expected_root_number: 2
select_chain:
- test_certificates: last
authority_key_identifier: "{{ acme_roots[2].subject_key_identifier }}"
use_csr_content: false
- name: Store obtain results for cert 7
set_fact:
cert_7_obtain_results: "{{ certificate_obtain_result }}"
cert_7_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}"
- name: Obtain cert 8
include_tasks: obtain-cert.yml
vars:
certgen_title: Certificate 8
certificate_name: cert-8
key_type: rsa
rsa_bits: "{{ default_rsa_key_size }}"
subject_alt_name:
- "IP:127.0.0.1"
# IPv4 only since our test validation server doesn't work
# with IPv6 (thanks to Python's socketserver).
subject_alt_name_critical: no
account_key: account-ec256
challenge: tls-alpn-01
challenge_alpn_tls: acme_challenge_cert_helper
modify_account: yes
deactivate_authzs: no
force: no
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
use_csr_content: true
- name: Store obtain results for cert 8
set_fact:
cert_8_obtain_results: "{{ certificate_obtain_result }}"
cert_8_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
- block:
- name: Obtain cert 6
include_tasks: obtain-cert.yml
vars:
certgen_title: Certificate 6
certificate_name: cert-6
key_type: rsa
rsa_bits: "{{ default_rsa_key_size }}"
subject_alt_name: "DNS:example.org"
subject_alt_name_critical: no
account_key: account-ec256
challenge: tls-alpn-01
modify_account: yes
deactivate_authzs: no
force: no
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
acme_expected_root_number: 0
select_chain:
# All intermediates have the same subject key identifier, so always
# the first chain will be found, and we need a second condition to
# make sure that the first condition actually works. (The second
# condition has been tested above.)
- test_certificates: first
subject_key_identifier: "{{ acme_intermediates[0].subject_key_identifier }}"
- test_certificates: last
issuer: "{{ acme_roots[1].subject }}"
use_csr_content: true
- name: Store obtain results for cert 6
set_fact:
cert_6_obtain_results: "{{ certificate_obtain_result }}"
cert_6_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
when: acme_intermediates[0].subject_key_identifier is defined
- block:
- name: Obtain cert 7
include_tasks: obtain-cert.yml
vars:
certgen_title: Certificate 7
certificate_name: cert-7
key_type: rsa
rsa_bits: "{{ default_rsa_key_size }}"
subject_alt_name:
- "IP:127.0.0.1"
# - "IP:::1"
subject_alt_name_critical: no
account_key: account-ec256
challenge: http-01
modify_account: yes
deactivate_authzs: no
force: no
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
acme_expected_root_number: 2
select_chain:
- test_certificates: last
authority_key_identifier: "{{ acme_roots[2].subject_key_identifier }}"
use_csr_content: false
- name: Store obtain results for cert 7
set_fact:
cert_7_obtain_results: "{{ certificate_obtain_result }}"
cert_7_alternate: "{{ 2 if select_crypto_backend == 'cryptography' else 0 }}"
when: acme_roots[2].subject_key_identifier is defined
- block:
- name: Obtain cert 8
include_tasks: obtain-cert.yml
vars:
certgen_title: Certificate 8
certificate_name: cert-8
key_type: rsa
rsa_bits: "{{ default_rsa_key_size }}"
subject_alt_name:
- "IP:127.0.0.1"
# IPv4 only since our test validation server doesn't work
# with IPv6 (thanks to Python's socketserver).
subject_alt_name_critical: no
account_key: account-ec256
challenge: tls-alpn-01
challenge_alpn_tls: acme_challenge_cert_helper
modify_account: yes
deactivate_authzs: no
force: no
remaining_days: 10
terms_agreed: yes
account_email: "example@example.org"
use_csr_content: true
- name: Store obtain results for cert 8
set_fact:
cert_8_obtain_results: "{{ certificate_obtain_result }}"
cert_8_alternate: "{{ 0 if select_crypto_backend == 'cryptography' else 0 }}"
when: cryptography_version.stdout is version('1.3', '>=')
## DISSECT CERTIFICATES #######################################################################
# Make sure certificates are valid. Root certificate for Pebble equals the chain certificate.
- name: Verifying cert 1
@@ -376,14 +382,17 @@
command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-6-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-6-chain.pem" "{{ remote_tmp_dir }}/cert-6.pem"'
ignore_errors: yes
register: cert_6_valid
when: acme_intermediates[0].subject_key_identifier is defined
- name: Verifying cert 7
command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-7-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-7-chain.pem" "{{ remote_tmp_dir }}/cert-7.pem"'
ignore_errors: yes
register: cert_7_valid
when: acme_roots[2].subject_key_identifier is defined
- name: Verifying cert 8
command: '{{ openssl_binary }} verify -CAfile "{{ remote_tmp_dir }}/cert-8-root.pem" -untrusted "{{ remote_tmp_dir }}/cert-8-chain.pem" "{{ remote_tmp_dir }}/cert-8.pem"'
ignore_errors: yes
register: cert_8_valid
when: cryptography_version.stdout is version('1.3', '>=')
# Dump certificate info
- name: Dumping cert 1
command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-1.pem" -noout -text'
@@ -403,12 +412,15 @@
- name: Dumping cert 6
command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-6.pem" -noout -text'
register: cert_6_text
when: acme_intermediates[0].subject_key_identifier is defined
- name: Dumping cert 7
command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-7.pem" -noout -text'
register: cert_7_text
when: acme_roots[2].subject_key_identifier is defined
- name: Dumping cert 8
command: '{{ openssl_binary }} x509 -in "{{ remote_tmp_dir }}/cert-8.pem" -noout -text'
register: cert_8_text
when: cryptography_version.stdout is version('1.3', '>=')
# Dump certificate info
- name: Dumping cert 1
x509_certificate_info:
@@ -434,14 +446,17 @@
x509_certificate_info:
path: "{{ remote_tmp_dir }}/cert-6.pem"
register: cert_6_info
when: acme_intermediates[0].subject_key_identifier is defined
- name: Dumping cert 7
x509_certificate_info:
path: "{{ remote_tmp_dir }}/cert-7.pem"
register: cert_7_info
when: acme_roots[2].subject_key_identifier is defined
- name: Dumping cert 8
x509_certificate_info:
path: "{{ remote_tmp_dir }}/cert-8.pem"
register: cert_8_info
when: cryptography_version.stdout is version('1.3', '>=')
## GET ACCOUNT ORDERS #########################################################################
- name: Don't retrieve orders
acme_account_info:

View File

@@ -124,14 +124,38 @@
that:
- cert_5_recreate_3 == True
- name: Check that certificate 6 is valid
assert:
that:
- cert_6_valid is not failed
- name: Check that certificate 6 contains correct SANs
assert:
that:
- "'DNS:example.org' in cert_6_text.stdout"
- block:
- name: Check that certificate 6 is valid
assert:
that:
- cert_6_valid is not failed
- name: Check that certificate 6 contains correct SANs
assert:
that:
- "'DNS:example.org' in cert_6_text.stdout"
when: acme_intermediates[0].subject_key_identifier is defined
- block:
- name: Check that certificate 7 is valid
assert:
that:
- cert_7_valid is not failed
- name: Check that certificate 7 contains correct SANs
assert:
that:
- "'IP Address:127.0.0.1' in cert_8_text.stdout or 'IP:127.0.0.1' in cert_8_text.stdout"
when: acme_roots[2].subject_key_identifier is defined
- block:
- name: Check that certificate 8 is valid
assert:
that:
- cert_8_valid is not failed
- name: Check that certificate 8 contains correct SANs
assert:
that:
- "'IP Address:127.0.0.1' in cert_8_text.stdout or 'IP:127.0.0.1' in cert_8_text.stdout"
when: cryptography_version.stdout is version('1.3', '>=')
- name: Validate that orders were not retrieved
assert:

View File

@@ -1,2 +1,14 @@
shippable/cloud/group1
cloud/acme
# Since skipping below fails miserably with ansible-core 2.11 and earlier, we have to skip all POSIX tests...
# (https://github.com/ansible/ansible/issues/75711)
# shippable/posix/group1
# Skip all VMs, since we cannot talk to the ACME simulator from these:
# (TODO: remove when ansible-core 2.12 is the earliest version we support)
# skip/aix
# skip/freebsd
# skip/macos
# skip/osx
# skip/rhel

View File

@@ -1,2 +1,14 @@
shippable/cloud/group1
cloud/acme
# Since skipping below fails miserably with ansible-core 2.11 and earlier, we have to skip all POSIX tests...
# (https://github.com/ansible/ansible/issues/75711)
# shippable/posix/group1
# Skip all VMs, since we cannot talk to the ACME simulator from these:
# (TODO: remove when ansible-core 2.12 is the earliest version we support)
# skip/aix
# skip/freebsd
# skip/macos
# skip/osx
# skip/rhel

View File

@@ -31,4 +31,4 @@
terms_agreed: yes
account_email: "example@example.org"
when: openssl_version.stdout is version('1.0.0', '>=') or cryptography_version.stdout is version('1.5', '>=')
when: cryptography_version.stdout is version('1.5', '>=')

View File

@@ -1,2 +1,14 @@
shippable/cloud/group1
cloud/acme
# Since skipping below fails miserably with ansible-core 2.11 and earlier, we have to skip all POSIX tests...
# (https://github.com/ansible/ansible/issues/75711)
# shippable/posix/group1
# Skip all VMs, since we cannot talk to the ACME simulator from these:
# (TODO: remove when ansible-core 2.12 is the earliest version we support)
# skip/aix
# skip/freebsd
# skip/macos
# skip/osx
# skip/rhel

View File

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

View File

@@ -1 +1,2 @@
shippable/cloud/group1
shippable/posix/group1

View File

@@ -1,3 +1,4 @@
dependencies:
- prepare_jinja2_compat
- setup_openssl
- 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:
- name: Make sure testhost directory exists
file:
path: '{{ remote_tmp_dir }}/files/'
@@ -13,68 +14,14 @@
copy:
src: '{{ role_path }}/files/'
dest: '{{ remote_tmp_dir }}/files/'
- name: Find root for cert 1
certificate_complete_chain:
input_chain: '{{ lookup("file", "cert1-fullchain.pem", rstrip=False) }}'
root_certificates:
- '{{ remote_tmp_dir }}/files/roots/'
register: cert1_root
- name: Verify root for cert 1
assert:
that:
- 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)
- name: Run tests with copied certificates
import_tasks: existing.yml
- name: Create more certificates
import_tasks: create.yml
- name: Run tests with created certificates
import_tasks: created.yml
when: cryptography_version.stdout is version('1.5', '>=')

View File

@@ -1,3 +1,4 @@
shippable/cloud/group1
shippable/posix/group1
destructive
needs/httptester

View File

@@ -4,16 +4,35 @@
# and should not be used as examples of how to write Ansible roles #
####################################################################
- set_fact:
skip_tests: false
- block:
- name: Get servers certificate with backend auto-detection
get_certificate:
host: "{{ httpbin_host }}"
port: 443
ignore_errors: true
register: result
- set_fact:
skip_tests: |
{{
result is failed and (
'error: [Errno 1] _ssl.c:492: error:14094410:SSL routines:SSL3_READ_BYTES:sslv3 alert handshake failure' in result.msg
or
'error: _ssl.c:314: Invalid SSL protocol variant specified.' in result.msg
)
}}
- assert:
that:
- result is success or skip_tests
when: |
pyopenssl_version.stdout is version('0.15', '>=') or
(cryptography_version.stdout is version('1.6', '>=') and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6))
cryptography_version.stdout is version('1.6', '>=')
- block:
@@ -21,7 +40,7 @@
vars:
select_crypto_backend: pyopenssl
when: pyopenssl_version.stdout is version('0.15', '>=')
when: pyopenssl_version.stdout is version('0.15', '>=') and not skip_tests
- block:
@@ -32,6 +51,4 @@
# The module doesn't work with CentOS 6. Since the pyOpenSSL installed there is too old,
# we never noticed before. This becomes a problem with the new cryptography backend,
# since there is a new enough cryptography version...
when: |
cryptography_version.stdout is version('1.6', '>=') and
(ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)
when: cryptography_version.stdout is version('1.6', '>=') and not skip_tests

View File

@@ -20,6 +20,81 @@
valid_from: always
valid_to: forever
- block:
- name: Generate cert with updated signature algorithm
openssh_cert:
type: user
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
signature_algorithm: rsa-sha2-256
valid_from: always
valid_to: forever
register: updated_signature_algorithm
- name: Assert signature algorithm update causes change
assert:
that:
- updated_signature_algorithm is changed
- name: Generate cert with updated signature algorithm (idempotent)
openssh_cert:
type: user
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
signature_algorithm: rsa-sha2-256
valid_from: always
valid_to: forever
register: updated_signature_algorithm_idempotent
- name: Assert signature algorithm update is idempotent
assert:
that:
- updated_signature_algorithm_idempotent is not changed
- name: Generate cert with original signature algorithm
openssh_cert:
type: user
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
signature_algorithm: ssh-rsa
valid_from: always
valid_to: forever
register: second_signature_algorithm
- name: Assert second signature algorithm update causes change
assert:
that:
- second_signature_algorithm is changed
- name: Omit signature algorithm
openssh_cert:
type: user
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
valid_from: always
valid_to: forever
register: omitted_signature_algorithm
- name: Assert omitted_signature_algorithm does not cause change
assert:
that:
- omitted_signature_algorithm is not changed
- name: Revert to original certificate
openssh_cert:
type: user
path: "{{ certificate_path }}"
public_key: "{{ public_key }}"
signing_key: "{{ signing_key }}"
valid_from: always
valid_to: forever
regenerate: always
when: openssh_version is version("7.3", ">=")
- name: Generate cert with new signing key
openssh_cert:
type: user

View File

@@ -86,6 +86,27 @@
regenerate: full_idempotence
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
assert:
that:
@@ -95,6 +116,7 @@
- explicit_extension_after is not changed
- explicit_extension_and_directive is changed
- default_options is not changed
- host_cert_full_idempotence is not changed
- name: Remove certificate
openssh_cert:

View File

@@ -5,12 +5,18 @@
####################################################################
# Ensures no conflicts from previous test runs
- name: "({{ backend }}) Find old test artifacts"
ansible.builtin.find:
paths: "{{ remote_tmp_dir }}"
patterns:
- "regenerate*"
register: old_test_artifacts
- name: "({{ backend }}) Cleanup Output Directory"
ansible.builtin.file:
path: "{{ item }}"
path: "{{ item.path }}"
state: absent
with_fileglob:
- "{{ remote_tmp_dir }}/regenerate*"
loop: "{{ old_test_artifacts.files }}"
- name: "({{ backend }}) Regenerate - setup simple keys"
openssh_keypair:

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -2,3 +2,4 @@ dependencies:
- setup_openssl
- setup_pyopenssl
- setup_remote_tmp_dir
- prepare_jinja2_compat

View File

@@ -8,7 +8,7 @@
select_crypto_backend: '{{ select_crypto_backend }}'
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:
that:
- result.subject.organizationalUnitName == 'ACME Department'
@@ -16,6 +16,21 @@
- "['organizationalUnitName', 'ACME Department'] in result.subject_ordered"
- result.public_key_type == 'RSA'
- 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"
assert:
@@ -24,6 +39,10 @@
- result.authority_key_identifier == "44:55:66:77"
- result.authority_cert_issuer == expected_authority_cert_issuer
- 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:
expected_authority_cert_issuer:
- "DNS:ca.example.org"

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -303,11 +303,18 @@
size: '{{ default_rsa_key_size }}'
select_crypto_backend: '{{ select_crypto_backend }}'
register: privatekey_mode_1
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
stat:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
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)"
openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
@@ -316,13 +323,6 @@
select_crypto_backend: '{{ select_crypto_backend }}'
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)"
openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
@@ -331,11 +331,17 @@
size: '{{ default_rsa_key_size }}'
select_crypto_backend: '{{ select_crypto_backend }}'
register: privatekey_mode_3
- name: "({{ select_crypto_backend }}) Stat for privatekey_mode"
stat:
path: '{{ remote_tmp_dir }}/privatekey_mode.pem'
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:
- name: "({{ select_crypto_backend }}) Generate privatekey_fmt_1 - auto format"
openssl_privatekey:

View File

@@ -186,7 +186,7 @@
- privatekey_mode_2 is not changed
- privatekey_mode_3 is changed
- 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"
assert:

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -2,3 +2,4 @@ dependencies:
- setup_openssl
- setup_pyopenssl
- setup_remote_tmp_dir
- prepare_jinja2_compat

View File

@@ -1,2 +1,4 @@
context/controller
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -1,2 +1,3 @@
shippable/cloud/group1
shippable/posix/group1
destructive

View File

@@ -2,3 +2,4 @@ dependencies:
- setup_openssl
- setup_pyopenssl
- setup_remote_tmp_dir
- prepare_jinja2_compat

View File

@@ -1,3 +1,4 @@
shippable/cloud/group1
shippable/posix/group1
openssl_signature_info
destructive

View File

@@ -0,0 +1,135 @@
# Copyright 2007 Pallets
#
# 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.
#
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# 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.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from jinja2.filters import contextfilter
from jinja2.runtime import Undefined
from jinja2.exceptions import TemplateRuntimeError, FilterArgumentError
try:
from jinja2.nodes import EvalContext
HAS_EVALCONTEXT = True
except ImportError:
HAS_EVALCONTEXT = False
def call_test(environment, test_name, value, args, kwargs):
try:
return environment.call_test(test_name, value, args, kwargs)
except AttributeError:
# call_test was added together with selectattr...
func = environment.tests.get(test_name)
if func is None:
raise TemplateRuntimeError('no test named %r' % test_name)
return func(value, *args, **kwargs)
def call_filter(environment, name, value, args=None, kwargs=None,
context=None, eval_ctx=None):
func = environment.filters.get(name)
if func is None:
raise TemplateRuntimeError('no filter named %r' % name)
args = list(args or ())
if getattr(func, 'contextfilter', False):
if context is None:
raise TemplateRuntimeError('Attempted to invoke context filter without context')
args.insert(0, context)
elif getattr(func, 'evalcontextfilter', False):
if eval_ctx is None:
if context is not None:
eval_ctx = context.eval_ctx
elif HAS_EVALCONTEXT:
eval_ctx = EvalContext(environment)
else:
raise TemplateRuntimeError('Too old Jinja2 does not have EvalContext')
args.insert(0, eval_ctx)
elif getattr(func, 'environmentfilter', False):
args.insert(0, environment)
return func(value, *args, **(kwargs or {}))
def make_attrgetter(environment, attribute_str, default=None):
attributes = [int(attribute) if attribute.isdigit() else attribute for attribute in attribute_str.split(".")]
def f(item):
for attribute in attributes:
item = environment.getitem(item, attribute)
if default and isinstance(item, Undefined):
item = default
return item
return f
@contextfilter
def compatibility_selectattr_filter(context, sequence, attribute_str, test_name, *args, **kwargs):
f = make_attrgetter(context.environment, attribute_str)
for item in sequence:
if call_test(context.environment, test_name, f(item), args, kwargs):
yield item
def prepare_map(context, args, kwargs):
if len(args) == 0 and "attribute" in kwargs:
attribute = kwargs.pop("attribute")
default = kwargs.pop("default", None)
if kwargs:
raise FilterArgumentError("Unexpected keyword argument {0!r}".format(next(iter(kwargs))))
func = make_attrgetter(context.environment, attribute, default=default)
else:
try:
name = args[0]
args = args[1:]
except LookupError:
raise FilterArgumentError("map requires a filter argument")
def func(item):
return call_filter(context.environment, name, item, args, kwargs, context=context)
return func
@contextfilter
def compatibility_map_filter(context, seq, *args, **kwargs):
func = prepare_map(context, args, kwargs)
if seq:
for item in seq:
yield func(item)
class FilterModule:
''' Jinja2 compat filters '''
def filters(self):
return {
'selectattr': compatibility_selectattr_filter,
'map': compatibility_map_filter,
}

View File

@@ -0,0 +1 @@
---

View File

@@ -2,6 +2,10 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
def compatibility_equalto_test(a, b):
return a == b
def compatibility_in_test(a, b):
return a in b
@@ -11,5 +15,6 @@ class TestModule:
def tests(self):
return {
'equalto': compatibility_equalto_test,
'in': compatibility_in_test,
}

View File

@@ -1,3 +1,3 @@
dependencies:
# - setup_openssl
- setup_openssl
- setup_remote_tmp_dir

View File

@@ -4,19 +4,5 @@
# and should not be used as examples of how to write Ansible roles #
####################################################################
# BEGIN HACK: remove whenever we know how to properly detect 'default' docker container !!!!!!!!!!!!!!!!!!!!!
- name: Default value for OpenSSL binary path
set_fact:
openssl_binary: openssl
- name: Register openssl version
shell: "{{ openssl_binary }} version | cut -d' ' -f2"
register: openssl_version
- name: Register cryptography version
command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'"
register: cryptography_version
# END HACK !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- debug:
msg: "ACME test container IP is {{ acme_host }}; OpenSSL version is {{ openssl_version.stdout }}; cryptography version is {{ cryptography_version.stdout }}"

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