Compare commits

..

441 Commits

Author SHA1 Message Date
Felix Fontein
f7c0a85c72 Release 2.21.0. 2024-07-12 22:17:24 +02:00
Felix Fontein
8935ab8fdc Reformat and re-order changelogs/changelog.yaml. 2024-07-11 22:44:23 +02:00
Felix Fontein
1f39b0ff2a Add missing changelog for #784. 2024-07-11 22:35:47 +02:00
G Derber
b02fb8e9a0 certificate_complete_chain: add ability to identify ed25519 complete chains (#777)
* Add ability to identify ed25519 complete chains.

* Add ability to identify ed448 complete chains.

* Formatting updates

* Remove unnecessary imports.

* Cleanup whitespace

* Fix algorithm names capitalization.
2024-07-11 22:25:16 +02:00
Felix Fontein
d50c3cc944 get_certificate: add get_certificate_chain option (#784)
* Implement get_certificate_chain option.

* Implement basic tests.

* Add compatibility for current Python 3.13 pre-releases.
2024-07-10 21:51:30 +02:00
Felix Fontein
4c26fada5e Polish docs. (#783) 2024-07-10 00:20:24 +02:00
Felix Fontein
d13d1868b6 Remove EOL'ed FreeBSD 13.2 from CI. (#781)
Apparently the packages are no longer available.
2024-07-08 22:44:14 +02:00
dependabot[bot]
6a0953b19f Bump fsfe/reuse-action from 3 to 4 (#780)
Bumps [fsfe/reuse-action](https://github.com/fsfe/reuse-action) from 3 to 4.
- [Release notes](https://github.com/fsfe/reuse-action/releases)
- [Commits](https://github.com/fsfe/reuse-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: fsfe/reuse-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 07:53:36 +02:00
dlehrman
6ba06f24ce Enable TLS/SSL CTX Options for the get_certificate Module (#779)
* Enable SSL CTX options for get_certificate

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Support both str and int SSL CTX options, override defaults

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Add changelog fragment

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Resolve doc builder error

ssl_ctx_options can be a mix of str and int, but `elements: [ str, int ]` made the Ansible doc builder angry.

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Set ssl_ctx_options version_added

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Initial application of suggestions from code review

Working on completing application of suggestions

Co-authored-by: Felix Fontein <felix@fontein.de>

* Finish applying suggestions from code review

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

* Documentation update

Co-authored-by: Felix Fontein <felix@fontein.de>

* Include value in fail output for wrong data type

Co-authored-by: Felix Fontein <felix@fontein.de>

* Handle invalid tls_ctx_option strings

Co-authored-by: Felix Fontein <felix@fontein.de>

* Minor documentation update

Signed-off-by: David Ehrman <dlehrman@liberty.edu>

---------

Signed-off-by: David Ehrman <dlehrman@liberty.edu>
Co-authored-by: Felix Fontein <felix@fontein.de>
2024-07-07 21:48:48 +02:00
Felix Fontein
577d86265e Prepare 2.21.0 release. 2024-07-07 20:11:55 +02:00
Felix Fontein
1c1b59b719 Add link to forum. (#778) 2024-07-05 22:33:00 +02:00
Felix Fontein
518847a92c CI: DSA SSH keys are no longer supported with OpenSSH 9.8p1 (#776)
* DSA SSH keys are no longer supported with OpenSSH 9.8p1.

* Add more compatibility tests.
2024-07-04 10:15:22 +02:00
Felix Fontein
aa30b4c803 Fix CI for CentOS 7. (#774) 2024-07-01 13:56:23 +02:00
Felix Fontein
a9dab608c7 Adjust docs publishing workflow. (#773)
Ref: https://github.com/ansible-community/github-docs-build/issues/92
2024-06-29 17:23:11 +02:00
Felix Fontein
e6643fd2dd Replace FreeBSD 14.0 with 14.1; add 14.0 for stable-2.17. (#772) 2024-06-21 21:38:19 +02:00
Felix Fontein
f58606b64d Add Python 3.13 to CI. (#768) 2024-06-18 23:08:54 +02:00
Felix Fontein
5e60bee9c0 Adjust CI matrix for ansible-core devel's ansible-test (#771)
* Adjust CI matrix for ansible-core devel's ansible-test.

* Don't install cryptography via pip on Ubuntu 24.04.

* Don't force-enable on Fedora.
2024-06-18 08:20:43 +02:00
Felix Fontein
33410b1d57 Removing Fedora 31 and 32 from CI. These images seem to no longer work. 2024-06-15 14:01:44 +02:00
Felix Fontein
e365ae3226 Use 2.9/2.10/2.11 from ansible-community/eol-ansible repo. (#769) 2024-06-15 13:49:41 +02:00
Felix Fontein
5f6e0095b0 Fix unit tests. (#767) 2024-06-13 21:33:36 +02:00
Felix Fontein
dc052bee21 Bump Azure test container to 6.0.0. (#764) 2024-06-10 20:41:22 +02:00
Felix Fontein
38849514f3 Stop building EE with CentOS Stream 8, which no longer has builds. (#763) 2024-06-04 07:40:46 +02:00
Felix Fontein
7810e2c3bf Remove usage of old ACME test container. (#760) 2024-05-20 16:11:35 +02:00
Felix Fontein
5d4cbbb038 The next expected release will be 2.21.0. 2024-05-20 12:15:59 +02:00
Felix Fontein
58a81374d6 Release 2.20.0. 2024-05-20 11:30:21 +02:00
Felix Fontein
c29c34bab2 Prepare 2.20.0. 2024-05-20 11:26:23 +02:00
Felix Fontein
b4452d4be1 From now on automatically add period to new plugins in changelog, and use FQCNs. (#759) 2024-05-20 08:44:11 +02:00
Felix Fontein
7fc3ad0263 Make sure the ACME inspect tests run with both backends. (#758) 2024-05-12 15:29:07 +02:00
Felix Fontein
65ea02a73d Pass codecov token to ansible-test-gh-action. (#755) 2024-05-11 21:29:25 +02:00
Felix Fontein
00d23753ca Revert "Revert all non-bugfixes merged since the last release."
This reverts commit 82251c2d80.
2024-05-11 17:05:03 +02:00
Felix Fontein
3d8c68e189 Next planned release is 2.20.0. 2024-05-11 17:05:03 +02:00
Felix Fontein
d7a0723a52 Release 2.19.1. 2024-05-11 16:43:18 +02:00
Felix Fontein
67bf3a7991 Prepare 2.19.1 bugfix release. 2024-05-11 16:10:21 +02:00
Felix Fontein
82251c2d80 Revert all non-bugfixes merged since the last release.
Revert "Fix documentation. (#751)"
Revert "ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)"
Revert "Refactor and extend argument spec helper, use for ACME modules (#749)"
Revert "Avoid exception if certificate has no AKI in acme_certificate. (#748)"
Revert "ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)"
Revert "Add acme_certificate_renewal_info module (#746)"
Revert "Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)"
Revert "Add tests for acme_certificate_deactivate_authz module. (#744)"
Revert "Create acme_certificate_deactivate_authz module (#741)"
Revert "acme_certificate: allow to request renewal of a certificate according to ARI (#739)"
Revert "Implement basic acme_ari_info module. (#732)"
Revert "Add function for retrieval of ARI information. (#738)"
Revert "acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)"
Revert "Implement certificate information retrieval code in the ACME backends. (#736)"
Revert "Split up the default acme docs fragment to allow modules ot not need account data. (#735)"

This reverts commits 5e59c5261e, aa82575a78,
f3c9cb7a8a, f82b335916, 553ab45f46,
59606d48ad, 0a15be1017, 9501a28a93,
d906914737, 33d278ad8f, 6d4fc589ae,
9614b09f7a, af5f4b57f8, c6fbe58382,
and afe7f7522c.
2024-05-11 16:07:53 +02:00
Felix Fontein
f43fa94549 x509_certificate: fix time idempotence (#754)
* Fix time idempotence.

* Lint and add changelog fragment.

* Add tests.

* Make sure 'ignore_timestamps: false' is passed for time idempotence tests; pass right private key for OwnCA tests
2024-05-11 16:04:41 +02:00
francescolovecchio
29ac3cbe81 ecs_certificate: allow to request renewal without csr (#740)
* renew request CSR validation

* Create 740-ecs_certificate-renewal-without-csr

* Rename 740-ecs_certificate-renewal-without-csr to 740-ecs_certificate-renewal-without-csr.yml

---------

Co-authored-by: flovecchio <flovecchio@sorint.com>
2024-05-09 20:24:48 +02:00
Felix Fontein
5e59c5261e Fix documentation. (#751) 2024-05-05 19:57:32 +02:00
Felix Fontein
aa82575a78 ACME modules: simplify code, refactor argspec handling code, move csr/csr_content to own docs fragment (#750)
* Fix bug in argspec module util.

* Move csr / csr_content to new docs fragment.

* Simplify code.

* Refactor ACME argspec creation. Add with_certificate argument for new CERTIFICATE docs fragment.
2024-05-05 14:37:52 +02:00
Felix Fontein
f3c9cb7a8a Refactor and extend argument spec helper, use for ACME modules (#749)
* Refactor argument spec helper.

* Remove superfluous comments.
2024-05-05 09:42:42 +00:00
Felix Fontein
f82b335916 Avoid exception if certificate has no AKI in acme_certificate. (#748)
Shouldn't happen since CA-issued certs should always have AKI,
but better be safe than sorry.
2024-05-05 09:43:29 +02:00
Felix Fontein
553ab45f46 ACME: improve acme_certificate docs, include cert_id in acme_certificate_renewal_info return value (#747)
* Use community.dns.quote_txt filter instead of regex replace to quote TXT entry value.

* Fix documentation of acme_certificate's challenge_data return value.

* Also return cert_id from acme_certificate_renewal_info module.

* The cert ID cannot be computed if the certificate has no AKI.

This happens with older Pebble versions, which are used when
testing against older ansible-core/-base/Ansible versions.

* Fix AKI extraction for older OpenSSL versions.
2024-05-04 23:38:57 +02:00
Felix Fontein
59606d48ad Add acme_certificate_renewal_info module (#746)
* Allow to provide cert_info object to get_renewal_info().

* Add acme_certificate_renewal_info module.

* Allow to provide value for 'now'.

* Actually append msg_append.

* Fix bug in module timestamp param parsing, and add tests.
2024-05-04 15:47:42 +02:00
Felix Fontein
0a15be1017 Refactor time code, add tests, fix bug when parsing absolute timestamps that omit seconds (#745)
* Add time module utils.

* Add time helpers to ACME backend.

* Add changelog fragment.

* ACME timestamp parser: do not choke on nanoseconds.
2024-05-03 22:25:39 +02:00
Felix Fontein
9501a28a93 Add tests for acme_certificate_deactivate_authz module. (#744) 2024-05-01 11:30:07 +02:00
Felix Fontein
d906914737 Create acme_certificate_deactivate_authz module (#741)
* Create acme_certificate_deactivate_authz module.

* Add ACME version check.
2024-05-01 10:32:03 +02:00
Felix Fontein
33d278ad8f acme_certificate: allow to request renewal of a certificate according to ARI (#739)
* Allow to request renewal of a certificate according to ARI in acme_certificate.

* Improve docs.

* Fix typo and use right object.

* Add warning.
2024-04-30 10:47:49 +02:00
Felix Fontein
6d4fc589ae Implement basic acme_ari_info module. (#732) 2024-04-30 08:47:24 +02:00
Felix Fontein
9614b09f7a Add function for retrieval of ARI information. (#738) 2024-04-29 23:37:55 +02:00
Felix Fontein
af5f4b57f8 acme module utils: add functions for parsing Retry-After header values and computation of ARI certificate IDs (#737)
* Implement Retry-After value parse.

* Add cert ID computation function.

* Add tests and links to MDN.
2024-04-29 23:06:35 +02:00
Felix Fontein
c6fbe58382 Implement certificate information retrieval code in the ACME backends. (#736) 2024-04-29 22:29:43 +02:00
Felix Fontein
afe7f7522c Split up the default acme docs fragment to allow modules ot not need account data. (#735) 2024-04-29 22:22:38 +02:00
Felix Fontein
0c62837296 crypto.math module utils: add some tests, fix quick_is_not_prime() for small primes (#733)
* Fix quick_is_not_prime() for small primes. Add some tests.

* Fix return value of convert_int_to_bytes(0, 0) on Python 2.

* Add some more test cases.

* Simplify the changelog and point out that these errors only happen for cases not happening in regular use.
2024-04-29 08:50:28 +02:00
Felix Fontein
d71637c77d Arch Linux switched to Python 3.12. (#731) 2024-04-28 15:20:03 +00:00
Felix Fontein
3899f79f97 Next expected release will be 2.20.0. 2024-04-20 12:06:08 +02:00
Felix Fontein
8ce0051f9b Release 2.19.0. 2024-04-20 11:48:34 +02:00
Felix Fontein
4be691da50 Include changelog in docsite. (#729) 2024-04-18 12:22:34 +02:00
Felix Fontein
8fe012cf09 Prepare 2.19.0 release. 2024-04-18 07:51:28 +02:00
Felix Fontein
27a9ff14fb Add x509_certificate_convert module. (#728) 2024-04-18 05:50:36 +00:00
Felix Fontein
ae548de502 Use timezone aware functionality when using cryptography >= 42.0.0 (#727)
* Use timezone aware functionality when using cryptography >= 42.0.0.

* Adjust OpenSSH certificate code to avoid functions deprecated in Python 3.12.

* Strip timezone info from isoformat() output.

* InvalidityDate.invalidity_date currently has no _utc variant.
2024-04-18 05:49:53 +00:00
Felix Fontein
1b75f1aa9c Add and use CryptoBackend.get_ordered_csr_identifiers(). (#725) 2024-04-13 22:43:14 +02:00
Felix Fontein
7e33398d5c ansible-core devel dropped support for Python 3.7. (#722) 2024-04-05 07:49:15 +02:00
Felix Fontein
50c2c4db29 CI: Add stable-2.17; copy ignore.txt files from 2.17 to 2.18; move stable-2.14 from AZP to GHA (#721)
* Add stable-2.17 to CI; copy ignore files from 2.17 to 2.18.

* Move stable-2.14 from AZP to GHA.
2024-04-03 08:32:16 +02:00
Felix Fontein
ee0ceea118 Move Alpine 3.18 docker to stable-2.16, add Alpine 3.19 docker, bump Alpine VM to 3.19. (#720) 2024-03-22 12:48:40 +01:00
Felix Fontein
b98cec74ae Add FreeBSD 13.3 and 14.0 for devel, move FreeBSD 13.2 to stable-2.16. (#719) 2024-03-21 21:58:37 +01:00
Felix Fontein
05cc5fe82b Add macOS 14.3 for devel, move 13.2 to stable-2.16. (#718) 2024-03-12 08:02:23 +01:00
dependabot[bot]
fad3c1352b Bump fsfe/reuse-action from 2 to 3 (#717)
Bumps [fsfe/reuse-action](https://github.com/fsfe/reuse-action) from 2 to 3.
- [Release notes](https://github.com/fsfe/reuse-action/releases)
- [Commits](https://github.com/fsfe/reuse-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: fsfe/reuse-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-04 06:23:55 +01:00
Felix Fontein
4167d2c4b3 Next expected release will be 2.19.0. 2024-02-25 21:32:06 +01:00
Felix Fontein
ff1504dc58 Release 2.18.0. 2024-02-25 20:57:38 +01:00
Felix Fontein
08adb6b297 Deprecate check mode behavior of pipe modules. (#714) 2024-02-25 17:00:37 +01:00
Felix Fontein
42ba0a88f4 Prepare 2.18.0. 2024-02-23 20:07:06 +01:00
Felix Fontein
1736602ce7 Allow to configure how serial numbers are provided to x509_crl. (#715) 2024-02-19 21:05:13 +01:00
Felix Fontein
6b1a3d6e68 Add conversion filters for serial numbers (#713)
* Refactoring.

* Add parse_filter and to_filter plugins.

* Mention filters when serial numbers are accepted or returned.
2024-02-18 21:27:48 +01:00
Steffen Gufler
51591891d3 luks_device: fix remove_keyslot not working when set to 0 and duplicate keys (#710)
* luks_device: fix remove_keyslot not working when set to 0

* luks_device: fix module outputting 'ok' when trying to add a key that is already present in another keyslot

* luks_device: fix breaking unit tests

* luks_device: Duplicate key test case code cleanup

* luks_device: Fix testing of LUKS passphrases when only testing one key slot

* luks_device: Fix testing of LUKS passphrases when only testing one key slot

* luks_device: Add changelog fragment for PR #710

* luks_device: Update changlog fragment
2024-02-11 12:23:21 +01:00
Felix Fontein
d1a229c255 Add MarkDown changelog and use it by default. (#708) 2024-02-09 13:08:12 +01:00
Felix Fontein
d9698a6eff Next expected release is 2.18.0. 2024-01-27 12:47:38 +01:00
Felix Fontein
37fed289e6 Release 2.17.1. 2024-01-27 10:44:08 +01:00
Felix Fontein
9ec8680936 Emit warning when consistency cannot be checked. (#705) 2024-01-27 10:39:13 +01:00
Felix Fontein
87af1f2761 Disable consistency checking of RSA keys for cryptography 42.0.0 which no longer gives access to the required function. (#702) 2024-01-26 17:47:46 +01:00
Felix Fontein
da30487119 Prepare 2.17.1 release. 2024-01-25 23:52:22 +01:00
Felix Fontein
b57aa4a2ca Fix openssl_dhparam. (#698) 2024-01-25 23:42:03 +01:00
Felix Fontein
a5f5ea1128 Next expected release is 2.18.0. 2024-01-21 09:29:10 +01:00
Felix Fontein
91dd7cd4dc Release 2.17.0. 2024-01-21 09:03:37 +01:00
Felix Fontein
2913826352 Prepare 2.17.0 release. 2024-01-21 08:46:32 +01:00
Felix Fontein
0bc15598d7 Simplifiy workflows. (#696) 2024-01-17 23:14:53 +01:00
Felix Fontein
fb3f68ca96 Use import galaxy workflow from https://github.com/ansible-collections/community.docker/pull/754. (#694) 2024-01-13 17:08:03 +01:00
0x00ace
a4edf22a9c add allow discard option for luks devices (#693)
* add allow discard option for luks devices

* Add allow_discards to perfomance tests

* Fix version for luks devices doc

* Update plugins/modules/luks_device.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* add changelog fragment

* Update changelogs/fragments/693-allow-discards.yaml

Co-authored-by: Felix Fontein <felix@fontein.de>

* added allow_discards to the persistently stored option list

* allow_discards works with not only luks2 containers

* Update plugins/modules/luks_device.py

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
2024-01-13 09:34:07 +01:00
Felix Fontein
97e44c4ba5 Remove some Shippable specific code that trips latest shellcheck. (#692) 2024-01-04 22:46:46 +01:00
Felix Fontein
453adb5d04 Remove FreeBSD 12.4 from CI. (#690) 2023-12-31 13:51:54 +00:00
Felix Fontein
033b456b7a Add new error message. (#688) 2023-12-20 13:37:19 +01:00
dependabot[bot]
73dbb84fc6 Bump actions/setup-python from 4 to 5 (#686)
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 5.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](https://github.com/actions/setup-python/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 06:56:23 +01:00
Felix Fontein
780fb28946 Next expected release is 2.17.0. 2023-12-09 11:24:42 +01:00
Felix Fontein
815ce43d17 Release 2.16.2. 2023-12-09 11:03:32 +01:00
Felix Fontein
170d837122 Increase retry count from 5 to 10. (#685) 2023-12-08 21:36:20 +01:00
Felix Fontein
b5269b25a3 Improve error reporting. (#684) 2023-12-08 20:57:49 +01:00
Felix Fontein
f12e814344 Deactivate FreeBSD 13.1 in CI. (#683) 2023-12-07 22:50:33 +01:00
Felix Fontein
5d5a21fddf Directly handle unexpected non-JSON results. (#682) 2023-12-07 22:26:04 +01:00
Felix Fontein
67f1d1129b Fix handling of non-existing ACME accounts with Digicert ACME endpoint (#681)
* Compatibility for DigiCert CA: also accept 404 instead of 400 for non-existing accounts.

* Add changelog fragment.

* Fix URL.
2023-12-07 22:25:54 +01:00
Felix Fontein
d9362a2ce9 Prepare 2.16.2 release. 2023-12-07 21:08:34 +01:00
Felix Fontein
4e5966e477 Next expected release is 2.17.0. 2023-12-04 22:52:42 +01:00
Felix Fontein
22e24f24c6 Release 2.16.1. 2023-12-04 21:49:56 +01:00
Felix Fontein
35b47f73f4 Fix version in galaxy.yml to 2.16.1. 2023-12-04 21:49:44 +01:00
Felix Fontein
9cc1731767 Revert "Release 2.17.0."
This reverts commit c592eaa35a.
2023-12-04 21:49:29 +01:00
Felix Fontein
c592eaa35a Release 2.17.0. 2023-12-04 21:49:01 +01:00
Felix Fontein
525a8a5df4 Prepare 2.16.1. 2023-12-04 21:35:41 +01:00
Felix Fontein
e4ba0861e5 Retry also on certain connection errors. (#680) 2023-12-04 21:34:51 +01:00
Felix Fontein
29cd0b3bde Fix bad expressions in tests. (#677)
ci_complete
2023-11-28 22:57:45 +01:00
Felix Fontein
f2ebae635a Remove Fedora 36 from CI. (#676) 2023-11-24 21:21:14 +01:00
Felix Fontein
75934cdd8c devel supports Fedora 39, and no longer Fedora 38. (#674) 2023-11-17 21:29:45 +01:00
Felix Fontein
cf1fe027dd Add rhel/9.3 for devel, remove rhel/9.2. (#673) 2023-11-15 21:55:20 +01:00
Felix Fontein
e9dbc1a5a5 Next release is expected to be 2.17.0. 2023-10-29 16:17:00 +01:00
Felix Fontein
6bd5eee9b0 Release 2.16.0. 2023-10-29 15:59:31 +01:00
Felix Fontein
fc707c7e31 Add changelog fragment for #664. 2023-10-29 10:55:12 +01:00
Felix Fontein
eba7e32df1 Due to a new feature, the next release will be 2.16.0. 2023-10-29 10:53:53 +01:00
Steffen Gufler
6504e67139 luks_device: add support for keyslots (#664)
* luks_device: add support for keyslots

* luks_device: replace python3 format strings with python2 format strings, remove print statements

* luks_device: add missing copyright information in keyslot integration test files

* luks_device: updated failing unit tests for keyslot support

* luks_device: improve detection of luks version

* luks_device: Update documentation on keyslot parameters, minor code improvements

* luks_device: improve validation of keyslot parameters, fix tests for systems that do not support luks2

* luks_device: correct spelling and errors in documentation and output, check all possible locations for LUKS2 header
2023-10-29 10:53:00 +01:00
Felix Fontein
428550165a Fix typos and FQCN (#669)
* Fix typos.

* Use FQCNs in examples.
2023-10-28 22:54:56 +02:00
Felix Fontein
a150e77507 Prepare 2.15.2 release. 2023-10-28 22:14:10 +02:00
Felix Fontein
d1299c11d6 Handle pyOpenSSL 23.3.0, which removed PKCS#12 support (at least partially). (#666) 2023-10-28 13:38:07 +00:00
Felix Fontein
fccc9d32ee macOS in CI seems to be very unreliable or even totally dead. (#665) 2023-10-22 18:05:21 +02:00
Felix Fontein
d63c195bff Emphasize that openssl_publickey doesn't support OpenSSH private keys. (#663) 2023-10-07 15:21:09 +02:00
Felix Fontein
e7515584b1 Latest OpenSSH's ssh-keygen defaults to ed25519 keys, no longer RSA. (#662) 2023-10-07 15:15:33 +02:00
Felix Fontein
0d010968e5 ansible-core devel drops support for Python 2.7 and 3.6. (#660) 2023-10-04 08:22:33 +02:00
Felix Fontein
5f4fc95c50 Fix Galaxy URLs. (#658) 2023-09-30 21:30:36 +02:00
Felix Fontein
b2a92ef0bf Add ansible-core 2.16 to the matrix. (#656) 2023-09-19 17:51:29 +02:00
dependabot[bot]
01cdc4a572 Bump actions/checkout from 3 to 4 (#655)
Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-11 06:00:41 +02:00
Felix Fontein
cdfc881b32 Next expected release is 2.16.0. 2023-08-22 17:16:26 +02:00
Felix Fontein
d7293aa1cd Release 2.15.1. 2023-08-22 06:54:54 +02:00
Felix Fontein
1e78918ad3 Prepare 2.15.1 release. 2023-08-21 20:51:04 +02:00
Felix Fontein
526b3c4393 Allow type to be missing. (#652) 2023-08-21 20:49:55 +02:00
Felix Fontein
5d2bfddc15 FreeBSD 13.0 and 12.3 are no longer availabe, bump versions and disable since these versions are already tested with stable-2.15. (#649) 2023-08-13 19:19:30 +02:00
Felix Fontein
5ac603bbcc Next expected release is 2.16.0. 2023-08-12 19:48:40 +02:00
Felix Fontein
e41a50af97 Release 2.15.0. 2023-08-12 18:10:13 +02:00
Felix Fontein
d3737f5ef7 Update release summary. 2023-08-12 17:15:09 +02:00
Felix Fontein
addbd067c8 openssh_* modules: check return code on ssh(-keygen) invocations; fail if comment cannot be updated (#646)
* Check return code on ssh(-keygen) invocations.

* openssh_cert: only check for errors if certificate should be present and module is not in check mode.

* Handle rc check for _get_private_key().

* Add changelog fragment.

* Only pass -o for comment updating when necessary.

* Now fails if comment cannot be updated.

This was silently ignored in the past.

* Avoid failing operation.
2023-08-12 17:14:00 +02:00
Felix Fontein
62c842548d Deprecate the default value 'false' of asn1_base64. (#600) 2023-08-12 12:23:37 +02:00
Kloppi313
5526fcac27 Update openssl_privatekey.py (#644)
added example for ECC
2023-08-08 13:40:24 +02:00
Felix Fontein
55c94eb5c0 Update content list in README. (#643) 2023-08-02 12:00:25 +02:00
Felix Fontein
e64d617de6 Prepare 2.15.0 release. 2023-08-02 11:23:22 +02:00
Felix Fontein
ba456c5eaf Add gpg_fingerprint lookup and filter (#639)
* Add gpg_fingerprint lookup.

* Work around problems on some CI targets.

* Use get_bin_path to find the gpg executable. Document that we need it.

* Improve and test error handling.

* Refactor (potentially) common code to module_utils and plugin_utils.

This will be useful to create a filter version of this, and further lookups, filters, and modules.

* Do not create a keyring when there isn't one.

* Fixups.

* Fix description.

* More fixes for lookup.

* Also add a gpg_fingerprint filter.

* Improve formulation.

Co-authored-by: Sandra McCann <samccann@redhat.com>

---------

Co-authored-by: Sandra McCann <samccann@redhat.com>
2023-08-02 11:16:34 +02:00
Felix Fontein
5e630ffe78 CI: ansible-core devel only supports Alpine 3.18 VMs, no longer Alpine 3.17 VMs (#642)
* ansible-core devel only supports Alpine 3.18 VMs, no longer Alpine 3.17 VMs.

* lsblk was moved to a separate package in Alpine 3.18.
2023-08-02 11:15:54 +02:00
Felix Fontein
9ae75d4840 Fix license disclaimer for some vendored Jinja2 code in tests. (#640) 2023-07-26 17:45:24 +02:00
Felix Fontein
78eeb1219a Move FreeBSD 12.4 from ansible-core devel to stable-2.15. (#641) 2023-07-20 20:35:42 +02:00
Felix Fontein
54b2163c56 Remove no longer needed ignore. (#638) 2023-07-15 12:40:58 +02:00
Felix Fontein
1ca0d2f21d Install and use Python 3.11 on RHEL UBI 9. (#637) 2023-07-12 19:24:51 +02:00
Felix Fontein
2a789f8b01 Disable EE with ansible-core devel for now until UBI 9 has Python 3.10 support. (#636) 2023-07-12 08:12:46 +02:00
Felix Fontein
cffba005f0 Next expected release is 2.15.0. 2023-06-27 21:21:08 +02:00
Felix Fontein
6c72734652 Release 2.14.1. 2023-06-27 18:03:55 +02:00
Felix Fontein
83af72a3bc Improve PEM identification. (#628) 2023-06-27 17:35:55 +02:00
Felix Fontein
ed6285e083 Remove Fedora 37 from devel; add Fedora 38. (#633) 2023-06-26 22:36:04 +02:00
Felix Fontein
57a8c7e652 Add Debian Bookworm to CI. (#631) 2023-06-24 16:29:21 +02:00
Felix Fontein
b40a1c54f7 Bump AZP container. (#629) 2023-06-24 12:14:01 +02:00
Felix Fontein
8fa4dc75c9 Prepare 2.14.1. 2023-06-24 10:02:16 +02:00
Felix Fontein
99d1521266 Use semantic markup (#626)
* Enable semantic markup.

* Use semantic markup.

* Break long lines.

* Add ignores.

* Use real option, not alias.
2023-06-24 10:00:56 +02:00
Felix Fontein
c78536dfeb Support for Ubuntu 20.04 VM was removed. (#625) 2023-06-21 22:36:23 +02:00
Felix Fontein
288dc5be2c Update README. 2023-06-19 23:19:04 +02:00
Felix Fontein
9ae28e2fab Add RHEL 8.7, 8.8, and 9.2 to CI. (#624) 2023-06-19 22:50:07 +02:00
Felix Fontein
f27b66baa3 Ubuntu 20.02 VM is being removed from ansible-core devel. (#623) 2023-06-16 06:16:34 +02:00
Felix Fontein
230f0b51f2 Next expected release is 2.15.0. 2023-06-15 13:34:02 +02:00
Felix Fontein
1f84d0a317 Release 2.14.0. 2023-06-15 12:52:42 +02:00
Felix Fontein
2f64d42855 Adjust release summary. 2023-06-15 12:52:15 +02:00
Marcin Słowikowski
9c07a8354e Added support for certificates in DER format for x509_certificate_info module (#622)
* Added support for DER format

* Updated description

* Adjusted description

The content of the certificate cannot be in DER format due to an input encoding problem in the Ansible module, but it works fine when reading the certificate from a file

* Update support.py

* Added der_support_enabled flag for DER-format support

* Added changelog fragment for #603

* Fixed typo

* Fixed missing import

* Resolved issues found by static code analysis

* Update plugins/module_utils/crypto/support.py

Committed suggested change

Co-authored-by: Felix Fontein <felix@fontein.de>

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-06-15 12:51:14 +02:00
Felix Fontein
a7e9bb7618 Fix example. (#620) 2023-06-09 07:30:35 +02:00
Felix Fontein
ad118bbbd6 Prepare 2.14.0 release. 2023-06-09 06:10:06 +02:00
Felix Fontein
d823382732 Validate challenges in parallel instead of serially. (#617) 2023-06-09 06:04:34 +02:00
Felix Fontein
3a5d9129b2 ansible-core devel drops support for Python 3.5. (#618) 2023-06-06 21:28:59 +02:00
Felix Fontein
17702d1a76 acme_certificate: allow 'no challenge' (#615)
* Allow 'no challenge'.

* Fix undefined variable.
2023-06-05 20:54:07 +02:00
Felix Fontein
9305bfe190 Fix typo. (#616) 2023-06-04 20:12:35 +02:00
Felix Fontein
0d30a3793a Move ansible-core 2.12 to EOL CI (#609)
* https://github.com/ansible/ansible/pull/79734 has been merged and backported for all branches but stable-2.10 and stable-2.11.

* Move ansible-core 2.12 to EOL CI.
2023-05-29 17:01:04 +02:00
Felix Fontein
a402c485a3 Next expected release is 2.14.0. 2023-05-21 14:36:32 +02:00
Felix Fontein
05ad2e5008 Release 2.13.1. 2023-05-21 14:12:40 +02:00
Felix Fontein
e3bc22f7d5 Switch to Ansible Galaxy compatible requirements files for tests. (#607) 2023-05-21 13:33:19 +02:00
Felix Fontein
c703dd6056 Rewrite EE test workflows to use ansible-builder 3.0.0; fix EE dependencies (#606)
* Adjust EE tests to ansible-builder 3.0.0.

* Remove other CI workflows.

* Use docker instead of podman...

* Support Rocky Linux 9+.

* Add CentOS Stream 9 to EE tests.

* Fix installation of PyOpenSSL on CentOS/RHEL/Rocky.

* ansible-builder only attempts to install EPEL deps on CentOS.

* Make EPEL also available on Rocky Linux 9, even though ansible-builder will ignore it.

* Make sure cryptography is already installed.

* Try ansible-runner < 2.0.0 for CentOS Stream 8 / RHEL 8.

* Show more info.

* Start restricting transitive dependencies...

* Looks like PyOpenSSL is **broken** on CentOS Stream 9 + EPEL.

* ansible-builder will NOT work with Python 3.6.

use Python 3.9 on RHEL8 / CentOS Stream 8. Manually install cryptography and PyOpenSSL for Python 3.9 as well.

* PyOpenSSL isn't available for Python 3.8 or 3.9.

* Revert "Remove other CI workflows."

This reverts commit 3a9d125f45.

* Use podman instead of docker.

* Re-order bindep entries.

* python3-pyOpenSSL does not exist on RHEL/CentOS 6 and 7.
2023-05-21 12:43:14 +02:00
Felix Fontein
153de3ffef Prepare 2.13.1. 2023-05-21 08:39:06 +02:00
Felix Fontein
3bcc0db4fc Improve examples: use FQCNs and always add name: to tasks (#604)
* Improve examples: use FQCNs and always add name: to tasks.

* Improve formulation.

Co-authored-by: Don Naro <dnaro@redhat.com>

* Accidentally added a period.

---------

Co-authored-by: Don Naro <dnaro@redhat.com>
2023-05-15 21:41:18 +02:00
Felix Fontein
142403c6cb Arch Linux now uses Python 3.11. (#602) 2023-05-04 07:12:16 +02:00
Felix Fontein
a2d4554c78 Add FreeBSD 13.2, drop FreeBSD 12.2. (#601) 2023-05-03 22:04:40 +02:00
Felix Fontein
a89fd2733b Next expected release is 2.14.0. 2023-05-01 22:01:33 +02:00
Felix Fontein
39bba05a17 Release 2.13.0. 2023-05-01 21:27:25 +02:00
Felix Fontein
a8f27f93b7 Prepare 2.13.0. 2023-05-01 21:18:46 +02:00
David Zaslavsky
ce3299f106 Always generate a new key pair if the private key doesn't exist (#598)
* Always generate a new key pair if the private key doesn't exist (#597)

This commit updates `KeypairBackend._should_generate()` to first check
if the original private key named by the `path` argument exists, and
return True if it does not. This brings the code in line with
the documentation, which says that a new key will always be generated if
the key file doesn't already exist.

As an alternative to the approach implemented here, I also considered
only modifying the condition in the `fail` branch of the if statement,
but I thought that would not map as cleanly to the behavior specified in
the documentation, so doing it the way I did should make it easier to
check that the code is doing the right thing just by looking at it.
I also considered doing something to make the logic more similar to
`PrivateKeyBackend.needs_regeneration()` (the openssl version of this
functionality), because the two are supposed to be acting the same way,
but I thought that'd be going beyond the scope of just fixing this bug.
If it'd be useful to make both methods work the same way, someone can
refactor the code in a future commit.

* Test different regenerate values with nonexistent keys

This commit changes the test task that generates new keys to use each of
the different values for the `regenerate` argument, which will ensure
that the module is capable of generating a key when no previous key
exists regardless of the value of `regenerate`. Previously, the task
would always run with the `partial_idempotence` value, and that obscured
a bug (#597) that would occur when it was set to `fail`. The bug was
fixed in the previous commit.
2023-05-01 21:16:42 +02:00
Felix Fontein
c568923478 x509_crl: prepare releasing the mode option for AnsibleModule's use (#596)
* Prepare releasing the mode option for AnsibleModule's use.

* Update docs.
2023-04-29 20:54:24 +02:00
Felix Fontein
54eeb8d563 Next expected release is 2.13.0. 2023-04-16 20:14:03 +02:00
Felix Fontein
e6a0d2884a Release 2.12.0. 2023-04-16 19:48:15 +02:00
Felix Fontein
ceabef7e58 Do extra docs validation; explicitly disallow semantic markup in docs (#593)
* Do extra docs validation. Explicitly disallow semantic markup in docs.

* Forgot to add new requirement.

* Improve test.

* TEMP - make CI fail.

* Revert "TEMP - make CI fail."

This reverts commit a71b8901c1.

* Remove unnecessary import.

* Make sure ANSIBLE_COLLECTIONS_PATH is set.

* Make sure sanity tests from older Ansible versions don't complain.
2023-04-16 18:18:09 +02:00
Felix Fontein
0be88ab458 Prepare 2.12.0 release. 2023-04-16 13:36:59 +02:00
Felix Fontein
30756b12ea Add asn1_base64 option. (#592) 2023-04-16 13:34:45 +02:00
Felix Fontein
ec354a8a91 Update CI matrix: add stable-2.15 (#589)
* Add ignore files for bumped devel version.

* Update CI matrix.
2023-04-04 08:42:53 +02:00
Felix Fontein
1a4b22dff8 Next expected release is 2.12.0. 2023-03-24 07:49:51 +01:00
Felix Fontein
50a26191ea Release 2.11.1. 2023-03-24 07:19:51 +01:00
Felix Fontein
a28b02b0ac Prepare 2.11.1 release. 2023-03-23 21:27:44 +01:00
Felix Fontein
0829bc641e Use curl instead of get_url on Python 2.6. (#585) 2023-03-22 21:11:26 +01:00
Thomas Anderson
b997773139 fix(doc): privatekey_content docs were the same as privatekey_path (#583) 2023-03-21 13:38:54 +01:00
Felix Fontein
9044f25f33 CI: add extra VM group (#545)
* Add extra VM group.

* Use available VM names.
2023-03-09 07:58:14 +01:00
Felix Fontein
f8bd224c99 Add macOS 13.2 to CI (#581)
* Add macOS 13.2 to CI.

* Run brew --prefix with same user used for installing with brew.
2023-03-09 06:42:33 +01:00
Felix Fontein
4d21f1c19c More bools. (#580) 2023-03-06 23:04:27 +01:00
Felix Fontein
5a3e21788d Cancel concurrent workflow runs in PRs. 2023-02-23 09:56:12 +01:00
Felix Fontein
816a97ab47 Next expected release is 2.12.0. 2023-02-23 09:54:29 +01:00
Felix Fontein
d4509bce5f Release 2.11.0. 2023-02-23 09:28:13 +01:00
Austin Lane
ced0e30506 EL9 - Retrieve python3-pyOpenSSL from epel (#575)
* EL9 - pull python3-pyOpenSSL from epel

* Incorporate bindep changes from felixfontein

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add changelog fragment for PR #575

* Update changelog fragment.

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-02-22 23:16:26 +01:00
Felix Fontein
2fb543b144 Normalize bools in tests. (#577) 2023-02-15 22:23:36 +01:00
Felix Fontein
b08f6eefe8 Remove unnecessary test imports. (#576) 2023-02-12 20:59:55 +01:00
Felix Fontein
65d1881f12 Prepare 2.11.0 release. 2023-02-10 21:02:11 +01:00
dlehrman
b000491514 Support Custom Cipher Selection (#571)
* Enable custom cipher selection for get_certificate

* get_certificate ciphers desc grammar fix

Co-authored-by: Felix Fontein <felix@fontein.de>

* get_certificate ciphers desc grammar fix

Co-authored-by: Felix Fontein <felix@fontein.de>

* get_certificate ciphers include version_added

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add changelog fragment

* Fail if ciphers is set and Python < 2.7.9

* Standardize ciphers conditionals in get_certificate

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
2023-02-10 21:01:13 +01:00
Felix Fontein
70c4585b88 Fix deprecation handling. (#572) 2023-02-09 15:36:23 +01:00
Felix Fontein
aea3713484 Remove unneccessary imports (#569)
* Remove unneccessary imports.

* Add noqas.

* Add comment which name is actually ignored.
2023-02-09 11:57:54 +01:00
Felix Fontein
7f040011f0 Document PSF-2.0 license in galaxy.yml. (#567) 2023-01-31 21:31:30 +01:00
Felix Fontein
c6429eae4f Fix acme_inspect tests. (#565) 2023-01-22 22:41:12 +01:00
Felix Fontein
d2a30d2801 Update CI matrix. (#562) 2023-01-07 12:46:05 +01:00
Felix Fontein
a122be7942 Update CI matrix. (#562) 2023-01-07 12:45:52 +01:00
Felix Fontein
61f431dff3 Next expected release is 2.11.0. 2023-01-02 20:20:50 +01:00
Felix Fontein
b19c83578d Release 2.10.0. 2023-01-02 19:54:30 +01:00
Felix Fontein
ddfb18b609 openssl_csr: fix bad tests, avoid accepting invalid crl_distribution_points records (#560)
* Improve error handling.

* Remove invalid tests.

* Add changelog fragment.

* Fix tests.

* Improve exception catching.

Co-authored-by: Kristian Heljas <11139388+kristianheljas@users.noreply.github.com>

* Prevent empty full_name.

* Fix condition. Make sure errors are caught.

* Add more checks.

Co-authored-by: Kristian Heljas <11139388+kristianheljas@users.noreply.github.com>
2023-01-02 14:52:59 +00:00
Felix Fontein
095434a4c1 Prepare 2.10.0 release. 2022-12-31 18:05:12 +01:00
Felix Fontein
8a80ced4b8 Add openssl_privatekey_info filter (#555)
* Add openssl_privatekey_info filter.

* Update description.
2022-12-31 17:45:45 +01:00
Felix Fontein
ef2bb6d510 Add openssl_csr_info ilter. (#554) 2022-12-31 07:58:37 +01:00
Felix Fontein
889cfdf47e Add openssl_publickey_info filter. (#556) 2022-12-31 07:56:54 +01:00
Felix Fontein
c173449c46 Add x509_crl_info filter (#558)
* Add x509_crl_info filter.

* Work around bugs in Ansible 2.9 and ansible-base 2.10.
2022-12-31 07:56:34 +01:00
Felix Fontein
c08bae8308 Add x509_certificate_info filter. (#557) 2022-12-31 07:56:11 +01:00
Felix Fontein
80f7b084c0 Add filter module base, prepare adding filters (#553)
* Improve string handling.

* Cleanup tests.

* Add filter module mock.
2022-12-30 20:44:13 +01:00
Felix Fontein
5d24d04adf Fix crash when public key cannot be parsed. (#551) 2022-12-28 18:28:50 +01:00
Felix Fontein
7cc9a70e43 Add split_pem filter (#549)
* Add split_pem filter.

* Fix documentation.

* Python 2.7.

* Improve error message matching.

Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com>

Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com>
2022-12-27 21:57:20 +01:00
Felix Fontein
5ddfb2c2ca CI: balance groups (#548)
* Show timings with devel, and skip everything else.

* Move to other group.

* Try smaller SSH key size (i.e. make tests run faster).

* Add implicit size that now must be explicit.

* Change group of luks_device.

* Revert "Show timings with devel, and skip everything else."

This reverts commit 7b73f7e4d7.
2022-12-21 08:12:53 +01:00
Felix Fontein
242c15bf4c Make sure that iteration_count=1000 is not used with algorithm=argon* (which is SLOW and takes around 10 minutes). (#546) 2022-12-20 20:01:26 +01:00
Felix Fontein
867f407401 CI: improve CI matrix, split into two groups (#544)
* Prepare having more than one group.

* Remove duplicates; add CentOS Stream 8 with Python 3.6.

* Split up tests into two groups.
2022-12-20 12:57:53 +01:00
Felix Fontein
54f49f38f2 Improve docsite build. 2022-12-18 21:50:28 +01:00
Felix Fontein
83d2a782f6 Switch to my fork of ansible-test-gh-action. 2022-12-18 09:53:58 +01:00
Felix Fontein
d6dd8e0d45 The ansible-test patch has been backported to stable-2.12. 2022-12-17 19:39:06 +01:00
Felix Fontein
9029f8ce34 Fix CI name. 2022-12-12 21:28:19 +01:00
Felix Fontein
ca23b2ed9a Improve CI (#539)
* Update CI scripts to be more close to the ones in ansible-core.

* Extend CI matrix.

* Mark more VMs.

* Revert "Mark more VMs."

This reverts commit 8bc79af636.

* Disable alpine VMs for get_certificate due to httptester problems.

* Improve retrieval of cryptsetup version.

* ACME 'emulator' won't work on Alpine either.

* Improve luks test setup.

* Make sure wipefs is installed on Alpine.

* dmsetup (from device-mapper) is used by the tests.

* Fix bcrypt install failure handling.

* String, not float.

* openssl_privatekey_convert is not an action module.

* Update Python info.

* Try out which VMs can be used by now.

* Enable ACME tests on all VMs but Alpine; update comment.

* Adjust acme-tiny shebang.

* Remove new entries from CI matrix.
2022-12-11 19:55:47 +01:00
Felix Fontein
664f34f2ac Mark x509_certificate-acme test as target test. 2022-12-09 23:10:26 +01:00
Felix Fontein
1c2c404ca9 Bump CentOS Stream 8 Python from 3.8 to 3.9. (#540) 2022-12-09 14:58:03 +01:00
Felix Fontein
eef4df9063 Allow triggering docs workflow manually. 2022-12-07 19:54:22 +01:00
Felix Fontein
176da44faf Backports to stable-2.13 and stable-2.14 have been merged. (#537)
https://github.com/ansible/ansible/pull/79538
https://github.com/ansible/ansible/pull/79507
2022-12-07 08:59:00 +01:00
Felix Fontein
619d7d1dfe Improve CI image selection. (#536) 2022-12-03 15:31:03 +01:00
Felix Fontein
2eab4ec19c Switch CI from ubuntu-latest to ubuntu-20.04 to avoid problems with ansible-test from ansible-core 2.12, 2.13, 2.14. (#535) 2022-12-01 23:01:49 +01:00
Felix Fontein
05eff13ec8 ansible-core 2.11 is EOL. Move CI runs to GHA. (#534) 2022-11-27 22:44:07 +01:00
Felix Fontein
4d28266eba Use proposed new options for ansible-test-gh-action. (#533) 2022-11-27 21:04:52 +01:00
Felix Fontein
ba9c50c358 Next expected release is 2.10.0. 2022-11-27 18:55:12 +01:00
Felix Fontein
e1e5dfccc1 Release 2.9.0. 2022-11-27 18:15:33 +01:00
Felix Fontein
1097371cf4 Be more precise about which private keys are supported in openssl_publickey. (#532) 2022-11-27 18:13:59 +01:00
Felix Fontein
0b08d6bc52 Include collection name into docs workflows. 2022-11-27 17:44:19 +01:00
Felix Fontein
72ed39a481 Reference documentation in README. 2022-11-26 09:53:59 +01:00
Felix Fontein
d4683d941f Add GH Pages publishing. 2022-11-26 09:38:36 +01:00
Felix Fontein
f853108d69 Prepare 2.9.0 release. 2022-11-17 12:43:40 +01:00
Felix Fontein
045ff10826 Allow changelog fragments with .yaml ending. 2022-11-17 12:41:33 +01:00
Katze
2a746115ca fix #529 issuer_uri in x509_certificate_info (#530)
The issuer_uri is retrieved from the Authority Information Access field the same way as the OCSP responder URI is.
Handling is exactly the same since they reside in the same OID space and have the same data type.
Tests have also been added based on the integration test certificates.

Signed-off-by: benaryorg <binary@benary.org>

Signed-off-by: benaryorg <binary@benary.org>
2022-11-17 12:40:44 +01:00
Christoph
37fddc61d8 openssl_privatekey: fix example for cipher (#527)
the cipher parameter required for encrypted private keys only accepts the value "auto" 
as described in /plugins/doc_fragments/module_privatekey.py.

The previously documented value of "aes256" is invalid here.
2022-11-10 20:25:56 +01:00
Felix Fontein
a050250153 Next expected release is 2.9.0. 2022-11-06 22:31:21 +01:00
Felix Fontein
42e27a360d Release 2.8.1 2022-11-06 22:02:07 +01:00
Felix Fontein
95b9df187f Prepare 2.8.1 release. 2022-11-06 21:13:02 +01:00
Felix Fontein
7bbe8f467c Document attributes (#526)
* Add 'acme' action group attribute.

* Compatibility with older ansible-core releases.

* Fix typo.

* Document standard attributes.

* Improve docs.

* Add shortcuts for common combinations.
2022-11-06 21:10:56 +01:00
Felix Fontein
0c67afb6c3 Next expected release is 2.9.0. 2022-11-02 13:33:30 +01:00
Felix Fontein
68b7c0d38c Release 2.8.0. 2022-11-02 12:56:08 +01:00
Felix Fontein
9ba0e25bfe Handle new nonce call more gracefully when it does not return nonce. (#525) 2022-11-02 08:32:01 +00:00
Felix Fontein
9a64347ea6 Prepare 2.8.0 release. 2022-11-01 19:52:23 +01:00
Felix Fontein
e4e2b804bc Allow to configure encryption level. (#523) 2022-11-01 19:51:28 +01:00
Felix Fontein
4533b3e934 Include symbolic HTTP status codes in error and log messages when available. (#524) 2022-10-31 21:33:27 +01:00
Felix Fontein
fd71773668 Add badge for EOL CI. (#522) 2022-10-30 09:21:12 +01:00
Felix Fontein
b17d57f737 CI: drop remote CI targets for EoL Ansible/ansible-base versions; move docker CI targets for EoL Ansible/ansible-base versions to GHA (#520)
* Drop remote CI targets for EoL Ansible/ansible-base versions; move docker CI targets for EoL Ansible/ansible-base versions to GHA.

* Add target.

* Use change detection.
2022-10-29 21:23:33 +02:00
Felix Fontein
f5d98e3148 Use dependabot to update GHAs. 2022-10-17 22:57:28 +02:00
Felix Fontein
5f9536af06 Next expected elease is 2.8.0. 2022-10-17 22:47:26 +02:00
Felix Fontein
7c41b31c37 Bump GHA actions. 2022-10-17 22:47:26 +02:00
Felix Fontein
a5c43c26f3 Release 2.7.1. 2022-10-17 22:24:20 +02:00
Felix Fontein
82aa1480af Prepare 2.7.1 release. 2022-10-17 21:41:04 +02:00
Felix Fontein
516be406e0 Improve import error handling for ACME modules (#519)
* Improve import error handling for ACME modules

* Update plugins/module_utils/acme/acme.py

Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>

Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
2022-10-17 21:38:54 +02:00
Felix Fontein
1f4840ba2f Change CI group identifiers (#517)
* Change CI group identifiers.

* cloud → generic.
2022-10-10 22:39:10 +02:00
Felix Fontein
52bc2cb266 Next expected release is 2.8.0. 2022-09-23 08:06:55 +02:00
Felix Fontein
18502d5250 Release 2.7.0. 2022-09-23 07:40:12 +02:00
Felix Fontein
b3f589df62 Action plugin support code: ensure compatibility with newer versions of ansible-core (#515)
* Only access C.STRING_CONVERSION_ACTION for old ansible-base / Ansible versions.

* Always use self.__xxx instead of xxx directly.
2022-09-23 07:02:46 +02:00
Felix Fontein
8ebf1279f9 Prepare 2.7.0 release. 2022-09-23 06:48:26 +02:00
Felix Fontein
19161ae4a0 Use stable-2.14 (default) for docs build, instead of devel. 2022-09-22 06:39:51 +02:00
Felix Fontein
c24e5c63e8 acme modules: also support 503 for retries (#513)
* Also support 503 for retries.

* Forgot to adjust status code comparison.

* Also support 408.
2022-09-21 13:50:26 +02:00
Felix Fontein
e656570d13 Add stable-2.14 to CI. (#514) 2022-09-21 08:27:54 +02:00
Felix Fontein
9e4209b837 Next expected release is 2.7.0. 2022-09-19 21:34:32 +02:00
Felix Fontein
ed52123206 Release 2.6.0. 2022-09-19 20:46:26 +02:00
Felix Fontein
d10bcd3d6c Fix fragment. 2022-09-19 20:46:13 +02:00
Felix Fontein
45e81a1b0c Prepare 2.6.0 release. 2022-09-19 20:11:29 +02:00
Andrew Pantuso
829707fc5a fix: add warning when openssh-keypair has private_key_format specified with opensshbin (#512) 2022-09-19 20:10:21 +02:00
Felix Fontein
a0d862e1f1 Support 429 Too Many Requests for acme module_utils. (#508) 2022-09-19 20:10:03 +02:00
Andrew Pantuso
1dcc135da5 feat: add private_key_format choices for openssh_keypair (#511)
* feat: add private_key_format choices for openssh_keypair

* chore: add changelog fragment
2022-09-18 20:10:29 -04:00
Felix Fontein
95626abdd3 Make mostly reuse conformant (#502)
* Add .license files.

* Update README.

* Normalize licenses test.

* Add reuse GHA.

* Add blanket rule for changelog fragments.

* Add .license file for vendored third-party certificates.

* Fix workflow's permissions.

* Revert "Add .license file for vendored third-party certificates."

This reverts commit 35e106867c.

* Make reuse lint test optional.

* Add exceptions.

* Update README.

* Improve reuse test.
2022-09-13 19:13:04 +00:00
Maxwell G
152c5422f1 Prefer unitest.mock by universally using compat.mock (#506)
* tests.unit.compat.mock: Remove legacy compat code

This removes old Python 3.4 compatibility code that is no longer needed.

* Prefer unitest.mock by universally using compat.mock

`mock` is a backport of the `unittest.mock` module from the stdlib, and
there's no reason to use it on newer Python versions.
2022-09-04 22:21:31 +02:00
Felix Fontein
98bfdb322a Make sure that escape sequence in YAML is not interpreted by Python. (#507) 2022-09-04 22:09:06 +02:00
Felix Fontein
d0d99c31b0 [TEMP] Create temp remote directory in ~. (#504) 2022-08-24 06:54:09 +02:00
Felix Fontein
0e15d6cea8 Convert yes/no to true/false. (#503) 2022-08-23 21:33:29 +02:00
Felix Fontein
ed03b1aa7f Remove included fake CA cert, create one on demand. (#501) 2022-08-21 09:53:57 +02:00
Felix Fontein
0379fb5614 Move Fedora 35 from devel to stable-2.13 CI runs. (#500) 2022-08-16 21:54:00 +02:00
Felix Fontein
fd1263c9aa Fix some new linting errors (#499)
* Fix some new linting errors.

* More linting errors.

ci_complete
2022-08-12 08:34:51 +02:00
Felix Fontein
c0bab015a4 Next expected release is 2.6.0. 2022-08-04 23:24:04 +02:00
Felix Fontein
4428daa411 Release 2.5.0. 2022-08-04 23:06:49 +02:00
Felix Fontein
f821fa0f2d Prepare 2.5.0 release. 2022-08-04 22:47:16 +02:00
Felix Fontein
2dafef1fab Fix docs (#497)
* Fix docs.

* Fix YAML.

* Prevent crashes with older pyyaml versions.
2022-08-04 20:02:56 +02:00
Daniel Ziegenberg
d83f7639be add production directory URL for ACME v2 for Sectigo (#496)
According the to official Sectigo documentation [1] the directory URL
for ACME v2 is: https://acme-qa.secure.trust-provider.com/v2/DV

[1] https://docs.sectigo.com/scm/acme-integration-docs/1/eab-clients-sectigo-acme-integration.html

Signed-off-by: Daniel Ziegenberg <daniel@ziegenberg.at>
2022-07-26 18:10:43 +02:00
Felix Fontein
e08efe2598 Correctly mark plugins/module_utils/crypto/_obj2txt.py as having two licenses. (#495) 2022-07-25 07:17:56 +02:00
Felix Fontein
e4ebca0945 Fix ssh-agent tests (#493)
* Work around stupid ssh-agent output format.

* Workaround for Ansible 2.9.

* Old jinja2...

* Jinja2 on CentOS 6 is really annoying.
2022-07-22 13:54:15 +02:00
Felix Fontein
6bf3ef47e1 Move licenses to LICENSES/, use SPDX-License-Identifier, mention all licenses in galaxy.yml (#491)
* Add SPDX license identifiers, mention all licenses in galaxy.yml.

* Add default copyright headers.

* Add headers for documents.

* Fix/add more copyright statements.

* Add copyright / license info for vendored code.

* Add extra sanity test.

* Add changelog fragment.

* Comment PSF-2.0 license out in galaxy.yml for now.

* Remove colon after 'Copyright'.

* Avoid colon after 'Copyright' in lint script.

* Mention correct filename.

* Add BSD-3-Clause.

* Improve lint script.

* Update README.

* Symlinks...
2022-07-21 07:27:26 +02:00
Felix Fontein
7deb0a6db9 openssl_csr: extend tests to check for privatekey_content together with privatekey_passphrase (#490)
* Extend tests to check for privatekey_content together with privatekey_passphrase.

* Also test privatekey_content for private keys without passphrases.
2022-07-14 14:32:53 +02:00
Felix Fontein
c106638648 Next expected release is 2.5.0. 2022-07-09 14:11:28 +02:00
Felix Fontein
4dcbbfba5b Release 2.4.0. 2022-07-09 13:20:28 +02:00
Felix Fontein
036c4c8e6f Prepare 2.4.0. 2022-07-09 13:19:48 +02:00
Felix Fontein
9ed4526fee openssl_pkcs12: fix crash when trying to get non-existing other certificates (#487)
* Fix crash when trying to get non-existing other certificates.

* Add test.
2022-07-07 22:30:22 +02:00
Felix Fontein
b16f12faa3 Add docs PR workflow that a) builds docs and b) show the docs diff that a PR ceates. (#484) 2022-06-29 20:46:25 +02:00
Felix Fontein
9df02aa335 Next expected release is 2.4.0. 2022-06-21 12:22:01 +02:00
Felix Fontein
d60d3fe1cb Revert "Revert "Revert "Revert "Revert "Revert "Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460)""""""
This reverts commit 4a1842c004.
2022-06-21 12:21:46 +02:00
Felix Fontein
a554a588c9 Release 2.3.4. 2022-06-21 12:11:22 +02:00
Felix Fontein
4a1842c004 Revert "Revert "Revert "Revert "Revert "Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460)"""""
This reverts commit 39ec208171.
2022-06-21 12:09:00 +02:00
Felix Fontein
39ec208171 Revert "Revert "Revert "Revert "Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460)""""
This reverts commit f5ccc1516b.
2022-06-17 09:26:38 +02:00
Felix Fontein
899de8227d Next expected release is 2.4.0. 2022-06-17 09:26:36 +02:00
Felix Fontein
5af4a16e57 Release 2.3.3. 2022-06-17 08:25:13 +02:00
Felix Fontein
fc07de73e3 Prepare 2.3.3 release. 2022-06-17 08:24:47 +02:00
Felix Fontein
f5ccc1516b Revert "Revert "Revert "Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460)"""
(So we can do yet another bugfix release.)

This reverts commit cb77d81f8d.
2022-06-17 08:24:03 +02:00
Felix Fontein
de0ec1f739 Add Apache 2.0 license; simplify and standardize license headers (#478)
* Add Apache 2.0 license for Apache 2.0 licensed parts.

* Unify license headers.

* Move additional licenses to licenses/.

* Revert "Move additional licenses to licenses/."

This reverts commit c12b22de1c.
2022-06-17 08:20:40 +02:00
Songmin Li
b29f238083 Fix ValueError: excluded_subtrees must be a non-empty list or None (#481) 2022-06-17 07:53:07 +02:00
Felix Fontein
2941bb9bb8 Prepare 2.4.0 release. 2022-06-15 22:16:49 +02:00
Felix Fontein
297b44f24b x509_crl: do not crash when signing with Ed25519 or Ed448 (#475)
* Do not crash when signing with Ed25519 or Ed448.

* Forgot replace.
2022-06-15 22:06:40 +02:00
Felix Fontein
429ed5faa5 Skip Ansible 2.9 coverage reporting with new AZP container. (#476)
ci_coverage
2022-06-14 21:41:27 +02:00
Felix Fontein
b3029f75cd Bump AZP container version. (#472) 2022-06-13 21:16:19 +02:00
Felix Fontein
f7bc3aa77c Disable broken CI platforms. (#469) 2022-06-05 23:11:29 +02:00
Felix Fontein
cb77d81f8d Revert "Revert "Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460)""
This reverts commit 798d12b499.
2022-06-02 12:52:58 +02:00
Felix Fontein
b87617945e Next expected release is 2.4.0. 2022-06-02 12:52:37 +02:00
Felix Fontein
9289ce8534 Release 2.3.2. 2022-06-02 12:28:12 +02:00
Felix Fontein
798d12b499 Revert "Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460)"
(This is so we can do a bugfix release now. Deprecations are not alloewd in bugfix releases.)

This reverts commit 74960eaeac.
2022-06-02 07:49:28 +02:00
Felix Fontein
5146760def Adjust release summary, since there are other fragments. 2022-06-02 07:49:16 +02:00
Felix Fontein
48649d50b5 Prepare 2.3.2 release. 2022-06-02 07:46:42 +02:00
Felix Fontein
ccd66419f4 Add simplified_bsd.txt license file (#467)
* Add simplified_bsd.txt and adjust references.

* Add changelog.
2022-06-02 07:45:50 +02:00
Felix Fontein
80334884fb Update CI matrix. (#464) 2022-05-27 11:00:01 +02:00
Andrew Pantuso
4ab45e8c21 ci: enable rhel9.0 tests for openssh_cert (#463)
* ci: enable rhel9.0 tests for openssh_cert

* ci: allow openssh_cert second signature algorithm test for versions >8.7

* ci: narrowing condition to not attempt RSA1 signing exclusively on RHEL >=9

* ci: grouping and documenting condition
2022-05-21 16:43:54 +02:00
Felix Fontein
c566a7abf3 Add RHEL 9.0, FreeBSD 13.1, Ubuntu 22.04 and Fedora 36 to CI (#456)
* Add RHEL 9.0 and FreeBSD 13.1 to CI.

* Add Ubuntu 22.04 and Fedora 36 to CI.

* Switch orders so that root doesn't have a SHA1 signature.

* Skip openssh_cert test on RHEL 9.0.

* Make it possible that pyOpenSSL isn't installed *at all*.

* Work with default.
2022-05-20 23:03:54 +02:00
Felix Fontein
fd0048827d Remove FreeBSD 12.1 from CI. The remote seems to be no longer working. (#459) 2022-05-20 14:47:05 +02:00
Felix Fontein
74960eaeac Deprecate Ansible 2.9 and ansible-base 2.10 support. (#460) 2022-05-20 14:45:56 +02:00
Felix Fontein
c49102d688 certificate_complete_chain: do not stop execution on unsupported algorithm (#457)
* Do not stop execution on unsupported algorithm.

* Fix typo.
2022-05-20 07:22:35 +02:00
Felix Fontein
40cf8ba2ce Next expected release is 2.4.0. 2022-05-16 13:02:15 +02:00
Felix Fontein
ffc0ab2d40 Release 2.3.1. 2022-05-16 12:27:29 +02:00
Felix Fontein
7183596586 Update with actual CPython 3.9.5 license. (#454) 2022-05-16 08:24:43 +02:00
Felix Fontein
5664bfe4b6 Add PSF-license.txt file. (#453) 2022-05-16 07:40:04 +02:00
Felix Fontein
4074ff4132 Prepare 2.3.1 release. 2022-05-16 07:13:15 +02:00
Felix Fontein
e7f9885aa3 Next expected release is 2.4.0. 2022-05-09 21:11:07 +02:00
Felix Fontein
5641e2ac9b Release 2.3.0. 2022-05-09 20:53:39 +02:00
Felix Fontein
4cf951596f Improve handling of IDNA/Unicode domains (#436)
* Prepare IDNA/Unicode conversion code. Use to normalize input.

* Use IDNA library first (IDNA2008) and Python's IDNA2003 implementation as a fallback.

* Make sure idna is installed.

* Add changelog fragment.

* 'punycode' → 'idna'.

* Add name_encoding options and tests.

* Avoid invalid character for IDNA2008.

* Linting.

* Forgot to upate value.

* Work around cryptography bug. Fix port handling for URIs.

* Forgot other place sensitive to cryptography bug.

* Forgot one. (Will likely still fail.)

* Decode IDNA in _compress_entry() to avoid comparison screw-ups.

* Work around Python 3.5 problem in Ansible 2.9's default test container.

* Update changelog fragment.

* Fix error, add tests.

* Python 2 compatibility.

* Update requirements.
2022-05-09 19:57:14 +02:00
Felix Fontein
90efcc1ca7 Add privatekey_content option. (#452) 2022-05-09 19:56:08 +02:00
Felix Fontein
8a1c60e54a Incorporate feedback from #443 that ACME modules work with Sectigo ACME Service for InCommon (#451)
* Incorporate feedback from #443.

* Update comment.
2022-05-09 13:28:03 +02:00
Felix Fontein
43665a3892 Prepare 2.3.0 release. 2022-05-03 19:24:20 +02:00
Felix Fontein
640bdbc066 Add EE support (#440)
* Add EE files.

* Install cryptography and PyOpenSSL from PyPi.

* Revert "Install cryptography and PyOpenSSL from PyPi."

This reverts commit 6b90a1efae.

* Only run test when cryptography has a new enough version.

* And another one.

* Extend changelog.
2022-05-03 19:22:55 +02:00
Jonas Verhofsté
c16d9f78b8 Make request timeout configurable for all acme modules (#448)
* Make request timeout configurable for all acme modules

Fixes #447.

* Log change made in #448
2022-05-03 17:29:38 +02:00
Felix Fontein
91f192ce5b Fix main for new cryptography 37.0.0 release (#445)
* Fix empty check for openssl_pkcs12 tests.

* Remove unnecessary imports.

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

* Add changelog fragment.

* Fix constraints file.

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

* Prevent upgrading cryptography on ansible-core 2.12's default container with Python 3.9.
2022-04-26 22:18:37 +02:00
Felix Fontein
e560acdac5 Remove distutils from unit tests. (#444) 2022-04-21 11:05:18 +02:00
Felix Fontein
9d03178b00 Fix crash in x509_crl when certificate issuer is specified (#441)
* Fix x509_crl certificate issuer issue.

* Add tests.

* Add changelog fragment.
2022-04-18 08:17:27 +02:00
Yauhen
041fff5057 Add persistent and perf options to the luks_device (#434)
Read and write work queue significantly degrades performance on
SSD/NVME devices[1].

In Debian 11 crypttab does not support no-read-workqueue and
no-write-workqueue flags, so the persistent flag is workaround: once
opened with perf parameters persists forever.

[1] https://blog.cloudflare.com/speeding-up-linux-disk-encryption/

Signed-off-by: Yauhen Artsiukhou <jsirex@gmail.com>
2022-04-10 14:30:10 +02:00
Felix Fontein
c7f581daad Switch from antsibull to antsibull-docs. (#438) 2022-04-10 10:43:36 +02:00
Felix Fontein
b47168994d Remove OpenSuSE Python 2 from devel CI. (#435) 2022-04-02 18:09:09 +02:00
bluikko
635b25519b Document OpenSSH 7.7 required for validity always (#429)
* Document OpenSSH 7.7 required for validity always

* Document cert start time option for OpenSSH <7.7

* Improve language of "always" time support

* Update language on `always` to suggested

* Fix indetation
2022-04-02 12:02:42 -04:00
Felix Fontein
bc00c30faf Replace antsibull-lint collection-docs with antsibull-docs lint-collection-docs. (#432) 2022-03-30 08:00:14 +02:00
Felix Fontein
a28b2a5b4b Add stable-2.13 to CI, thin out older version matrix (#425)
* Add stable-2.13 to CI, thin out older version matrix.

* Thin out a bit more.
2022-03-29 06:49:38 +02:00
Felix Fontein
9e611a6148 Next expected release is 2.3.0. 2022-03-22 13:44:19 +01:00
Felix Fontein
025091c3fb Release 2.2.4 2022-03-22 13:17:36 +01:00
Felix Fontein
b0cede8231 Prepare 2.2.4 release. 2022-03-22 07:31:54 +01:00
Felix Fontein
22084b26d4 Add collection links file. (#421) 2022-03-22 07:04:23 +01:00
Felix Fontein
867158a942 Run ACME tests on more targets. (#419) 2022-03-12 08:55:06 +01:00
Andrew Pantuso
033bab7db1 openssh_* - catch and report top-level exceptions via fail_json (#417)
* ensure exceptions are properly reported

* adding changelog fragment

* applying review suggestions

* typo

* adding back exception msg
2022-03-08 13:23:09 +01:00
Felix Fontein
47d9fad45f Next expected release is 2.3.0. 2022-03-04 08:12:32 +01:00
Felix Fontein
f82dcbea21 Release 2.2.3. 2022-03-04 07:38:44 +01:00
Felix Fontein
67f511b5ad Prepare 2.2.3 release. 2022-03-03 21:18:07 +01:00
Andrew Pantuso
010f1a4d2d fixing public key return value docs (#412) 2022-03-02 13:52:07 +01:00
Felix Fontein
0d4b3ed991 Fix parsing of lsblk output. (#410) 2022-03-02 13:48:38 +01:00
Felix Fontein
2f2c8b57e8 Next expected release is 2.3.0. 2022-02-21 22:41:39 +01:00
Felix Fontein
b952b103e2 Releasing 2.2.2. 2022-02-21 22:11:55 +01:00
Felix Fontein
c85659ebfc Prepare 2.2.2 release. 2022-02-19 18:55:41 +01:00
Felix Fontein
73c8577b61 Integrate Alpine into CI (#408)
* Integrate Alpine into CI.

* Fix package names.
2022-02-19 17:54:05 +00:00
Felix Fontein
84c1a20af7 CI: add community ansible-test images (#404)
* Use community ansible-test images.

* Adjust tests for new operating systems, and pass on Python version as well.

* Fix Python version.

Co-authored-by: David Moreau Simard <moi@dmsimard.com>

* Fix package name.

Co-authored-by: David Moreau Simard <moi@dmsimard.com>
2022-02-17 22:29:50 +01:00
Felix Fontein
28729657ac 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.
2022-02-16 07:38:11 +01:00
Felix Fontein
3ebc132c03 Regenerate certificate on CA's subject change. (#402) 2022-02-14 18:04:29 +01:00
Felix Fontein
11a14543c8 certificate_complete_chain: handle duplicate intermediate subjects (#403)
* 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.
2022-02-14 13:29:19 +01:00
Felix Fontein
7e92f04c93 Next expected release is 2.3.0. 2022-02-05 21:51:19 +01:00
Felix Fontein
8a74b3e259 Release 2.2.1. 2022-02-05 21:28:47 +01:00
Felix Fontein
a9fcd584e9 Prepare 2.2.1 release. 2022-02-05 20:18:32 +01:00
Andrew Pantuso
a307618872 openssh_cert - fix full_idempotence for host certificates (#396)
* fixing host cert idempotence

* adding changelog fragment
2022-02-04 20:53:50 +01:00
Felix Fontein
12749088a0 Next expected release is 2.3.0. 2022-02-01 06:25:49 +01:00
Felix Fontein
90da233341 Release 2.2.0. 2022-02-01 05:49:27 +01:00
Felix Fontein
23226dce8f Drop CentOS 8 from CI. (#393) 2022-01-31 21:17:41 +00:00
Felix Fontein
9faed1dad0 Prepare 2.2.0 release. 2022-01-31 06:02:48 +01:00
Felix Fontein
ea2e45d63f Set LANG and similar env variables to prevent translated cryptsetup output. (#388) 2022-01-30 21:30:56 +01:00
Felix Fontein
5abfe8fca9 PyOpenSSL 22.0.0 no longer supports Python 2.7. (#387) 2022-01-30 13:59:32 +01:00
JochenKorge
b339e71973 Added 'ignore_timestamps' parameter (#381)
* Added 'ignore_timestamps' parameter

* Update plugins/modules/openssh_cert.py

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

* Update plugins/modules/openssh_cert.py

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

* Update plugins/modules/openssh_cert.py

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

* Added fragment

* Update plugins/modules/openssh_cert.py

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

* added ignore_timestamps to example

* corrected styling

* fixed styling (again)

* Update changelogs/fragments/381_openssh_cert_add_ignore_timestamps.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* splitted description as suggested by felixfontein

* fixed linebreak

* Mentioned ignore_timestamps in regenerate

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
Co-authored-by: Felix Fontein <felix@fontein.de>
2022-01-20 16:15:50 +01:00
Felix Fontein
a467f036b1 Fix indentation of when in example. (#382) 2022-01-18 21:52:53 +01:00
Felix Fontein
cd5ed011a5 Update CI matrix for Remote Devel (#377)
* Update CI matrix for Remote Devel.

* Add Python info entries.
2022-01-13 09:18:48 +01:00
Felix Fontein
9dfdaaf471 Next expected release is 2.2.0. 2022-01-10 23:03:02 +01:00
Felix Fontein
950d1d072f Release 2.1.0. 2022-01-10 22:48:03 +01:00
Felix Fontein
cb14e73c61 Prepare 2.1.0 release. 2022-01-10 21:03:19 +01:00
Felix Fontein
bd2bd79497 Add openssl_privatekey_convert module (#362)
* Add openssl_privatekey_convert module.

* Extend tests and fix bugs.

* Fix wrong required.

* Fix condition.

* Fix bad tests.

* Fix documentation for format.

* Fix copyright lines.
2022-01-10 21:01:52 +01:00
Felix Fontein
62272296da Small docs improvements (#374)
* Small improvements.

* Document behavior changes.
2022-01-10 12:05:09 +00:00
Felix Fontein
1b0fcde862 Fix comment. (#372) 2022-01-06 12:56:10 +00:00
Felix Fontein
46f39efc43 Use vendored copy of distutils.version. (#369) 2022-01-05 21:30:11 +01:00
Felix Fontein
b2ea4a7ce5 Add basic crypto_info module (#363)
* Add basic crypto_info module.

* Improve check.

* Actually test capabilities.

* Also output EC curve list.

* Fix detections.

* Ed25519 and Ed448 are not supported on FreeBSD 12.1.

* Refactor.

* Also retrieve information on the OpenSSL binary.

* Improve splitting.

* Update plugins/modules/crypto_info.py

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

* Replace list by tuple.

Co-authored-by: Andrew Pantuso <ajpantuso@gmail.com>
2022-01-05 18:19:42 +01:00
Felix Fontein
3e307fe062 Fix typo. (#367) 2022-01-05 18:16:27 +01:00
Felix Fontein
6ee238d961 certificate_complete_chain: avoid infinite loops, and double roots when root certificate was already part of chain (#360)
* Avoid infinite loops, and double roots when root certificate was already part of chain.

* Refactor tests for readability.
2022-01-04 07:00:09 +01:00
Felix Fontein
f3e431912d Fix indentation in docs. (#364) 2022-01-03 21:38:37 +01:00
Felix Fontein
471506c5d4 Improve changed / nonchanged validations by using new modules from community.internal_test_tools (#183)
* Use modules from internal_test_tools instead of stat workaround to check whether file actually changed.

* Properly add testing dependency.
2022-01-03 18:43:17 +01:00
Jens Heinrich
2c05221d89 Feature/rename test cases (#356)
* 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>
2021-12-30 10:06:43 +01:00
Felix Fontein
a539cd6939 Prepare for distutils.version being removed in Python 3.12 (#353)
* 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.
2021-12-24 11:28:14 +01:00
Felix Fontein
11c3974b0f Next expected release is 2.1.0. 2021-12-20 20:37:59 +01:00
Felix Fontein
605cf2631e Release 2.0.2. 2021-12-20 20:20:32 +01:00
Felix Fontein
ee196fd8a3 Prepare 2.0.2 release. 2021-12-20 20:15:27 +01:00
Jasmine Hegman
45b7aa797e Fix module reference in example (#351)
openssl_privatekey -> openssl_publickey
2021-12-13 06:48:59 +01:00
Bruno Vernay
32dab841d7 Fix CSR copy/paste error (#349)
The first case about ca_csr has been copy/pasted.
But in the following cases, the CSR must be the certificate csr.
2021-12-09 20:52:18 +01:00
Felix Fontein
5b1816719f Next expected release is 2.1.0. 2021-11-22 12:19:10 +01:00
Felix Fontein
bd2270fb05 Release 2.0.1. 2021-11-22 11:41:56 +01:00
Felix Fontein
3f40795a98 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
2021-11-22 07:42:49 +01:00
Felix Fontein
73bc0f5de7 Use new PKCS#12 deserialization code from cryptography 36.0.0 if available (#302)
* 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
2021-11-22 07:41:54 +01:00
Felix Fontein
f832c0a4ac Fix missing s. 2021-11-22 07:41:39 +01:00
Felix Fontein
10579c8834 Prepare 2.0.1 release. 2021-11-22 07:40:23 +01:00
Felix Fontein
f1a6baadc7 Fix collection dependency installation in CI. (#341) 2021-11-18 20:42:57 +01:00
Felix Fontein
5de50b9f91 Fix compatibility to fetch_url change in ansible-core devel (#339)
* Fix compatibility to fetch_url change in ansible-core devel.

* Adjust tests.
2021-11-17 21:26:49 +01:00
Felix Fontein
cf0d2679aa Replace RHEL 8.4 by RHEL 8.5 for devel. (#337) 2021-11-17 07:18:08 +01:00
Felix Fontein
2d388bf8d0 This is no longer a problem with the dev version of cryptography. (#335) 2021-11-13 17:59:17 +01:00
Felix Fontein
056a86fcae Replace Bash codecov uploader by new Python codecov uploader. (#333)
ci_coverage
2021-11-13 12:06:10 +01:00
Felix Fontein
ebbfd7c56f luks_device: add built-in signature wiper to work around older wipefs versions with LUKS2 containers (#327)
* 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.
2021-11-11 06:59:35 +01:00
Felix Fontein
91d98c4413 Replace Fedora 33 with Fedora 35 for devel tests. (#328) 2021-11-08 22:17:30 +01:00
Felix Fontein
51b6bb210d acme_certificate: fix crash when using fullchain_dest (#324)
* 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>
2021-11-05 08:51:43 +01:00
Felix Fontein
94634a347d Next expected release is 2.1.0. 2021-11-01 20:11:50 +01:00
Felix Fontein
e5acd27c9b Release 2.0.0. 2021-11-01 19:18:03 +01:00
Felix Fontein
e6cd66df53 Add release summary. (#319) 2021-11-01 18:53:46 +01:00
Felix Fontein
589e7c72ef Allow to specify subject (for CSRs) and issuer (for CRLs) ordered (#316)
* Allow to specify subject (for CSRs) and issuer (for CRLs) ordered.

* Forgot import.

* Apply suggestions from code review

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

* Apply suggestions from code review

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

* Fix typo.

* Simplify error handling, reject empty values outright.

* Document d497231e1c.

Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
2021-10-31 15:05:04 +01:00
Felix Fontein
ecbd44df22 Add warning for ASN.1 encoded extension values returned by some modules (#318)
* Add warning that ASN.1 encoded extension values returned by some modules might not reflect the exact byte sequence in the source file anymore depending on the cryptography version.

* Apply suggestions from code review

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

Co-authored-by: Ajpantuso <ajpantuso@gmail.com>
2021-10-31 14:34:33 +01:00
Felix Fontein
4ab2ed8b77 Add ignore_timestamps option. (#317) 2021-10-30 16:34:27 +02:00
Felix Fontein
eb8dabce84 Improve Python 2 Unicode handling. (#313) 2021-10-22 07:15:20 +02:00
Felix Fontein
c5df302faa openssl_privatekey_info: disable private key consistency checks by default (#309)
* Disable private key consistency checks by default.

* Improve formulations, mention side-channel attacks.
2021-10-20 18:28:22 +02:00
Felix Fontein
a581f1ebcd Remove other deprecations (#290)
* Remove deprecated redirects.

* Remove deprecations.

* Add changelog fragment.

* Add some forgotten pieces.

* Bump version to 2.0.0.

* Fix formulation.
2021-10-16 21:00:48 +02:00
Felix Fontein
78b27ffedb Remove centos8 for devel from CI. (#307) 2021-10-16 08:33:29 +02:00
Felix Fontein
e735bdab60 Adjust to latest devel changes. 2021-10-12 19:16:46 +02:00
Felix Fontein
5f1efb6f7e Remove assertonly (#289)
* Remove assertonly backend.

* Remove assertonly tests.

* The expired test is basically a test of assertonly.

* Replace assertonly verification by _info + assert.
2021-10-10 10:24:00 +02:00
Felix Fontein
c68bfedbaa Fix shellcheck error. (#303) 2021-10-08 14:14:08 +02:00
Felix Fontein
871a185ecb Remove vendored copy of ipaddress (#287)
* Remove vendored copy of ipaddress.

* Forgot an import.

* Remove sanity ignores and checks related to ipaddress.

* Remove octal IPv4 address.

Such IPs are no longer accepted by ipaddress in Python's standard library (CVE-2021-29921).

* Remove unused import.

Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>

Co-authored-by: Sviatoslav Sydorenko <wk.cvs.github@sydorenko.org.ua>
2021-10-06 14:53:40 +02:00
Felix Fontein
ed03841fd1 x509_certificate: document that *notBefore/*notAfter are not used for idempotency (#298)
* Document that *notBefore/*notAfter are not used for idempotency.

* Change formulation.
2021-10-03 22:03:58 +02:00
Felix Fontein
d6c0d53442 Fix PKCS#12 friendly name extraction for cryptography 35.0.0. (#296) 2021-10-03 20:25:24 +02:00
Felix Fontein
a2a7d94055 Support cryptography 35.0.0 for all modules except openssl_pkcs12 (#294)
* 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.
2021-10-03 16:53:22 +02:00
Felix Fontein
2a7e452cf8 Ansible-core devel dropped support for Python 2.6. 2021-10-01 13:45:57 +02:00
Felix Fontein
74ae95038c Add patchback config. 2021-09-30 17:27:12 +02:00
Felix Fontein
57c364fe87 Temporarily fix CI for cryptography 35.0.0 release. (#292) 2021-09-30 13:39:34 +02:00
Felix Fontein
04958ece31 Deprecate ACME v1 (#288)
* Deprecate ACME v1.

* Fix syntax error.
2021-09-29 06:44:31 +02:00
Felix Fontein
838bdd711b Make Dirname (de)serialization conformant to RFC 4514 (#274)
* Adjust dirName serialization to RFC 4514.

* Adjust deserialization to RFC 4514.

* Add changelog fragment.

* Use Unicode strings, and work around Python 2 and Python 3 differences and problems with old cryptography versions.

* Work with bytes, not Unicode strings, to handle escaping of Unicode endpoints correctly.
2021-09-28 18:15:38 +02:00
Felix Fontein
f644db3c79 Remove PyOpenSSL backends (except for openssl_pkcs12) (#273)
* Remove Ubuntu 16.04 (Xenial Xerus) from CI.

* Removing PyOpenSSL backend from everywhere but openssl_pkcs12.

* Remove PyOpenSSL support from module_utils that's not needed for openssl_pkcs12.

* Add changelog fragment.
2021-09-28 17:46:35 +02:00
Felix Fontein
24e7d07973 Run CI on stable branches only once per week. 2021-09-28 17:34:30 +02:00
540 changed files with 21609 additions and 11727 deletions

View File

@@ -0,0 +1,9 @@
<!--
Copyright (c) Ansible Project
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
-->
## Azure Pipelines Configuration
Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information.

View File

@@ -0,0 +1,367 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
trigger:
batch: true
branches:
include:
- main
- stable-*
pr:
autoCancel: true
branches:
include:
- main
- stable-*
schedules:
- cron: 0 9 * * *
displayName: Nightly
always: true
branches:
include:
- main
- cron: 0 12 * * 0
displayName: Weekly (old stable branches)
always: true
branches:
include:
- stable-*
variables:
- name: checkoutPath
value: ansible_collections/community/crypto
- name: coverageBranches
value: main
- name: pipelinesCoverage
value: coverage
- name: entryPoint
value: tests/utils/shippable/shippable.sh
- name: fetchDepth
value: 0
resources:
containers:
- container: default
image: quay.io/ansible/azure-pipelines-test-container:6.0.0
pool: Standard
stages:
### Sanity & units
- stage: Ansible_devel
displayName: Sanity & Units devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: 'devel/sanity/1'
- name: Sanity Extra # Only on devel
test: 'devel/sanity/extra'
- name: Units
test: 'devel/units/1'
- stage: Ansible_2_17
displayName: Sanity & Units 2.17
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.17/sanity/1'
- name: Units
test: '2.17/units/1'
- stage: Ansible_2_16
displayName: Sanity & Units 2.16
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.16/sanity/1'
- name: Units
test: '2.16/units/1'
- stage: Ansible_2_15
displayName: Sanity & Units 2.15
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
targets:
- name: Sanity
test: '2.15/sanity/1'
- name: Units
test: '2.15/units/1'
### Docker
- stage: Docker_devel
displayName: Docker devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: devel/linux/{0}
targets:
- name: Fedora 40
test: fedora40
- name: Ubuntu 24.04
test: ubuntu2404
- name: Alpine 3.20
test: alpine320
groups:
- 1
- 2
- stage: Docker_2_17
displayName: Docker 2.17
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.17/linux/{0}
targets:
- name: Fedora 39
test: fedora39
- name: Ubuntu 22.04
test: ubuntu2204
- name: Alpine 3.19
test: alpine319
groups:
- 1
- 2
- stage: Docker_2_16
displayName: Docker 2.16
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.16/linux/{0}
targets:
- name: Fedora 38
test: fedora38
- name: openSUSE 15
test: opensuse15
- name: Alpine 3
test: alpine3
groups:
- 1
- 2
- stage: Docker_2_15
displayName: Docker 2.15
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.15/linux/{0}
targets:
- name: Fedora 37
test: fedora37
- name: CentOS 7
test: centos7
groups:
- 1
- 2
### Community Docker
- stage: Docker_community_devel
displayName: Docker (community images) devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: devel/linux-community/{0}
targets:
- name: Debian Bullseye
test: debian-bullseye/3.9
- name: Debian Bookworm
test: debian-bookworm/3.11
- name: ArchLinux
test: archlinux/3.12
groups:
- 1
- 2
### Remote
- stage: Remote_devel_extra_vms
displayName: Remote devel extra VMs
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: devel/{0}
targets:
- name: Alpine 3.20
test: alpine/3.20
- name: Fedora 40
test: fedora/40
- name: Ubuntu 22.04
test: ubuntu/22.04
- name: Ubuntu 24.04
test: ubuntu/24.04
groups:
- vm
- stage: Remote_devel
displayName: Remote devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: devel/{0}
targets:
- name: macOS 14.3
test: macos/14.3
- name: RHEL 9.4
test: rhel/9.4
- name: FreeBSD 14.1
test: freebsd/14.1
groups:
- 1
- 2
- stage: Remote_2_17
displayName: Remote 2.17
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.17/{0}
targets:
- name: RHEL 9.3
test: rhel/9.3
- name: FreeBSD 13.3
test: freebsd/13.3
- name: FreeBSD 14.0
test: freebsd/14.0
groups:
- 1
- 2
- stage: Remote_2_16
displayName: Remote 2.16
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.16/{0}
targets:
- name: macOS 13.2
test: macos/13.2
- name: RHEL 9.2
test: rhel/9.2
- name: RHEL 8.8
test: rhel/8.8
# - name: FreeBSD 13.2
# test: freebsd/13.2
groups:
- 1
- 2
- stage: Remote_2_15
displayName: Remote 2.15
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
testFormat: 2.15/{0}
targets:
- name: RHEL 9.1
test: rhel/9.1
- name: RHEL 8.7
test: rhel/8.7
- name: RHEL 7.9
test: rhel/7.9
# - name: FreeBSD 13.1
# test: freebsd/13.1
# - name: FreeBSD 12.4
# test: freebsd/12.4
groups:
- 1
- 2
### Generic
- stage: Generic_devel
displayName: Generic devel
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: devel/generic/{0}
targets:
- test: 3.8
# - test: 3.9
# - test: "3.10"
- test: "3.11"
- test: "3.13"
groups:
- 1
- 2
- stage: Generic_2_17
displayName: Generic 2.17
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.17/generic/{0}
targets:
- test: "3.7"
- test: "3.12"
groups:
- 1
- 2
- stage: Generic_2_16
displayName: Generic 2.16
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.16/generic/{0}
targets:
- test: "2.7"
- test: "3.6"
- test: "3.11"
groups:
- 1
- 2
- stage: Generic_2_15
displayName: Generic 2.15
dependsOn: []
jobs:
- template: templates/matrix.yml
parameters:
nameFormat: Python {0}
testFormat: 2.15/generic/{0}
targets:
- test: 3.5
- test: "3.10"
groups:
- 1
- 2
## Finally
- stage: Summary
condition: succeededOrFailed()
dependsOn:
- Ansible_devel
- Ansible_2_17
- Ansible_2_16
- Ansible_2_15
- Remote_devel_extra_vms
- Remote_devel
- Remote_2_17
- Remote_2_16
- Remote_2_15
- Docker_devel
- Docker_2_17
- Docker_2_16
- Docker_2_15
- Docker_community_devel
- Generic_devel
- Generic_2_17
- Generic_2_16
- Generic_2_15
jobs:
- template: templates/coverage.yml

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Aggregate code coverage results for later processing.
set -o pipefail -eu
agent_temp_directory="$1"
PATH="${PWD}/bin:${PATH}"
mkdir "${agent_temp_directory}/coverage/"
if [[ "$(ansible --version)" =~ \ 2\.9\. ]]; then
exit
fi
options=(--venv --venv-system-site-packages --color -v)
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.
# Doing so allows this script to work unmodified for multiple Ansible versions.
ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}"
fi

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env python
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
"""
Combine coverage data from multiple jobs, keeping the data only from the most recent attempt from each job.
Coverage artifacts must be named using the format: "Coverage $(System.JobAttempt) {StableUniqueNameForEachJob}"
The recommended coverage artifact name format is: Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)
Keep in mind that Azure Pipelines does not enforce unique job display names (only names).
It is up to pipeline authors to avoid name collisions when deviating from the recommended format.
"""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import re
import shutil
import sys
def main():
"""Main program entry point."""
source_directory = sys.argv[1]
if '/ansible_collections/' in os.getcwd():
output_path = "tests/output"
else:
output_path = "test/results"
destination_directory = os.path.join(output_path, 'coverage')
if not os.path.exists(destination_directory):
os.makedirs(destination_directory)
jobs = {}
count = 0
for name in os.listdir(source_directory):
match = re.search('^Coverage (?P<attempt>[0-9]+) (?P<label>.+)$', name)
label = match.group('label')
attempt = int(match.group('attempt'))
jobs[label] = max(attempt, jobs.get(label, 0))
for label, attempt in jobs.items():
name = 'Coverage {attempt} {label}'.format(label=label, attempt=attempt)
source = os.path.join(source_directory, name)
source_files = os.listdir(source)
for source_file in source_files:
source_path = os.path.join(source, source_file)
destination_path = os.path.join(destination_directory, source_file + '.' + label)
print('"%s" -> "%s"' % (source_path, destination_path))
shutil.copyfile(source_path, destination_path)
count += 1
print('Coverage file count: %d' % count)
print('##vso[task.setVariable variable=coverageFileCount]%d' % count)
print('##vso[task.setVariable variable=outputPath]%s' % output_path)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Check the test results and set variables for use in later steps.
set -o pipefail -eu
if [[ "$PWD" =~ /ansible_collections/ ]]; then
output_path="tests/output"
else
output_path="test/results"
fi
echo "##vso[task.setVariable variable=outputPath]${output_path}"
if compgen -G "${output_path}"'/junit/*.xml' > /dev/null; then
echo "##vso[task.setVariable variable=haveTestResults]true"
fi
if compgen -G "${output_path}"'/bot/ansible-test-*' > /dev/null; then
echo "##vso[task.setVariable variable=haveBotResults]true"
fi
if compgen -G "${output_path}"'/coverage/*' > /dev/null; then
echo "##vso[task.setVariable variable=haveCoverageData]true"
fi

View File

@@ -0,0 +1,105 @@
#!/usr/bin/env python
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
"""
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

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Generate code coverage reports for uploading to Azure Pipelines and codecov.io.
set -o pipefail -eu
PATH="${PWD}/bin:${PATH}"
if [[ "$(ansible --version)" =~ \ 2\.9\. ]]; then
exit
fi
if ! ansible-test --help >/dev/null 2>&1; then
# Install the devel version of ansible-test for generating code coverage reports.
# This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs).
# Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used.
pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
fi
ansible-test coverage xml --group-by command --stub --venv --venv-system-site-packages --color -v

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Configure the test environment and run the tests.
set -o pipefail -eu
entry_point="$1"
test="$2"
read -r -a coverage_branches <<< "$3" # space separated list of branches to run code coverage on for scheduled builds
export COMMIT_MESSAGE
export COMPLETE
export COVERAGE
export IS_PULL_REQUEST
if [ "${SYSTEM_PULLREQUEST_TARGETBRANCH:-}" ]; then
IS_PULL_REQUEST=true
COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2)
else
IS_PULL_REQUEST=
COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD)
fi
COMPLETE=
COVERAGE=
if [ "${BUILD_REASON}" = "Schedule" ]; then
COMPLETE=yes
if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then
COVERAGE=yes
fi
fi
"${entry_point}" "${test}" 2>&1 | "$(dirname "$0")/time-command.py"

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env python
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
"""Prepends a relative timestamp to each input line from stdin and writes it to stdout."""
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import sys
import time
def main():
"""Main program entry point."""
start = time.time()
sys.stdin.reconfigure(errors='surrogateescape')
sys.stdout.reconfigure(errors='surrogateescape')
for line in sys.stdin:
seconds = time.time() - start
sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line))
sys.stdout.flush()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,44 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# This template adds a job for processing code coverage data.
# It will upload results to Azure Pipelines and codecov.io.
# Use it from a job stage that completes after all other jobs have completed.
# This can be done by placing it in a separate summary stage that runs after the test stage(s) have completed.
jobs:
- job: Coverage
displayName: Code Coverage
container: default
workspace:
clean: all
steps:
- checkout: self
fetchDepth: $(fetchDepth)
path: $(checkoutPath)
- task: DownloadPipelineArtifact@2
displayName: Download Coverage Data
inputs:
path: coverage/
patterns: "Coverage */*=coverage.combined"
- bash: .azure-pipelines/scripts/combine-coverage.py coverage/
displayName: Combine Coverage Data
- bash: .azure-pipelines/scripts/report-coverage.sh
displayName: Generate Coverage Report
condition: gt(variables.coverageFileCount, 0)
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
# Azure Pipelines only accepts a single coverage data file.
# That means only Python or PowerShell coverage can be uploaded, but not both.
# Set the "pipelinesCoverage" variable to determine which type is uploaded.
# Use "coverage" for Python and "coverage-powershell" for PowerShell.
summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
displayName: Publish to Azure Pipelines
condition: gt(variables.coverageFileCount, 0)
- bash: .azure-pipelines/scripts/publish-codecov.py "$(outputPath)"
displayName: Publish to codecov.io
condition: gt(variables.coverageFileCount, 0)
continueOnError: true

View File

@@ -0,0 +1,60 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# This template uses the provided targets and optional groups to generate a matrix which is then passed to the test template.
# If this matrix template does not provide the required functionality, consider using the test template directly instead.
parameters:
# A required list of dictionaries, one per test target.
# Each item in the list must contain a "test" or "name" key.
# Both may be provided. If one is omitted, the other will be used.
- name: targets
type: object
# An optional list of values which will be used to multiply the targets list into a matrix.
# Values can be strings or numbers.
- name: groups
type: object
default: []
# An optional format string used to generate the job name.
# - {0} is the name of an item in the targets list.
- name: nameFormat
type: string
default: "{0}"
# An optional format string used to generate the test name.
# - {0} is the name of an item in the targets list.
- name: testFormat
type: string
default: "{0}"
# An optional format string used to add the group to the job name.
# {0} is the formatted name of an item in the targets list.
# {{1}} is the group -- be sure to include the double "{{" and "}}".
- name: nameGroupFormat
type: string
default: "{0} - {{1}}"
# An optional format string used to add the group to the test name.
# {0} is the formatted test of an item in the targets list.
# {{1}} is the group -- be sure to include the double "{{" and "}}".
- name: testGroupFormat
type: string
default: "{0}/{{1}}"
jobs:
- template: test.yml
parameters:
jobs:
- ${{ if eq(length(parameters.groups), 0) }}:
- ${{ each target in parameters.targets }}:
- name: ${{ format(parameters.nameFormat, coalesce(target.name, target.test)) }}
test: ${{ format(parameters.testFormat, coalesce(target.test, target.name)) }}
- ${{ if not(eq(length(parameters.groups), 0)) }}:
- ${{ each group in parameters.groups }}:
- ${{ each target in parameters.targets }}:
- name: ${{ format(format(parameters.nameGroupFormat, parameters.nameFormat), coalesce(target.name, target.test), group) }}
test: ${{ format(format(parameters.testGroupFormat, parameters.testFormat), coalesce(target.test, target.name), group) }}

View File

@@ -0,0 +1,50 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# This template uses the provided list of jobs to create test one or more test jobs.
# It can be used directly if needed, or through the matrix template.
parameters:
# A required list of dictionaries, one per test job.
# Each item in the list must contain a "job" and "name" key.
- name: jobs
type: object
jobs:
- ${{ each job in parameters.jobs }}:
- job: test_${{ replace(replace(replace(job.test, '/', '_'), '.', '_'), '-', '_') }}
displayName: ${{ job.name }}
container: default
workspace:
clean: all
steps:
- checkout: self
fetchDepth: $(fetchDepth)
path: $(checkoutPath)
- bash: .azure-pipelines/scripts/run-tests.sh "$(entryPoint)" "${{ job.test }}" "$(coverageBranches)"
displayName: Run Tests
- bash: .azure-pipelines/scripts/process-results.sh
condition: succeededOrFailed()
displayName: Process Results
- bash: .azure-pipelines/scripts/aggregate-coverage.sh "$(Agent.TempDirectory)"
condition: eq(variables.haveCoverageData, 'true')
displayName: Aggregate Coverage Data
- task: PublishTestResults@2
condition: eq(variables.haveTestResults, 'true')
inputs:
testResultsFiles: "$(outputPath)/junit/*.xml"
displayName: Publish Test Results
- task: PublishPipelineArtifact@1
condition: eq(variables.haveBotResults, 'true')
displayName: Publish Bot Results
inputs:
targetPath: "$(outputPath)/bot/"
artifactName: "Bot $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)"
- task: PublishPipelineArtifact@1
condition: eq(variables.haveCoverageData, 'true')
displayName: Publish Coverage Data
inputs:
targetPath: "$(Agent.TempDirectory)/coverage/"
artifactName: "Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)"

11
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,11 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"

9
.github/patchback.yml vendored Normal file
View File

@@ -0,0 +1,9 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
backport_branch_prefix: patchback/backports/
backport_label_prefix: backport-
target_branch_prefix: stable-
...

View File

@@ -3,13 +3,20 @@
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# For the comprehensive list of the inputs supported by the ansible-community/ansible-test-gh-action GitHub Action, see
# https://github.com/marketplace/actions/ansible-test
name: EOL CI
on:
# Run EOL CI against all pushes (direct commits, also merged PRs), Pull Requests
push:
branches:
- stable-1
- main
- stable-*
pull_request:
# Run EOL CI once per day (at 09:00 UTC)
schedule:
- cron: '0 9 * * *'
concurrency:
# Make sure there is at most one active run per PR, but do not cancel any non-PR runs
@@ -27,6 +34,7 @@ jobs:
- '2.11'
- '2.12'
- '2.13'
- '2.14'
# Ansible-test on various stable branches does not yet work well with cgroups v2.
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
# image for these stable branches. The list of branches where this is necessary will
@@ -68,6 +76,7 @@ jobs:
- '2.11'
- '2.12'
- '2.13'
- '2.14'
steps:
- name: >-
@@ -83,7 +92,15 @@ jobs:
testing-type: units
integration:
runs-on: ${{ matrix.runs_on }}
# Ansible-test on various stable branches does not yet work well with cgroups v2.
# Since ubuntu-latest now uses Ubuntu 22.04, we need to fall back to the ubuntu-20.04
# image for these stable branches. The list of branches where this is necessary will
# shrink over time, check out https://github.com/ansible-collections/news-for-maintainers/issues/28
# for the latest list.
runs-on: >-
${{ contains(fromJson(
'["2.9", "2.10", "2.11"]'
), matrix.ansible) && 'ubuntu-20.04' || 'ubuntu-latest' }}
name: EOL I (Ⓐ${{ matrix.ansible }}+${{ matrix.docker }}+py${{ matrix.python }}:${{ matrix.target }})
strategy:
fail-fast: false
@@ -96,171 +113,152 @@ jobs:
- ''
target:
- ''
runs_on:
- ubuntu-latest
exclude:
- ansible: ''
include:
# 2.9
- ansible: '2.9'
docker: centos6
docker: ubuntu1804
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
#- ansible: '2.9'
# docker: centos7
# python: ''
# target: shippable/posix/group1/
# runs_on: ubuntu-20.04
- ansible: '2.9'
docker: ubuntu1604
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
target: azp/posix/1/
- ansible: '2.9'
docker: ubuntu1804
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
target: azp/posix/2/
- ansible: '2.9'
docker: default
python: '2.7'
target: shippable/cloud/group1/
runs_on: ubuntu-20.04
target: azp/generic/1/
- ansible: '2.9'
docker: default
python: '2.7'
target: azp/generic/2/
# 2.10
- ansible: '2.10'
docker: centos6
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
target: azp/posix/1/
- ansible: '2.10'
docker: ubuntu1604
docker: centos6
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
target: azp/posix/2/
- ansible: '2.10'
docker: default
python: '3.6'
target: shippable/cloud/group1/
runs_on: ubuntu-20.04
target: azp/generic/1/
- ansible: '2.10'
docker: default
python: '3.6'
target: azp/generic/2/
# 2.11
#- ansible: '2.11'
# docker: centos7
# python: ''
# target: shippable/posix/group1/
# runs_on: ubuntu-20.04
- ansible: '2.11'
docker: opensuse15py2
docker: alpine3
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
target: azp/posix/1/
- ansible: '2.11'
docker: ubuntu1804
docker: alpine3
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
target: azp/posix/2/
- ansible: '2.11'
docker: default
python: '3.8'
target: shippable/cloud/group1/
runs_on: ubuntu-20.04
target: azp/generic/1/
- ansible: '2.11'
docker: default
python: '3.8'
target: azp/generic/2/
# 2.12
- ansible: '2.12'
docker: centos6
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
target: azp/posix/1/
- ansible: '2.12'
docker: centos6
python: ''
target: azp/posix/2/
- ansible: '2.12'
docker: fedora33
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
target: azp/posix/1/
- ansible: '2.12'
docker: opensuse15
docker: fedora33
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
- ansible: '2.12'
docker: ubuntu2004
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
target: azp/posix/2/
- ansible: '2.12'
docker: default
python: '2.6'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
target: azp/generic/1/
- ansible: '2.12'
docker: default
python: '3.9'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
target: azp/generic/2/
# 2.13
- ansible: '2.13'
docker: centos7
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-20.04
- ansible: '2.13'
docker: fedora34
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
docker: fedora35
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
docker: opensuse15py2
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
target: azp/posix/1/
- ansible: '2.13'
docker: opensuse15
docker: opensuse15py2
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
target: azp/posix/2/
- ansible: '2.13'
docker: fedora35
python: ''
target: azp/posix/1/
- ansible: '2.13'
docker: fedora35
python: ''
target: azp/posix/2/
- ansible: '2.13'
docker: fedora34
python: ''
target: azp/posix/1/
- ansible: '2.13'
docker: fedora34
python: ''
target: azp/posix/2/
- ansible: '2.13'
docker: ubuntu1804
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
target: azp/posix/1/
- ansible: '2.13'
docker: ubuntu1804
python: ''
target: azp/posix/2/
- ansible: '2.13'
docker: alpine3
python: ''
target: azp/posix/1/
- ansible: '2.13'
docker: alpine3
python: ''
target: azp/posix/2/
- ansible: '2.13'
docker: default
python: '3.8'
target: azp/generic/1/
- ansible: '2.13'
docker: default
python: '3.8'
target: azp/generic/2/
# 2.14
- ansible: '2.14'
docker: ubuntu2004
python: ''
target: shippable/posix/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
docker: default
python: '2.7'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
docker: default
python: '3.5'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
docker: default
python: '3.6'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
docker: default
python: '3.7'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
target: azp/posix/1/
- ansible: '2.14'
docker: ubuntu2004
python: ''
target: azp/posix/2/
- ansible: '2.14'
docker: default
python: '3.9'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
- ansible: '2.13'
target: azp/generic/1/
- ansible: '2.14'
docker: default
python: '3.10'
target: shippable/cloud/group1/
runs_on: ubuntu-latest
python: '3.9'
target: azp/generic/2/
steps:
- name: >-
Perform integration testing against
@@ -284,30 +282,3 @@ jobs:
target: ${{ matrix.target }}
target-python-version: ${{ matrix.python }}
testing-type: integration
extra-sanity:
name: Extra Sanity
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
path: ansible_collections/community/crypto
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install ansible-core
run: pip install https://github.com/ansible/ansible/archive/stable-2.13.tar.gz --disable-pip-version-check
- name: Install collection dependencies
run: >-
ansible-galaxy collection install -p .
git+https://github.com/ansible-collections/community.internal_test_tools.git,main
- name: Run sanity tests
run: ../../community/internal_test_tools/tools/run.py --color
working-directory: ./ansible_collections/community/crypto

95
.github/workflows/docs-pr.yml vendored Normal file
View File

@@ -0,0 +1,95 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
name: Collection Docs
concurrency:
group: docs-pr-${{ github.head_ref }}
cancel-in-progress: true
on:
pull_request_target:
types: [opened, synchronize, reopened, closed]
env:
GHP_BASE_URL: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}
jobs:
build-docs:
permissions:
contents: read
name: Build Ansible Docs
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-pr.yml@main
with:
collection-name: community.crypto
init-lenient: false
init-fail-on-error: true
squash-hierarchy: true
init-project: Community.Crypto Collection
init-copyright: Community.Crypto Contributors
init-title: Community.Crypto Collection Documentation
init-html-short-title: Community.Crypto Collection Docs
init-extra-html-theme-options: |
documentation_home_url=https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/branch/main/
render-file-line: '> * `$<status>` [$<path_tail>](https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/pr/${{ github.event.number }}/$<path_tail>)'
publish-docs-gh-pages:
# for now we won't run this on forks
if: github.repository == 'ansible-collections/community.crypto'
permissions:
contents: write
pages: write
id-token: write
needs: [build-docs]
name: Publish Ansible Docs
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
with:
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
action: ${{ (github.event.action == 'closed' || needs.build-docs.outputs.changed != 'true') && 'teardown' || 'publish' }}
publish-gh-pages-branch: true
secrets:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
comment:
permissions:
pull-requests: write
runs-on: ubuntu-latest
needs: [build-docs, publish-docs-gh-pages]
name: PR comments
steps:
- name: PR comment
uses: ansible-community/github-docs-build/actions/ansible-docs-build-comment@main
with:
body-includes: '## Docs Build'
reactions: heart
action: ${{ needs.build-docs.outputs.changed != 'true' && 'remove' || '' }}
on-closed-body: |
## Docs Build 📝
This PR is closed and any previously published docsite has been unpublished.
on-merged-body: |
## Docs Build 📝
Thank you for contribution!✨
This PR has been merged and the docs are now incorporated into `main`:
${{ env.GHP_BASE_URL }}/branch/main
body: |
## Docs Build 📝
Thank you for contribution!✨
The docs for **this PR** have been published here:
${{ env.GHP_BASE_URL }}/pr/${{ github.event.number }}
You can compare to the docs for the `main` branch here:
${{ env.GHP_BASE_URL }}/branch/main
The docsite for **this PR** is also available for download as an artifact from this run:
${{ needs.build-docs.outputs.artifact-url }}
File changes:
${{ needs.build-docs.outputs.diff-files-rendered }}
${{ needs.build-docs.outputs.diff-rendered }}

55
.github/workflows/docs-push.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
name: Collection Docs
concurrency:
group: docs-push-${{ github.sha }}
cancel-in-progress: true
on:
push:
branches:
- main
- stable-*
tags:
- '*'
# Run CI once per day (at 09:00 UTC)
schedule:
- cron: '0 9 * * *'
# Allow manual trigger (for newer antsibull-docs, sphinx-ansible-theme, ... versions)
workflow_dispatch:
jobs:
build-docs:
permissions:
contents: read
name: Build Ansible Docs
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-push.yml@main
with:
collection-name: community.crypto
init-lenient: false
init-fail-on-error: true
squash-hierarchy: true
init-project: Community.Crypto Collection
init-copyright: Community.Crypto Contributors
init-title: Community.Crypto Collection Documentation
init-html-short-title: Community.Crypto Collection Docs
init-extra-html-theme-options: |
documentation_home_url=https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/branch/main/
publish-docs-gh-pages:
# for now we won't run this on forks
if: github.repository == 'ansible-collections/community.crypto'
permissions:
contents: write
pages: write
id-token: write
needs: [build-docs]
name: Publish Ansible Docs
uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main
with:
artifact-name: ${{ needs.build-docs.outputs.artifact-name }}
publish-gh-pages-branch: true
secrets:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

179
.github/workflows/ee.yml vendored Normal file
View File

@@ -0,0 +1,179 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
name: execution environment
on:
# Run CI against all pushes (direct commits, also merged PRs), Pull Requests
push:
branches:
- main
- stable-*
pull_request:
# Run CI once per day (at 04:45 UTC)
# This ensures that even if there haven't been commits that we are still testing against latest version of ansible-builder
schedule:
- cron: '45 4 * * *'
env:
NAMESPACE: community
COLLECTION_NAME: crypto
jobs:
build:
name: Build and test EE (${{ matrix.name }})
strategy:
fail-fast: false
matrix:
name:
- ''
ansible_core:
- ''
ansible_runner:
- ''
base_image:
- ''
pre_base:
- ''
extra_vars:
- ''
other_deps:
- ''
exclude:
- ansible_core: ''
include:
- name: ansible-core devel @ RHEL UBI 9
ansible_core: https://github.com/ansible/ansible/archive/devel.tar.gz
ansible_runner: ansible-runner
other_deps: |2
python_interpreter:
package_system: python3.11 python3.11-pip python3.11-wheel python3.11-cryptography
python_path: "/usr/bin/python3.11"
base_image: docker.io/redhat/ubi9:latest
pre_base: '"#"'
# For some reason ansible-builder will not install EPEL dependencies on RHEL
extra_vars: -e has_no_pyopenssl=true
- name: ansible-core 2.15 @ Rocky Linux 9
ansible_core: https://github.com/ansible/ansible/archive/stable-2.15.tar.gz
ansible_runner: ansible-runner
base_image: quay.io/rockylinux/rockylinux:9
pre_base: RUN dnf install -y epel-release
# For some reason ansible-builder will not install EPEL dependencies on Rocky Linux
extra_vars: -e has_no_pyopenssl=true
- name: ansible-core 2.14 @ CentOS Stream 9
ansible_core: https://github.com/ansible/ansible/archive/stable-2.14.tar.gz
ansible_runner: ansible-runner
base_image: quay.io/centos/centos:stream9
pre_base: RUN dnf install -y epel-release epel-next-release
# For some reason, PyOpenSSL is **broken** on CentOS Stream 9 / EPEL
extra_vars: -e has_no_pyopenssl=true
- name: ansible-core 2.13 @ RHEL UBI 8
ansible_core: https://github.com/ansible/ansible/archive/stable-2.13.tar.gz
ansible_runner: ansible-runner
other_deps: |2
python_interpreter:
package_system: python39 python39-pip python39-wheel python39-cryptography
base_image: docker.io/redhat/ubi8:latest
pre_base: '"#"'
# We don't have PyOpenSSL for Python 3.9
extra_vars: -e has_no_pyopenssl=true
runs-on: ubuntu-latest
steps:
- name: Check out code
uses: actions/checkout@v4
with:
path: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install ansible-builder and ansible-navigator
run: pip install ansible-builder ansible-navigator
- name: Verify requirements
run: ansible-builder introspect --sanitize .
- name: Make sure galaxy.yml has version entry
run: >-
python -c
'import yaml ;
f = open("galaxy.yml", "rb") ;
data = yaml.safe_load(f) ;
f.close() ;
data["version"] = data.get("version") or "0.0.1" ;
f = open("galaxy.yml", "wb") ;
f.write(yaml.dump(data).encode("utf-8")) ;
f.close() ;
'
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
- name: Build collection
run: |
ansible-galaxy collection build --output-path ../../../
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}
- name: Create files for building execution environment
run: |
COLLECTION_FILENAME="$(ls "${{ env.NAMESPACE }}-${{ env.COLLECTION_NAME }}"-*.tar.gz)"
# EE config
cat > execution-environment.yml <<EOF
---
version: 3
dependencies:
ansible_core:
package_pip: ${{ matrix.ansible_core }}
ansible_runner:
package_pip: ${{ matrix.ansible_runner }}
galaxy: requirements.yml
${{ matrix.other_deps }}
images:
base_image:
name: ${{ matrix.base_image }}
additional_build_files:
- src: ${COLLECTION_FILENAME}
dest: src
additional_build_steps:
prepend_base:
- ${{ matrix.pre_base }}
EOF
echo "::group::execution-environment.yml"
cat execution-environment.yml
echo "::endgroup::"
# Requirements
cat > requirements.yml <<EOF
---
collections:
- name: src/${COLLECTION_FILENAME}
type: file
EOF
echo "::group::requirements.yml"
cat requirements.yml
echo "::endgroup::"
- name: Build image based on ${{ matrix.base_image }}
run: |
ansible-builder build --verbosity 3 --tag test-ee:latest --container-runtime podman
- name: Show images
run: podman image ls
- name: Run basic tests
run: >
ansible-navigator run
--mode stdout
--container-engine podman
--pull-policy never
--set-environment-variable ANSIBLE_PRIVATE_ROLE_VARS=true
--execution-environment-image test-ee:latest
-v
all.yml
${{ matrix.extra_vars }}
working-directory: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }}/tests/ee

32
.github/workflows/reuse.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
name: Verify REUSE
on:
push:
branches: [main]
pull_request:
branches: [main]
# Run CI once per day (at 04:45 UTC)
schedule:
- cron: '45 4 * * *'
jobs:
check:
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Remove some files before checking REUSE compliance
run: |
rm -f tests/integration/targets/*/files/*.pem
rm -f tests/integration/targets/*/files/roots/*.pem
- name: REUSE Compliance Check
uses: fsfe/reuse-action@v4

4
.gitignore vendored
View File

@@ -1,3 +1,7 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
# Community.crypt specific things
/changelogs/.plugin-cache.yaml

5
.reuse/dep5 Normal file
View File

@@ -0,0 +1,5 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files: changelogs/fragments/*
Copyright: Ansible Project
License: GPL-3.0-or-later

File diff suppressed because it is too large Load Diff

View File

@@ -4,23 +4,57 @@ Community Crypto Release Notes
.. contents:: Topics
v1.9.26
v2.21.0
=======
Release Summary
---------------
Last release.
Feature release.
Major Changes
Minor Changes
-------------
- The 1.x.y release train of community.crypto is **End of Life**. There will be no further community.crypto 1.x.y releases.
Please upgrade to community.crypto 2.x.y.
- certificate_complete_chain - add ability to identify Ed25519 and Ed448 complete chains (https://github.com/ansible-collections/community.crypto/pull/777).
- get_certificate - adds ``tls_ctx_options`` option for specifying SSL CTX options (https://github.com/ansible-collections/community.crypto/pull/779).
- get_certificate - allow to obtain the certificate chain sent by the server, and the one used for validation, with the new ``get_certificate_chain`` option. Note that this option only works if the module is run with Python 3.10 or newer (https://github.com/ansible-collections/community.crypto/issues/568, https://github.com/ansible-collections/community.crypto/pull/784).
Thanks to everyone who contributed to community.crypto 1.x.y!
v2.20.0
=======
v1.9.25
Release Summary
---------------
Feature and bugfix release.
The deprecations in this release are only relevant for collections that use shared
code or docs fragments from this collection.
Minor Changes
-------------
- acme_certificate - add ``include_renewal_cert_id`` option to allow requesting renewal of a specific certificate according to the current ACME Renewal Information specification draft (https://github.com/ansible-collections/community.crypto/pull/739).
Deprecated Features
-------------------
- acme documentation fragment - the default ``community.crypto.acme[.documentation]`` docs fragment is deprecated and will be removed from community.crypto 3.0.0. Replace it with both the new ``community.crypto.acme.basic`` and ``community.crypto.acme.account`` fragments (https://github.com/ansible-collections/community.crypto/pull/735).
- acme.backends module utils - the ``get_cert_information()`` method for a ACME crypto backend must be implemented from community.crypto 3.0.0 on (https://github.com/ansible-collections/community.crypto/pull/736).
- crypto.module_backends.common module utils - the ``crypto.module_backends.common`` module utils is deprecated and will be removed from community.crypto 3.0.0. Use the improved ``argspec`` module util instead (https://github.com/ansible-collections/community.crypto/pull/749).
Bugfixes
--------
- x509_crl, x509_certificate, x509_certificate_info - when parsing absolute timestamps which omitted the second count, the first digit of the minutes was used as a one-digit minutes count, and the second digit of the minutes as a one-digit second count (https://github.com/ansible-collections/community.crypto/pull/745).
New Modules
-----------
- community.crypto.acme_ari_info - Retrieves ACME Renewal Information (ARI) for a certificate.
- community.crypto.acme_certificate_deactivate_authz - Deactivate all authz for an ACME v2 order.
- community.crypto.acme_certificate_renewal_info - Determine whether a certificate should be renewed or not.
v2.19.1
=======
Release Summary
@@ -31,25 +65,102 @@ Bugfix release.
Bugfixes
--------
- crypto.math module utils - change return values for ``quick_is_not_prime()`` for special cases that do not appear when using the collection (https://github.com/ansible-collections/community.crypto/pull/733).
- crypto.math module utils - change return values for ``quick_is_not_prime()`` and ``convert_int_to_bytes(0, 0)`` for special cases that do not appear when using the collection (https://github.com/ansible-collections/community.crypto/pull/733).
- ecs_certificate - fixed ``csr`` option to be empty and allow renewal of a specific certificate according to the Renewal Information specification (https://github.com/ansible-collections/community.crypto/pull/740).
- x509_certificate - since community.crypto 2.19.0 the module was no longer idempotent with respect to ``not_before`` and ``not_after`` times. This is now fixed (https://github.com/ansible-collections/community.crypto/issues/753, https://github.com/ansible-collections/community.crypto/pull/754).
v1.9.24
v2.19.0
=======
Release Summary
---------------
Bugfix release.
Bugfix and feature release.
Minor Changes
-------------
- When using cryptography >= 42.0.0, use offset-aware ``datetime.datetime`` objects (with timezone UTC) instead of offset-naive UTC timestamps (https://github.com/ansible-collections/community.crypto/issues/726, https://github.com/ansible-collections/community.crypto/pull/727).
- openssh_cert - avoid UTC functions deprecated in Python 3.12 when using Python 3 (https://github.com/ansible-collections/community.crypto/pull/727).
Deprecated Features
-------------------
- acme.backends module utils - from community.crypto on, all implementations of ``CryptoBackend`` must override ``get_ordered_csr_identifiers()``. The current default implementation, which simply sorts the result of ``get_csr_identifiers()``, will then be removed (https://github.com/ansible-collections/community.crypto/pull/725).
Bugfixes
--------
- acme_certificate - respect the order of the CNAME and SAN identifiers that are passed on when creating an ACME order (https://github.com/ansible-collections/community.crypto/issues/723, https://github.com/ansible-collections/community.crypto/pull/725).
New Modules
-----------
- community.crypto.x509_certificate_convert - Convert X.509 certificates
v2.18.0
=======
Release Summary
---------------
Bugfix and feature release.
Minor Changes
-------------
- x509_crl - the new option ``serial_numbers`` allow to configure in which format serial numbers can be provided to ``revoked_certificates[].serial_number``. The default is as integers (``serial_numbers=integer``) for backwards compatibility; setting ``serial_numbers=hex-octets`` allows to specify colon-separated hex octet strings like ``00:11:22:FF`` (https://github.com/ansible-collections/community.crypto/issues/687, https://github.com/ansible-collections/community.crypto/pull/715).
Deprecated Features
-------------------
- openssl_csr_pipe, openssl_privatekey_pipe, x509_certificate_pipe - the current behavior of check mode is deprecated and will change in community.crypto 3.0.0. The current behavior is similar to the modules without ``_pipe``: if the object needs to be (re-)generated, only the ``changed`` status is set, but the object is not updated. From community.crypto 3.0.0 on, the modules will ignore check mode and always act as if check mode is not active. This behavior can already achieved now by adding ``check_mode: false`` to the task. If you think this breaks your use-case of this module, please `create an issue in the community.crypto repository <https://github.com/ansible-collections/community.crypto/issues/new/choose>`__ (https://github.com/ansible-collections/community.crypto/issues/712, https://github.com/ansible-collections/community.crypto/pull/714).
Bugfixes
--------
- luks_device - fixed module a bug that prevented using ``remove_keyslot`` with the value ``0`` (https://github.com/ansible-collections/community.crypto/pull/710).
- luks_device - fixed module falsely outputting ``changed=false`` when trying to add a new slot with a key that is already present in another slot. The module now rejects adding keys that are already present in another slot (https://github.com/ansible-collections/community.crypto/pull/710).
- luks_device - fixed testing of LUKS passphrases in when specifying a keyslot for cryptsetup version 2.0.3. The output of this cryptsetup version slightly differs from later versions (https://github.com/ansible-collections/community.crypto/pull/710).
New Plugins
-----------
Filter
~~~~~~
- community.crypto.parse_serial - Convert a serial number as a colon-separated list of hex numbers to an integer
- community.crypto.to_serial - Convert an integer to a colon-separated list of hex numbers
v2.17.1
=======
Release Summary
---------------
Bugfix release for compatibility with cryptography 42.0.0.
Bugfixes
--------
- openssl_dhparam - was using an internal function instead of the public API to load DH param files when using the ``cryptography`` backend. The internal function was removed in cryptography 42.0.0. The module now uses the public API, which has been available since support for DH params was added to cryptography (https://github.com/ansible-collections/community.crypto/pull/698).
- openssl_privatekey_info - ``check_consistency=true`` no longer works for RSA keys with cryptography 42.0.0+ (https://github.com/ansible-collections/community.crypto/pull/701).
- x509_certificate - when using the PyOpenSSL backend with ``provider=assertonly``, better handle unexpected errors when validating private keys (https://github.com/ansible-collections/community.crypto/pull/704).
- openssl_privatekey_info - ``check_consistency=true`` now reports a warning if it cannot determine consistency (https://github.com/ansible-collections/community.crypto/pull/705).
v1.9.23
v2.17.0
=======
Release Summary
---------------
Feature release.
Minor Changes
-------------
- luks_device - add allow discards option (https://github.com/ansible-collections/community.crypto/pull/693).
v2.16.2
=======
Release Summary
@@ -60,9 +171,43 @@ Bugfix release.
Bugfixes
--------
- acme_* modules - directly react on bad return data for account creation/retrieval/updating requests (https://github.com/ansible-collections/community.crypto/pull/682).
- acme_* modules - fix improved error reporting in case of socket errors, bad status lines, and unknown connection errors (https://github.com/ansible-collections/community.crypto/pull/684).
- acme_* modules - increase number of retries from 5 to 10 to increase stability with unstable ACME endpoints (https://github.com/ansible-collections/community.crypto/pull/685).
- acme_* modules - make account registration handling more flexible to accept 404 instead of 400 send by DigiCert's ACME endpoint when an account does not exist (https://github.com/ansible-collections/community.crypto/pull/681).
v2.16.1
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- acme_* modules - also retry requests in case of socket errors, bad status lines, and unknown connection errors; improve error messages in these cases (https://github.com/ansible-collections/community.crypto/issues/680).
v2.16.0
=======
Release Summary
---------------
Bugfix release.
Minor Changes
-------------
- luks_devices - add new options ``keyslot``, ``new_keyslot``, and ``remove_keyslot`` to allow adding/removing keys to/from specific keyslots (https://github.com/ansible-collections/community.crypto/pull/664).
Bugfixes
--------
- openssl_pkcs12 - modify autodetect to not detect pyOpenSSL >= 23.3.0, which removed PKCS#12 support (https://github.com/ansible-collections/community.crypto/pull/666).
v1.9.22
v2.15.1
=======
Release Summary
@@ -73,63 +218,323 @@ Bugfix release.
Bugfixes
--------
- acme_* modules - correctly handle error documents without ``type`` (https://github.com/ansible-collections/community.crypto/issues/651, https://github.com/ansible-collections/community.crypto/pull/652).
v2.15.0
=======
Release Summary
---------------
Bugfix and feature release.
Minor Changes
-------------
- openssh_keypair - fail when comment cannot be updated (https://github.com/ansible-collections/community.crypto/pull/646).
Deprecated Features
-------------------
- get_certificate - the default ``false`` of the ``asn1_base64`` option is deprecated and will change to ``true`` in community.crypto 3.0.0 (https://github.com/ansible-collections/community.crypto/pull/600).
Bugfixes
--------
- openssh_cert, openssh_keypair - the modules ignored return codes of ``ssh`` and ``ssh-keygen`` in some cases (https://github.com/ansible-collections/community.crypto/issues/645, https://github.com/ansible-collections/community.crypto/pull/646).
- openssh_keypair - fix comment updating for OpenSSH before 6.5 (https://github.com/ansible-collections/community.crypto/pull/646).
New Plugins
-----------
Filter
~~~~~~
- community.crypto.gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key
Lookup
~~~~~~
- community.crypto.gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key file
v2.14.1
=======
Release Summary
---------------
Bugfix and maintenance release with updated documentation.
From this version on, community.crypto is using the new `Ansible semantic markup
<https://docs.ansible.com/ansible/devel/dev_guide/developing_modules_documenting.html#semantic-markup-within-module-documentation>`__
in its documentation. If you look at documentation with the ansible-doc CLI tool
from ansible-core before 2.15, please note that it does not render the markup
correctly. You should be still able to read it in most cases, but you need
ansible-core 2.15 or later to see it as it is intended. Alternatively you can
look at `the devel docsite <https://docs.ansible.com/ansible/devel/collections/community/crypto/>`__
for the rendered HTML version of the documentation of the latest release.
Bugfixes
--------
- Fix PEM detection/identification to also accept random other lines before the line starting with ``-----BEGIN`` (https://github.com/ansible-collections/community.crypto/issues/627, https://github.com/ansible-collections/community.crypto/pull/628).
Known Issues
------------
- Ansible markup will show up in raw form on ansible-doc text output for ansible-core before 2.15. If you have trouble deciphering the documentation markup, please upgrade to ansible-core 2.15 (or newer), or read the HTML documentation on https://docs.ansible.com/ansible/devel/collections/community/crypto/.
v2.14.0
=======
Release Summary
---------------
Feature release.
Minor Changes
-------------
- acme_certificate - allow to use no challenge by providing ``no challenge`` for the ``challenge`` option. This is needed for ACME servers where validation is done without challenges (https://github.com/ansible-collections/community.crypto/issues/613, https://github.com/ansible-collections/community.crypto/pull/615).
- acme_certificate - validate and wait for challenges in parallel instead handling them one after another (https://github.com/ansible-collections/community.crypto/pull/617).
- x509_certificate_info - added support for certificates in DER format when using ``path`` parameter (https://github.com/ansible-collections/community.crypto/issues/603).
v2.13.1
=======
Release Summary
---------------
Bugfix release.
Bugfixes
--------
- execution environment definition - fix installation of ``python3-pyOpenSSL`` package on CentOS and RHEL (https://github.com/ansible-collections/community.crypto/pull/606).
- execution environment definition - fix source of ``python3-pyOpenSSL`` package for Rocky Linux 9+ (https://github.com/ansible-collections/community.crypto/pull/606).
v2.13.0
=======
Release Summary
---------------
Bugfix and maintenance release.
Minor Changes
-------------
- x509_crl - the ``crl_mode`` option has been added to replace the existing ``mode`` option (https://github.com/ansible-collections/community.crypto/issues/596).
Deprecated Features
-------------------
- x509_crl - the ``mode`` option is deprecated; use ``crl_mode`` instead. The ``mode`` option will change its meaning in community.crypto 3.0.0, and will refer to the CRL file's mode instead (https://github.com/ansible-collections/community.crypto/issues/596).
Bugfixes
--------
- openssh_keypair - always generate a new key pair if the private key does not exist. Previously, the module would fail when ``regenerate=fail`` without an existing key, contradicting the documentation (https://github.com/ansible-collections/community.crypto/pull/598).
- x509_crl - remove problem with ansible-core 2.16 due to ``AnsibleModule`` is now validating the ``mode`` parameter's values (https://github.com/ansible-collections/community.crypto/issues/596).
v1.9.21
v2.12.0
=======
Release Summary
---------------
Bugfix release.
Feature release.
Minor Changes
-------------
- get_certificate - add ``asn1_base64`` option to control whether the ASN.1 included in the ``extensions`` return value is binary data or Base64 encoded (https://github.com/ansible-collections/community.crypto/pull/592).
v2.11.1
=======
Release Summary
---------------
Maintenance release with improved documentation.
v2.11.0
=======
Release Summary
---------------
Feature and bugfix release.
Minor Changes
-------------
- get_certificate - adds ``ciphers`` option for custom cipher selection (https://github.com/ansible-collections/community.crypto/pull/571).
Bugfixes
--------
- action plugin helper - fix handling of deprecations for ansible-core 2.14.2 (https://github.com/ansible-collections/community.crypto/pull/572).
- openssl_csr, openssl_csr_pipe - prevent invalid values for ``crl_distribution_points`` that do not have one of ``full_name``, ``relative_name``, and ``crl_issuer`` (https://github.com/ansible-collections/community.crypto/pull/560).
- execution environment binary dependencies (bindep.txt) - fix ``python3-pyOpenSSL`` dependency resolution on RHEL 9+ / CentOS Stream 9+ platforms (https://github.com/ansible-collections/community.crypto/pull/575).
- various plugins - remove unnecessary imports (https://github.com/ansible-collections/community.crypto/pull/569).
v1.9.20
v2.10.0
=======
Release Summary
---------------
Bugfix release.
Bugfix and feature release.
Bugfixes
--------
- openssl_csr, openssl_csr_pipe - prevent invalid values for ``crl_distribution_points`` that do not have one of ``full_name``, ``relative_name``, and ``crl_issuer`` (https://github.com/ansible-collections/community.crypto/pull/560).
- openssl_publickey_info - do not crash with internal error when public key cannot be parsed (https://github.com/ansible-collections/community.crypto/pull/551).
v1.9.19
=======
New Plugins
-----------
Filter
~~~~~~
- community.crypto.openssl_csr_info - Retrieve information from OpenSSL Certificate Signing Requests (CSR)
- community.crypto.openssl_privatekey_info - Retrieve information from OpenSSL private keys
- community.crypto.openssl_publickey_info - Retrieve information from OpenSSL public keys in PEM format
- community.crypto.split_pem - Split PEM file contents into multiple objects
- community.crypto.x509_certificate_info - Retrieve information from X.509 certificates in PEM format
- community.crypto.x509_crl_info - Retrieve information from X.509 CRLs in PEM format
v2.9.0
======
Release Summary
---------------
Bugfix release.
Regular feature release.
Minor Changes
-------------
- x509_certificate_info - adds ``issuer_uri`` field in return value based on Authority Information Access data (https://github.com/ansible-collections/community.crypto/pull/530).
v2.8.1
======
Release Summary
---------------
Maintenance release with improved documentation.
v2.8.0
======
Release Summary
---------------
Feature release.
Minor Changes
-------------
- acme_* modules - handle more gracefully if CA's new nonce call does not return a nonce (https://github.com/ansible-collections/community.crypto/pull/525).
- acme_* modules - include symbolic HTTP status codes in error and log messages when available (https://github.com/ansible-collections/community.crypto/pull/524).
- openssl_pkcs12 - add option ``encryption_level`` which allows to chose ``compatibility2022`` when cryptography >= 38.0.0 is used to enable a more backwards compatible encryption algorithm. If cryptography uses OpenSSL 3.0.0 or newer, the default algorithm is not compatible with older software (https://github.com/ansible-collections/community.crypto/pull/523).
v2.7.1
======
Release Summary
---------------
Maintenance release.
Bugfixes
--------
- acme_* modules - improve feedback when importing ``cryptography`` does not work (https://github.com/ansible-collections/community.crypto/issues/518, https://github.com/ansible-collections/community.crypto/pull/519).
v2.7.0
======
Release Summary
---------------
Feature release.
Minor Changes
-------------
- acme* modules - also support the HTTP 503 Service Unavailable and 408 Request Timeout response status for automatic retries (https://github.com/ansible-collections/community.crypto/pull/513).
Bugfixes
--------
- openssl_privatekey_pipe - ensure compatibility with newer versions of ansible-core (https://github.com/ansible-collections/community.crypto/pull/515).
v1.9.18
=======
v2.6.0
======
Release Summary
---------------
Bugfix release.
Feature release.
Minor Changes
-------------
- acme* modules - support the HTTP 429 Too Many Requests response status (https://github.com/ansible-collections/community.crypto/pull/508).
- openssh_keypair - added ``pkcs1``, ``pkcs8``, and ``ssh`` to the available choices for the ``private_key_format`` option (https://github.com/ansible-collections/community.crypto/pull/511).
v2.5.0
======
Release Summary
---------------
Maintenance release with improved licensing declaration and documentation fixes.
Minor Changes
-------------
- All software licenses are now in the ``LICENSES/`` directory of the collection root. Moreover, ``SPDX-License-Identifier:`` is used to declare the applicable license for every file that is not automatically generated (https://github.com/ansible-collections/community.crypto/pull/491).
v2.4.0
======
Release Summary
---------------
Deprecation and bugfix release. No new features this time.
Deprecated Features
-------------------
- Support for Ansible 2.9 and ansible-base 2.10 is deprecated, and will be removed in the next major release (community.crypto 3.0.0). Some modules might still work with these versions afterwards, but we will no longer keep compatibility code that was needed to support them (https://github.com/ansible-collections/community.crypto/pull/460).
Bugfixes
--------
- openssl_pkcs12 - when using the pyOpenSSL backend, do not crash when trying to read non-existing other certificates (https://github.com/ansible-collections/community.crypto/issues/486, https://github.com/ansible-collections/community.crypto/pull/487).
v1.9.17
=======
v2.3.4
======
Release Summary
---------------
Re-release of what was intended to be 2.3.3.
A mistake during the release process caused the 2.3.3 tag to end up on the
commit for 1.9.17, which caused the release pipeline to re-publish 1.9.17
as 2.3.3.
This release is identical to what should have been 2.3.3, except that the
version number has been bumped to 2.3.4 and this changelog entry for 2.3.4
has been added.
v2.3.3
======
Release Summary
---------------
@@ -143,8 +548,8 @@ Bugfixes
- openssl_csr - the module no longer crashes with 'permitted_subtrees/excluded_subtrees must be a non-empty list or None' if only one of ``name_constraints_permitted`` and ``name_constraints_excluded`` is provided (https://github.com/ansible-collections/community.crypto/issues/481).
- x509_crl - do not crash when signing CRL with Ed25519 or Ed448 keys (https://github.com/ansible-collections/community.crypto/issues/473, https://github.com/ansible-collections/community.crypto/pull/474).
v1.9.16
=======
v2.3.2
======
Release Summary
---------------
@@ -157,8 +562,8 @@ Bugfixes
- Include ``simplified_bsd.txt`` license file for the ECS module utils.
- certificate_complete_chain - do not stop execution if an unsupported signature algorithm is encountered; warn instead (https://github.com/ansible-collections/community.crypto/pull/457).
v1.9.15
=======
v2.3.1
======
Release Summary
---------------
@@ -170,23 +575,49 @@ Bugfixes
- Include ``PSF-license.txt`` file for ``plugins/module_utils/_version.py``.
v1.9.14
=======
v2.3.0
======
Release Summary
---------------
Regular bugfix release.
Feature and bugfix release.
Minor Changes
-------------
- Prepare collection for inclusion in an Execution Environment by declaring its dependencies. Please note that system packages are used for cryptography and PyOpenSSL, which can be rather limited. If you need features from newer cryptography versions, you will have to manually force a newer version to be installed by pip by specifying something like ``cryptography >= 37.0.0`` in your Execution Environment's Python dependencies file (https://github.com/ansible-collections/community.crypto/pull/440).
- Support automatic conversion for Internalionalized Domain Names (IDNs). When passing general names, for example Subject Alternative Names to ``community.crypto.openssl_csr``, these will automatically be converted to IDNA. Conversion will be done per label to IDNA2008 if possible, and IDNA2003 if IDNA2008 conversion fails for that label. Note that IDNA conversion requires `the Python idna library <https://pypi.org/project/idna/>`_ to be installed. Please note that depending on which versions of the cryptography library are used, it could try to process the converted IDNA another time with the Python ``idna`` library and reject IDNA2003 encoded values. Using a new enough ``cryptography`` version avoids this (https://github.com/ansible-collections/community.crypto/issues/426, https://github.com/ansible-collections/community.crypto/pull/436).
- acme_* modules - add parameter ``request_timeout`` to manage HTTP(S) request timeout (https://github.com/ansible-collections/community.crypto/issues/447, https://github.com/ansible-collections/community.crypto/pull/448).
- luks_devices - added ``perf_same_cpu_crypt``, ``perf_submit_from_crypt_cpus``, ``perf_no_read_workqueue``, ``perf_no_write_workqueue`` for performance tuning when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/issues/427).
- luks_devices - added ``persistent`` option when opening LUKS2 containers (https://github.com/ansible-collections/community.crypto/pull/434).
- openssl_csr_info - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
- openssl_pkcs12 - allow to provide the private key as text instead of having to read it from a file. This allows to store the private key in an encrypted form, for example in Ansible Vault (https://github.com/ansible-collections/community.crypto/pull/452).
- x509_certificate_info - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
- x509_crl - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
- x509_crl_info - add ``name_encoding`` option to control the encoding (IDNA, Unicode) used to return domain names in general names (https://github.com/ansible-collections/community.crypto/pull/436).
Bugfixes
--------
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography version (https://github.com/ansible-collections/community.crypto/pull/446).
- openssh_* modules - fix exception handling to report traceback to users for enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
- Make collection more robust when PyOpenSSL is used with an incompatible cryptography version (https://github.com/ansible-collections/community.crypto/pull/445).
- x509_crl - fix crash when ``issuer`` for a revoked certificate is specified (https://github.com/ansible-collections/community.crypto/pull/441).
v1.9.13
=======
v2.2.4
======
Release Summary
---------------
Regular maintenance release.
Bugfixes
--------
- openssh_* modules - fix exception handling to report traceback to users for enhanced traceability (https://github.com/ansible-collections/community.crypto/pull/417).
v2.2.3
======
Release Summary
---------------
@@ -198,32 +629,27 @@ Bugfixes
- luks_device - fix parsing of ``lsblk`` output when device name ends with ``crypt`` (https://github.com/ansible-collections/community.crypto/issues/409, https://github.com/ansible-collections/community.crypto/pull/410).
v1.9.12
=======
v2.2.2
======
Release Summary
---------------
Regular bugfix release.
In this release, we extended the test matrix to include Alpine 3, ArchLinux, Debian Bullseye, and CentOS Stream 8. CentOS 8 was removed from the test matrix.
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 - for the ``ownca`` provider, check whether the CA private key actually belongs to the CA certificate (https://github.com/ansible-collections/community.crypto/pull/407).
- x509_certificate - regenerate certificate when the CA's public key changes for ``provider=ownca`` (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).
- x509_certificate - regenerate certificate when the private key changes for ``provider=selfsigned`` (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
=======
v2.2.1
======
Release Summary
---------------
@@ -235,22 +661,37 @@ 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
=======
v2.2.0
======
Release Summary
---------------
Regular bugfix release.
Regular bugfix and feature release.
Minor Changes
-------------
- openssh_cert - added ``ignore_timestamps`` parameter so it can be used semi-idempotent with relative timestamps in ``valid_to``/``valid_from`` (https://github.com/ansible-collections/community.crypto/issues/379).
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
v2.1.0
======
Release Summary
---------------
Feature and bugfix release.
Minor Changes
-------------
- Adjust error messages that indicate ``cryptography`` is not installed from ``Can't`` to ``Cannot`` (https://github.com/ansible-collections/community.crypto/pull/374).
Bugfixes
--------
@@ -258,7 +699,13 @@ Bugfixes
- 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
New Modules
-----------
- community.crypto.crypto_info - Retrieve cryptographic capabilities
- community.crypto.openssl_privatekey_convert - Convert OpenSSL private keys
v2.0.2
======
Release Summary
@@ -266,7 +713,7 @@ Release Summary
Documentation fix release. No actual code changes.
v1.9.7
v2.0.1
======
Release Summary
@@ -287,36 +734,79 @@ Bugfixes
- 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
v2.0.0
======
Release Summary
---------------
Regular bugfix release.
A new major release of the ``community.crypto`` collection. The main changes are removal of the PyOpenSSL backends for almost all modules (``openssl_pkcs12`` being the only exception), and removal of the ``assertonly`` provider in the ``x509_certificate`` provider. There are also some other breaking changes which should improve the user interface/experience of this collection long-term.
Minor Changes
-------------
- acme_certificate - the ``subject`` and ``issuer`` fields in in the ``select_chain`` entries are now more strictly validated (https://github.com/ansible-collections/community.crypto/pull/316).
- openssl_csr, openssl_csr_pipe - provide a new ``subject_ordered`` option if the order of the components in the subject is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316).
- openssl_csr, openssl_csr_pipe - there is now stricter validation of the values of the ``subject`` option (https://github.com/ansible-collections/community.crypto/pull/316).
- openssl_privatekey_info - add ``check_consistency`` option to request private key consistency checks to be done (https://github.com/ansible-collections/community.crypto/pull/309).
- x509_certificate, x509_certificate_pipe - add ``ignore_timestamps`` option which allows to enable idempotency for 'not before' and 'not after' options (https://github.com/ansible-collections/community.crypto/issues/295, https://github.com/ansible-collections/community.crypto/pull/317).
- x509_crl - provide a new ``issuer_ordered`` option if the order of the components in the issuer is of importance (https://github.com/ansible-collections/community.crypto/issues/291, https://github.com/ansible-collections/community.crypto/pull/316).
- x509_crl - there is now stricter validation of the values of the ``issuer`` option (https://github.com/ansible-collections/community.crypto/pull/316).
Breaking Changes / Porting Guide
--------------------------------
- Adjust ``dirName`` text parsing and to text converting code to conform to `Sections 2 and 3 of RFC 4514 <https://datatracker.ietf.org/doc/html/rfc4514.html>`_. This is similar to how `cryptography handles this <https://cryptography.io/en/latest/x509/reference/#cryptography.x509.Name.rfc4514_string>`_ (https://github.com/ansible-collections/community.crypto/pull/274).
- acme module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
- acme_* modules - removed vendored copy of the Python library ``ipaddress``. If you are using Python 2.x, please make sure to install the library (https://github.com/ansible-collections/community.crypto/pull/287).
- compatibility module_utils - removed vendored copy of the Python library ``ipaddress`` (https://github.com/ansible-collections/community.crypto/pull/287).
- crypto module utils - removing compatibility code (https://github.com/ansible-collections/community.crypto/pull/290).
- get_certificate, openssl_csr_info, x509_certificate_info - depending on the ``cryptography`` version used, the modules might not return the ASN.1 value for an extension as contained in the certificate respectively CSR, but a re-encoded version of it. This should usually be identical to the value contained in the source file, unless the value was malformed. For extensions not handled by C(cryptography) the value contained in the source file is always returned unaltered (https://github.com/ansible-collections/community.crypto/pull/318).
- module_utils - removed various PyOpenSSL support functions and default backend values that are not needed for the openssl_pkcs12 module (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_csr, openssl_csr_pipe, x509_crl - the ``subject`` respectively ``issuer`` fields no longer ignore empty values, but instead fail when encountering them (https://github.com/ansible-collections/community.crypto/pull/316).
- openssl_privatekey_info - by default consistency checks are not run; they need to be explicitly requested by passing ``check_consistency=true`` (https://github.com/ansible-collections/community.crypto/pull/309).
- x509_crl - for idempotency checks, the ``issuer`` order is ignored. If order is important, use the new ``issuer_ordered`` option (https://github.com/ansible-collections/community.crypto/pull/316).
Deprecated Features
-------------------
- acme_* modules - ACME version 1 is now deprecated and support for it will be removed in community.crypto 2.0.0 (https://github.com/ansible-collections/community.crypto/pull/288).
Removed Features (previously deprecated)
----------------------------------------
- acme_* modules - the ``acme_directory`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
- acme_* modules - the ``acme_version`` option is now required (https://github.com/ansible-collections/community.crypto/pull/290).
- acme_account_facts - the deprecated redirect has been removed. Use community.crypto.acme_account_info instead (https://github.com/ansible-collections/community.crypto/pull/290).
- acme_account_info - ``retrieve_orders=url_list`` no longer returns the return value ``orders``. Use the ``order_uris`` return value instead (https://github.com/ansible-collections/community.crypto/pull/290).
- crypto.info module utils - the deprecated redirect has been removed. Use ``crypto.pem`` instead (https://github.com/ansible-collections/community.crypto/pull/290).
- get_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_certificate - the deprecated redirect has been removed. Use community.crypto.x509_certificate instead (https://github.com/ansible-collections/community.crypto/pull/290).
- openssl_certificate_info - the deprecated redirect has been removed. Use community.crypto.x509_certificate_info instead (https://github.com/ansible-collections/community.crypto/pull/290).
- openssl_csr - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_csr and openssl_csr_pipe - ``version`` now only accepts the (default) value 1 (https://github.com/ansible-collections/community.crypto/pull/290).
- openssl_csr_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_csr_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_privatekey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_privatekey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_privatekey_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_publickey - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_publickey_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_signature - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- openssl_signature_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- x509_certificate - remove ``assertonly`` provider (https://github.com/ansible-collections/community.crypto/pull/289).
- x509_certificate - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- x509_certificate_info - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
- x509_certificate_pipe - removed the ``pyopenssl`` backend (https://github.com/ansible-collections/community.crypto/pull/273).
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
======
@@ -434,20 +924,20 @@ Minor Changes
- openssh_keypair - added ``passphrase`` parameter for encrypting/decrypting OpenSSH private keys (https://github.com/ansible-collections/community.crypto/pull/225).
- openssl_csr - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- openssl_csr_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233).
- openssl_csr_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/204).
- openssl_csr_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/204).
- openssl_csr_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- openssl_pkcs12 - added option ``select_crypto_backend`` and a ``cryptography`` backend. This requires cryptography 3.0 or newer, and does not support the ``iter_size`` and ``maciter_size`` options (https://github.com/ansible-collections/community.crypto/pull/234).
- openssl_privatekey - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- openssl_privatekey_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/205).
- openssl_privatekey_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/205).
- openssl_privatekey_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- openssl_publickey - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- x509_certificate - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- x509_certificate_info - now returns ``public_key_type`` and ``public_key_data`` (https://github.com/ansible-collections/community.crypto/pull/233).
- x509_certificate_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/206).
- x509_certificate_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/206).
- x509_certificate_pipe - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- x509_crl - add diff mode (https://github.com/ansible-collections/community.crypto/issues/38, https://github.com/ansible-collections/community.crypto/pull/150).
- x509_crl_info - add ``list_revoked_certificates`` option to avoid enumerating all revoked certificates (https://github.com/ansible-collections/community.crypto/pull/232).
- x509_crl_info - refactor module to allow code re-use for diff mode (https://github.com/ansible-collections/community.crypto/pull/203).
- x509_crl_info - refactor module to allow code reuse for diff mode (https://github.com/ansible-collections/community.crypto/pull/203).
Bugfixes
--------
@@ -459,7 +949,7 @@ Bugfixes
New Modules
-----------
- openssl_publickey_info - Provide information for OpenSSL public keys
- community.crypto.openssl_publickey_info - Provide information for OpenSSL public keys
v1.6.2
======
@@ -574,11 +1064,11 @@ Minor Changes
-------------
- openssh_cert - add module parameter ``use_agent`` to enable using signing keys stored in ssh-agent (https://github.com/ansible-collections/community.crypto/issues/116).
- openssl_csr - refactor module to allow code re-use by openssl_csr_pipe (https://github.com/ansible-collections/community.crypto/pull/123).
- openssl_privatekey - refactor module to allow code re-use by openssl_privatekey_pipe (https://github.com/ansible-collections/community.crypto/pull/119).
- openssl_csr - refactor module to allow code reuse by openssl_csr_pipe (https://github.com/ansible-collections/community.crypto/pull/123).
- openssl_privatekey - refactor module to allow code reuse by openssl_privatekey_pipe (https://github.com/ansible-collections/community.crypto/pull/119).
- openssl_privatekey - the elliptic curve ``secp192r1`` now triggers a security warning. Elliptic curves of at least 224 bits should be used for new keys; see `here <https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec.html#elliptic-curves>`_ (https://github.com/ansible-collections/community.crypto/pull/132).
- x509_certificate - for the ``selfsigned`` provider, a CSR is not required anymore. If no CSR is provided, the module behaves as if a minimal CSR which only contains the public key has been provided (https://github.com/ansible-collections/community.crypto/issues/32, https://github.com/ansible-collections/community.crypto/pull/129).
- x509_certificate - refactor module to allow code re-use by x509_certificate_pipe (https://github.com/ansible-collections/community.crypto/pull/135).
- x509_certificate - refactor module to allow code reuse by x509_certificate_pipe (https://github.com/ansible-collections/community.crypto/pull/135).
Bugfixes
--------
@@ -590,9 +1080,9 @@ Bugfixes
New Modules
-----------
- openssl_csr_pipe - Generate OpenSSL Certificate Signing Request (CSR)
- openssl_privatekey_pipe - Generate OpenSSL private keys without disk access
- x509_certificate_pipe - Generate and/or check OpenSSL certificates
- community.crypto.openssl_csr_pipe - Generate OpenSSL Certificate Signing Request (CSR)
- community.crypto.openssl_privatekey_pipe - Generate OpenSSL private keys without disk access
- community.crypto.x509_certificate_pipe - Generate and/or check OpenSSL certificates
v1.2.0
======
@@ -668,8 +1158,8 @@ Bugfixes
New Modules
-----------
- openssl_signature - Sign data with openssl
- openssl_signature_info - Verify signatures with openssl
- community.crypto.openssl_signature - Sign data with openssl
- community.crypto.openssl_signature_info - Verify signatures with openssl
v1.0.0
======
@@ -689,7 +1179,7 @@ Minor Changes
- openssh_keypair - instead of regenerating some broken or password protected keys, fail the module. Keys can still be regenerated by calling the module with ``force=yes``.
- openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys.
- openssl_* modules - the cryptography backend now properly supports ``dirName``, ``otherName`` and ``RID`` (Registered ID) names.
- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be neccessary.)
- openssl_certificate - Add option for changing which ACME directory to use with acme-tiny. Set the default ACME directory to Let's Encrypt instead of using acme-tiny's default. (acme-tiny also uses Let's Encrypt at the time being, so no action should be necessary.)
- openssl_certificate - Change the required version of acme-tiny to >= 4.0.0
- openssl_certificate - allow to provide content of some input files via the ``csr_content``, ``privatekey_content``, ``ownca_privatekey_content`` and ``ownca_content`` options.
- openssl_certificate - allow to return the existing/generated certificate directly as ``certificate`` by setting ``return_content`` to ``yes``.
@@ -744,6 +1234,6 @@ Bugfixes
New Modules
-----------
- ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API
- x509_crl - Generate Certificate Revocation Lists (CRLs)
- x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs)
- community.crypto.ecs_domain - Request validation of a domain with the Entrust Certificate Services (ECS) API
- community.crypto.x509_crl - Generate Certificate Revocation Lists (CRLs)
- community.crypto.x509_crl_info - Retrieve information on Certificate Revocation Lists (CRLs)

3
CHANGELOG.rst.license Normal file
View File

@@ -0,0 +1,3 @@
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: Ansible Project

27
LICENSES/BSD-3-Clause.txt Normal file
View File

@@ -0,0 +1,27 @@
Copyright (c) Individual contributors.
All rights reserved.
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 PyCA Cryptography 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 OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1 @@
../COPYING

120
README.md
View File

@@ -1,5 +1,13 @@
<!--
Copyright (c) Ansible Project
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
-->
# Ansible Community Crypto Collection
[![Build Status](https://dev.azure.com/ansible/community.crypto/_apis/build/status/CI?branchName=main)](https://dev.azure.com/ansible/community.crypto/_build?definitionId=21)
[![EOL CI](https://github.com/ansible-collections/community.crypto/workflows/EOL%20CI/badge.svg?event=push)](https://github.com/ansible-collections/community.crypto/actions)
[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/community.crypto)](https://codecov.io/gh/ansible-collections/community.crypto)
Provides modules for [Ansible](https://www.ansible.com/community) for various cryptographic operations.
@@ -8,22 +16,9 @@ You can find [documentation for this collection on the Ansible docs site](https:
Please note that this collection does **not** support Windows targets.
## Communication
* Join the Ansible forum:
* [Get Help](https://forum.ansible.com/c/help/6): get help or help others. Please add appropriate tags if you start new discussions, for example the `crypto` or `acme` tags.
* [Posts tagged with 'crypto'](https://forum.ansible.com/tag/crypto): subscribe to participate in cryptography related conversations.
* [Posts tagged with 'acme'](https://forum.ansible.com/tag/acme): subscribe to participate in ACME (RFC 8555) related conversations.
* [Social Spaces](https://forum.ansible.com/c/chat/4): gather and interact with fellow enthusiasts.
* [News & Announcements](https://forum.ansible.com/c/news/5): track project-wide announcements including social events.
* The Ansible [Bullhorn newsletter](https://docs.ansible.com/ansible/devel/community/communication.html#the-bullhorn): used to announce releases and important changes.
For more information about communication, see the [Ansible communication guide](https://docs.ansible.com/ansible/devel/community/communication.html).
## Tested with Ansible
Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11, ansible-core 2.12 and ansible-core 2.13 releases. Ansible versions before 2.9.10 are not supported.
Tested with the current Ansible 2.9, ansible-base 2.10, ansible-core 2.11, ansible-core 2.12, ansible-core 2.13, ansible-core 2.14, ansible-core 2.15, ansible-core 2.16, and ansible-core-2.17 releases and the current development version of ansible-core. Ansible versions before 2.9.10 are not supported.
## External requirements
@@ -31,41 +26,64 @@ The exact requirements for every module are listed in the module documentation.
Most modules require a recent enough version of [the Python cryptography library](https://pypi.org/project/cryptography/). See the module documentations for the minimal version supported for each module.
## Collection Documentation
Browsing the [**latest** collection documentation](https://docs.ansible.com/ansible/latest/collections/community/crypto) will show docs for the _latest version released in the Ansible package_, not the latest version of the collection released on Galaxy.
Browsing the [**devel** collection documentation](https://docs.ansible.com/ansible/devel/collections/community/crypto) shows docs for the _latest version released on Galaxy_.
We also separately publish [**latest commit** collection documentation](https://ansible-collections.github.io/community.crypto/branch/main/) which shows docs for the _latest commit in the `main` branch_.
If you use the Ansible package and do not update collections independently, use **latest**. If you install or update this collection directly from Galaxy, use **devel**. If you are looking to contribute, use **latest commit**.
## Included content
- OpenSSL / PKI modules:
- openssl_csr_info
- openssl_csr
- openssl_dhparam
- openssl_pkcs12
- openssl_privatekey_info
- openssl_privatekey
- openssl_publickey
- openssl_signature_info
- openssl_signature
- x509_certificate_info
- x509_certificate
- x509_crl_info
- x509_crl
- certificate_complete_chain
- OpenSSH modules:
- openssh_cert
- openssh_keypair
- ACME modules:
- acme_account_info
- acme_account
- acme_certificate
- acme_certificate_revoke
- acme_challenge_cert_helper
- acme_inspect
- ECS modules:
- ecs_certificate
- ecs_domain
- Miscellaneous modules:
- get_certificate
- luks_device
- OpenSSL / PKI modules and plugins:
- certificate_complete_chain module
- openssl_csr_info module and filter
- openssl_csr_pipe module
- openssl_csr module
- openssl_dhparam module
- openssl_pkcs12 module
- openssl_privatekey_convert module
- openssl_privatekey_info module and filter
- openssl_privatekey_pipe module
- openssl_privatekey module
- openssl_publickey_info module and filter
- openssl_publickey module
- openssl_signature_info module
- openssl_signature module
- split_pem filter
- x509_certificate_convert module
- x509_certificate_info module and filter
- x509_certificate_pipe module
- x509_certificate module
- x509_crl_info module and filter
- x509_crl module
- OpenSSH modules and plugins:
- openssh_cert module
- openssh_keypair module
- ACME modules and plugins:
- acme_account_info module
- acme_account module
- acme_ari_info module
- acme_certificate module
- acme_certificate_deactivate_authz module
- acme_certificate_revoke module
- acme_challenge_cert_helper module
- acme_inspect module
- ECS modules and plugins:
- ecs_certificate module
- ecs_domain module
- GnuPG modules and plugins:
- gpg_fingerprint lookup and filter
- Miscellaneous modules and plugins:
- crypto_info module
- get_certificate module
- luks_device module
- parse_serial and to_serial filters
You can also find a list of all modules with documentation on the [Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/).
You can also find a list of all modules and plugins with documentation on the [Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/), or the [latest commit collection documentation](https://ansible-collections.github.io/community.crypto/branch/main/).
## Using this collection
@@ -97,7 +115,7 @@ See [Ansible's dev guide](https://docs.ansible.com/ansible/devel/dev_guide/devel
## Release notes
See the [changelog](https://github.com/ansible-collections/community.crypto/blob/stable-1/CHANGELOG.md).
See the [changelog](https://github.com/ansible-collections/community.crypto/blob/main/CHANGELOG.md).
## Roadmap
@@ -120,6 +138,10 @@ In 2.0.0, the following notable features will be removed:
## Licensing
GNU General Public License v3.0 or later.
This collection is primarily licensed and distributed as a whole under the GNU General Public License v3.0 or later.
See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.
See [LICENSES/GPL-3.0-or-later.txt](https://github.com/ansible-collections/community.crypto/blob/main/COPYING) for the full text.
Parts of the collection are licensed under the [Apache 2.0 license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/Apache-2.0.txt) (`plugins/module_utils/crypto/_obj2txt.py` and `plugins/module_utils/crypto/_objects_data.py`), the [BSD 2-Clause license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/BSD-2-Clause.txt) (`plugins/module_utils/ecs/api.py`), the [BSD 3-Clause license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/BSD-3-Clause.txt) (`plugins/module_utils/crypto/_obj2txt.py`, `tests/integration/targets/prepare_jinja2_compat/filter_plugins/jinja_compatibility.py`), and the [PSF 2.0 license](https://github.com/ansible-collections/community.crypto/blob/main/LICENSES/PSF-2.0.txt) (`plugins/module_utils/_version.py`). This only applies to vendored files in ``plugins/module_utils/`` and to the ECS module utils.
Almost all files have a machine readable `SDPX-License-Identifier:` comment denoting its respective license(s) or an equivalent entry in an accompanying `.license` file. Only changelog fragments (which will not be part of a release) are covered by a blanket statement in `.reuse/dep5`. Right now a few vendored PEM files do not have licensing information as well. This conforms to the [REUSE specification](https://reuse.software/spec/) up to the aforementioned PEM files.

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
SPDX-FileCopyrightText: Ansible Project

View File

@@ -1,3 +1,8 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
changelog_filename_template: ../CHANGELOG.rst
changelog_filename_version_depth: 0
changes_file: changelog.yaml
@@ -12,20 +17,25 @@ output_formats:
prelude_section_name: release_summary
prelude_section_title: Release Summary
sections:
- - major_changes
- Major Changes
- - minor_changes
- Minor Changes
- - breaking_changes
- Breaking Changes / Porting Guide
- - deprecated_features
- Deprecated Features
- - removed_features
- Removed Features (previously deprecated)
- - security_fixes
- Security Fixes
- - bugfixes
- Bugfixes
- - known_issues
- Known Issues
- - major_changes
- Major Changes
- - minor_changes
- Minor Changes
- - breaking_changes
- Breaking Changes / Porting Guide
- - deprecated_features
- Deprecated Features
- - removed_features
- Removed Features (previously deprecated)
- - security_fixes
- Security Fixes
- - bugfixes
- Bugfixes
- - known_issues
- Known Issues
title: Community Crypto
trivial_section_name: trivial
use_fqcn: true
add_plugin_period: true
changelog_nice_yaml: true
changelog_sort: version

7
docs/docsite/config.yml Normal file
View File

@@ -0,0 +1,7 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
changelog:
write_changelog: true

View File

@@ -1,4 +1,8 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
sections:
- title: Scenario Guides
toctree:

31
docs/docsite/links.yml Normal file
View File

@@ -0,0 +1,31 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
edit_on_github:
repository: ansible-collections/community.crypto
branch: main
path_prefix: ''
extra_links:
- description: Submit a bug report
url: https://github.com/ansible-collections/community.crypto/issues/new?assignees=&labels=&template=bug_report.md
- description: Request a feature
url: https://github.com/ansible-collections/community.crypto/issues/new?assignees=&labels=&template=feature_request.md
communication:
matrix_rooms:
- topic: General usage and support questions
room: '#users:ansible.im'
irc_channels:
- topic: General usage and support questions
network: Libera
channel: '#ansible'
mailing_lists:
- topic: Ansible Project List
url: https://groups.google.com/g/ansible-project
forums:
- topic: Ansible Forum
# The following URL directly points to the "Get Help" section
url: https://forum.ansible.com/c/help/6/none

View File

@@ -1,3 +1,8 @@
..
Copyright (c) Ansible Project
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
.. _ansible_collections.community.crypto.docsite.guide_ownca:
How to create a small CA
@@ -29,7 +34,7 @@ The following instructions show how to set up a simple self-signed CA certificat
use_common_name_for_san: false # since we do not specify SANs, don't use CN as a SAN
basic_constraints:
- 'CA:TRUE'
basic_constraints_critical: yes
basic_constraints_critical: true
key_usage:
- keyCertSign
key_usage_critical: true

View File

@@ -1,3 +1,8 @@
..
Copyright (c) Ansible Project
GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
SPDX-License-Identifier: GPL-3.0-or-later
.. _ansible_collections.community.crypto.docsite.guide_selfsigned:
How to create self-signed certificates
@@ -5,7 +10,7 @@ How to create self-signed certificates
The `community.crypto collection <https://galaxy.ansible.com/ui/repo/published/community/crypto/>`_ offers multiple modules that create private keys, certificate signing requests, and certificates. This guide shows how to create self-signed certificates.
For creating any kind of certificate, you always have to start with a private key. You can use the :ref:`community.crypto.openssl_privatekey module <ansible_collections.community.crypto.openssl_privatekey_module>` to create a private key. If you only specify ``path``, the default parameters will be used. This will result in a 4096 bit RSA private key:
For creating any kind of certificate, you always have to start with a private key. You can use the :ref:`community.crypto.openssl_privatekey module <ansible_collections.community.crypto.openssl_privatekey_module>` to create a private key. If you only specify :ansopt:`community.crypto.openssl_privatekey#module:path`, the default parameters will be used. This will result in a 4096 bit RSA private key:
.. code-block:: yaml+jinja
@@ -13,7 +18,7 @@ For creating any kind of certificate, you always have to start with a private ke
community.crypto.openssl_privatekey:
path: /path/to/certificate.key
You can specify ``type`` to select another key type, ``size`` to select a different key size (only available for RSA and DSA keys), or ``passphrase`` if you want to store the key password-protected:
You can specify :ansopt:`community.crypto.openssl_privatekey#module:type` to select another key type, :ansopt:`community.crypto.openssl_privatekey#module:size` to select a different key size (only available for RSA and DSA keys), or :ansopt:`community.crypto.openssl_privatekey#module:passphrase` if you want to store the key password-protected:
.. code-block:: yaml+jinja
@@ -33,9 +38,9 @@ To create a very simple self-signed certificate with no specific information, yo
privatekey_path: /path/to/certificate.key
provider: selfsigned
(If you used ``passphrase`` for the private key, you have to provide ``privatekey_passphrase``.)
(If you used :ansopt:`community.crypto.openssl_privatekey#module:passphrase` for the private key, you have to provide :ansopt:`community.crypto.x509_certificate#module:privatekey_passphrase`.)
You can use ``selfsigned_not_after`` to define when the certificate expires (default: in roughly 10 years), and ``selfsigned_not_before`` to define from when the certificate is valid (default: now).
You can use :ansopt:`community.crypto.x509_certificate#module:selfsigned_not_after` to define when the certificate expires (default: in roughly 10 years), and :ansopt:`community.crypto.x509_certificate#module:selfsigned_not_before` to define from when the certificate is valid (default: now).
To define further properties of the certificate, like the subject, Subject Alternative Names (SANs), key usages, name constraints, etc., you need to first create a Certificate Signing Request (CSR) and provide it to the :ref:`community.crypto.x509_certificate module <ansible_collections.community.crypto.x509_certificate_module>`. If you do not need the CSR file, you can use the :ref:`community.crypto.openssl_csr_pipe module <ansible_collections.community.crypto.openssl_csr_pipe_module>` as in the example below. (To store it to disk, use the :ref:`community.crypto.openssl_csr module <ansible_collections.community.crypto.openssl_csr_module>` instead.)

View File

@@ -1,11 +1,22 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
namespace: community
name: crypto
version: 1.9.26
version: 2.21.0
readme: README.md
authors:
- Ansible (github.com/ansible)
description: null
license_file: COPYING
license:
- GPL-3.0-or-later
- Apache-2.0
- BSD-2-Clause
- BSD-3-Clause
- PSF-2.0
#license_file: COPYING
tags:
- acme
- certificate

21
meta/ee-bindep.txt Normal file
View File

@@ -0,0 +1,21 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
cryptsetup [platform:dpkg]
cryptsetup [platform:rpm]
openssh-client [platform:dpkg]
openssh-clients [platform:rpm]
openssl [platform:dpkg]
openssl [platform:rpm]
python3-cryptography [platform:dpkg]
python3-cryptography [platform:rpm]
python3-openssl [platform:dpkg]
# On RHEL 9+, CentOS Stream 9+, and Rocky Linux 9+, python3-pyOpenSSL is part of EPEL
python3-pyOpenSSL [platform:rpm !platform:rhel !platform:centos !platform:rocky]
python3-pyOpenSSL [platform:rhel-8]
python3-pyOpenSSL [platform:rhel !platform:rhel-6 !platform:rhel-7 !platform:rhel-8 epel]
python3-pyOpenSSL [platform:centos-8]
python3-pyOpenSSL [platform:centos !platform:centos-6 !platform:centos-7 !platform:centos-8 epel]
python3-pyOpenSSL [platform:rocky-8]
python3-pyOpenSSL [platform:rocky !platform:rocky-8 epel]

5
meta/ee-requirements.txt Normal file
View File

@@ -0,0 +1,5 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
PyYAML

View File

@@ -0,0 +1,9 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
version: 1
dependencies:
python: meta/ee-requirements.txt
system: meta/ee-bindep.txt

View File

@@ -1,31 +1,35 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
requires_ansible: '>=2.9.10'
action_groups:
acme:
- acme_inspect
- acme_certificate_revoke
- acme_certificate
- acme_account
- acme_account_facts
- acme_account_info
- acme_inspect
- acme_certificate_deactivate_authz
- acme_certificate_revoke
- acme_certificate
- acme_account
- acme_account_info
plugin_routing:
modules:
acme_account_facts:
deprecation:
tombstone:
removal_version: 2.0.0
warning_text: The 'community.crypto.acme_account_facts' module has been renamed to 'community.crypto.acme_account_info'.
openssl_certificate:
deprecation:
tombstone:
removal_version: 2.0.0
warning_text: The 'community.crypto.openssl_certificate' module has been renamed to 'community.crypto.x509_certificate'
openssl_certificate_info:
deprecation:
tombstone:
removal_version: 2.0.0
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'
module_utils:
crypto.identify:
redirect: community.crypto.crypto.pem
deprecation:
tombstone:
removal_version: 2.0.0
warning_text: The 'crypto/identify.py' module_utils has been renamed 'crypto/pem.py'. Please update your imports

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -50,6 +51,16 @@ class PrivateKeyModule(object):
self.module_backend.generate_private_key()
privatekey_data = self.module_backend.get_private_key_data()
self.privatekey_bytes = privatekey_data
else:
self.module.deprecate(
'Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0'
' to behave the same as without check mode. You can get that behavior right now'
' by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this'
' breaks your use-case of this module, please create an issue in the'
' community.crypto repository',
version='3.0.0',
collection_name='community.crypto',
)
self.changed = True
elif self.module_backend.needs_conversion():
# Convert
@@ -57,6 +68,16 @@ class PrivateKeyModule(object):
self.module_backend.convert_private_key()
privatekey_data = self.module_backend.get_private_key_data()
self.privatekey_bytes = privatekey_data
else:
self.module.deprecate(
'Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0'
' to behave the same as without check mode. You can get that behavior right now'
' by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this'
' breaks your use-case of this module, please create an issue in the'
' community.crypto repository',
version='3.0.0',
collection_name='community.crypto',
)
self.changed = True
def dump(self):

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -10,22 +11,33 @@ __metaclass__ = type
class ModuleDocFragment(object):
# Standard files documentation fragment
#
# NOTE: This document fragment is DEPRECATED and will be removed from community.crypto 3.0.0.
# Use both the BASIC and ACCOUNT fragments as a replacement.
DOCUMENTATION = r'''
notes:
- "If a new enough version of the C(cryptography) library
is available (see Requirements for details), it will be used
instead of the C(openssl) binary. This can be explicitly disabled
or enabled with the C(select_crypto_backend) option. Note that using
or enabled with the O(select_crypto_backend) option. Note that using
the C(openssl) binary will be slower and less secure, as private key
contents always have to be stored on disk (see
C(account_key_content))."
O(account_key_content))."
- "Although the defaults are chosen so that the module can be used with
the L(Let's Encrypt,https://letsencrypt.org/) CA, the module can in
principle be used with any CA providing an ACME endpoint, such as
L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)."
- "So far, the ACME modules have only been tested by the developers against
Let's Encrypt (staging and production), Buypass (staging and production), ZeroSSL (production),
and L(Pebble testing server,https://github.com/letsencrypt/Pebble). We have got
community feedback that they also work with Sectigo ACME Service for InCommon.
If you experience problems with another ACME server, please
L(create an issue,https://github.com/ansible-collections/community.crypto/issues/new/choose)
to help us supporting it. Feedback that an ACME server not mentioned does work
is also appreciated."
requirements:
- python >= 2.6
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
- ipaddress
options:
account_key_src:
description:
@@ -33,20 +45,20 @@ options:
key."
- "Private keys can be created with the
M(community.crypto.openssl_privatekey) or M(community.crypto.openssl_privatekey_pipe)
modules. If the requisites (pyOpenSSL or cryptography) are not available,
modules. If the requisite (cryptography) is not available,
keys can also be created directly with the C(openssl) command line tool:
RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys
can be created with C(openssl ecparam -genkey ...). Any other tool creating
private keys in PEM format can be used as well."
- "Mutually exclusive with C(account_key_content)."
- "Required if C(account_key_content) is not used."
- "Mutually exclusive with O(account_key_content)."
- "Required if O(account_key_content) is not used."
type: path
aliases: [ account_key ]
account_key_content:
description:
- "Content of the ACME account RSA or Elliptic Curve key."
- "Mutually exclusive with C(account_key_src)."
- "Required if C(account_key_src) is not used."
- "Mutually exclusive with O(account_key_src)."
- "Required if O(account_key_src) is not used."
- "B(Warning:) the content will be written into a temporary file, which will
be deleted by Ansible when the module completes. Since this is an
important private key — it can be used to change the account key,
@@ -72,11 +84,11 @@ options:
acme_version:
description:
- "The ACME version of the endpoint."
- "Must be C(1) for the classic Let's Encrypt and Buypass ACME endpoints,
or C(2) for standardized ACME v2 endpoints."
- "The default value is C(1). Note that in community.crypto 2.0.0, this
option B(will be required) and will no longer have a default."
- "Please also note that we will deprecate ACME v1 support eventually."
- "Must be V(1) for the classic Let's Encrypt and Buypass ACME endpoints,
or V(2) for standardized ACME v2 endpoints."
- "The value V(1) is deprecated since community.crypto 2.0.0 and will be
removed from community.crypto 3.0.0."
required: true
type: int
choices: [ 1, 2 ]
acme_directory:
@@ -86,50 +98,220 @@ options:
- "For safety reasons the default is set to the Let's Encrypt staging
server (for the ACME v1 protocol). This will create technically correct,
but untrusted certificates."
- "The default value is C(https://acme-staging.api.letsencrypt.org/directory).
Note that in community.crypto 2.0.0, this option B(will be required) and
will no longer have a default. Note that the default is the Let's Encrypt
staging server for the ACME v1 protocol, which is deprecated and will
be disabled in May 2021 (see
L(here,https://community.letsencrypt.org/t/end-of-life-plan-for-acmev1/88430/7)
for details)."
- "For Let's Encrypt, all staging endpoints can be found here:
U(https://letsencrypt.org/docs/staging-environment/). For Buypass, all
endpoints can be found here:
U(https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints)"
- "For B(Let's Encrypt), the production directory URL for ACME v2 is
U(https://acme-v02.api.letsencrypt.org/directory).
(The production directory URL for ACME v1 is
U(https://acme-v01.api.letsencrypt.org/directory) and will be
disabled in July 2021.)"
U(https://acme-v02.api.letsencrypt.org/directory)."
- "For B(Buypass), the production directory URL for ACME v2 and v1 is
U(https://api.buypass.com/acme/directory)."
- "For B(ZeroSSL), the production directory URL for ACME v2 is
U(https://acme.zerossl.com/v2/DV90)."
- "B(Warning:) So far, the ACME modules have only been tested against Let's Encrypt
(staging and production), Buypass (staging and production), ZeroSSL (production),
and L(Pebble testing server,https://github.com/letsencrypt/Pebble). If you
experience problems with another ACME server, please
L(create an issue,https://github.com/ansible-collections/community.crypto/issues/new/choose)
to help us supporting it. Feedback that an ACME server not mentioned does work
is also appreciated."
- "For B(Sectigo), the production directory URL for ACME v2 is
U(https://acme-qa.secure.trust-provider.com/v2/DV)."
- The notes for this module contain a list of ACME services this module has
been tested against.
required: true
type: str
validate_certs:
description:
- Whether calls to the ACME directory will validate TLS certificates.
- "B(Warning:) Should B(only ever) be set to C(no) for testing purposes,
- "B(Warning:) Should B(only ever) be set to V(false) for testing purposes,
for example when testing against a local Pebble server."
type: bool
default: yes
default: true
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to
- The default choice is V(auto), which tries to use C(cryptography) if available, and falls back to
C(openssl).
- If set to C(openssl), will try to use the C(openssl) binary.
- If set to C(cryptography), will try to use the
- If set to V(openssl), will try to use the C(openssl) binary.
- If set to V(cryptography), will try to use the
L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, openssl ]
request_timeout:
description:
- The time Ansible should wait for a response from the ACME API.
- This timeout is applied to all HTTP(S) requests (HEAD, GET, POST).
type: int
default: 10
version_added: 2.3.0
'''
# Basic documentation fragment without account data
BASIC = r'''
notes:
- "Although the defaults are chosen so that the module can be used with
the L(Let's Encrypt,https://letsencrypt.org/) CA, the module can in
principle be used with any CA providing an ACME endpoint, such as
L(Buypass Go SSL,https://www.buypass.com/ssl/products/acme)."
- "So far, the ACME modules have only been tested by the developers against
Let's Encrypt (staging and production), Buypass (staging and production), ZeroSSL (production),
and L(Pebble testing server,https://github.com/letsencrypt/Pebble). We have got
community feedback that they also work with Sectigo ACME Service for InCommon.
If you experience problems with another ACME server, please
L(create an issue,https://github.com/ansible-collections/community.crypto/issues/new/choose)
to help us supporting it. Feedback that an ACME server not mentioned does work
is also appreciated."
requirements:
- either openssl or L(cryptography,https://cryptography.io/) >= 1.5
- ipaddress
options:
acme_version:
description:
- "The ACME version of the endpoint."
- "Must be V(1) for the classic Let's Encrypt and Buypass ACME endpoints,
or V(2) for standardized ACME v2 endpoints."
- "The value V(1) is deprecated since community.crypto 2.0.0 and will be
removed from community.crypto 3.0.0."
required: true
type: int
choices: [ 1, 2 ]
acme_directory:
description:
- "The ACME directory to use. This is the entry point URL to access
the ACME CA server API."
- "For safety reasons the default is set to the Let's Encrypt staging
server (for the ACME v1 protocol). This will create technically correct,
but untrusted certificates."
- "For Let's Encrypt, all staging endpoints can be found here:
U(https://letsencrypt.org/docs/staging-environment/). For Buypass, all
endpoints can be found here:
U(https://community.buypass.com/t/63d4ay/buypass-go-ssl-endpoints)"
- "For B(Let's Encrypt), the production directory URL for ACME v2 is
U(https://acme-v02.api.letsencrypt.org/directory)."
- "For B(Buypass), the production directory URL for ACME v2 and v1 is
U(https://api.buypass.com/acme/directory)."
- "For B(ZeroSSL), the production directory URL for ACME v2 is
U(https://acme.zerossl.com/v2/DV90)."
- "For B(Sectigo), the production directory URL for ACME v2 is
U(https://acme-qa.secure.trust-provider.com/v2/DV)."
- The notes for this module contain a list of ACME services this module has
been tested against.
required: true
type: str
validate_certs:
description:
- Whether calls to the ACME directory will validate TLS certificates.
- "B(Warning:) Should B(only ever) be set to V(false) for testing purposes,
for example when testing against a local Pebble server."
type: bool
default: true
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is V(auto), which tries to use C(cryptography) if available, and falls back to
C(openssl).
- If set to V(openssl), will try to use the C(openssl) binary.
- If set to V(cryptography), will try to use the
L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, openssl ]
request_timeout:
description:
- The time Ansible should wait for a response from the ACME API.
- This timeout is applied to all HTTP(S) requests (HEAD, GET, POST).
type: int
default: 10
version_added: 2.3.0
'''
# Account data documentation fragment
ACCOUNT = r'''
notes:
- "If a new enough version of the C(cryptography) library
is available (see Requirements for details), it will be used
instead of the C(openssl) binary. This can be explicitly disabled
or enabled with the O(select_crypto_backend) option. Note that using
the C(openssl) binary will be slower and less secure, as private key
contents always have to be stored on disk (see
O(account_key_content))."
options:
account_key_src:
description:
- "Path to a file containing the ACME account RSA or Elliptic Curve
key."
- "Private keys can be created with the
M(community.crypto.openssl_privatekey) or M(community.crypto.openssl_privatekey_pipe)
modules. If the requisite (cryptography) is not available,
keys can also be created directly with the C(openssl) command line tool:
RSA keys can be created with C(openssl genrsa ...). Elliptic curve keys
can be created with C(openssl ecparam -genkey ...). Any other tool creating
private keys in PEM format can be used as well."
- "Mutually exclusive with O(account_key_content)."
- "Required if O(account_key_content) is not used."
type: path
aliases: [ account_key ]
account_key_content:
description:
- "Content of the ACME account RSA or Elliptic Curve key."
- "Mutually exclusive with O(account_key_src)."
- "Required if O(account_key_src) is not used."
- "B(Warning:) the content will be written into a temporary file, which will
be deleted by Ansible when the module completes. Since this is an
important private key — it can be used to change the account key,
or to revoke your certificates without knowing their private keys
—, this might not be acceptable."
- "In case C(cryptography) is used, the content is not written into a
temporary file. It can still happen that it is written to disk by
Ansible in the process of moving the module with its argument to
the node where it is executed."
type: str
account_key_passphrase:
description:
- Phassphrase to use to decode the account key.
- "B(Note:) this is not supported by the C(openssl) backend, only by the C(cryptography) backend."
type: str
version_added: 1.6.0
account_uri:
description:
- "If specified, assumes that the account URI is as given. If the
account key does not match this account, or an account with this
URI does not exist, the module fails."
type: str
'''
# No account data documentation fragment
NO_ACCOUNT = r'''
notes:
- "If a new enough version of the C(cryptography) library
is available (see Requirements for details), it will be used
instead of the C(openssl) binary. This can be explicitly disabled
or enabled with the O(select_crypto_backend) option. Note that using
the C(openssl) binary will be slower."
options: {}
'''
CERTIFICATE = r'''
options:
csr:
description:
- "File containing the CSR for the new certificate."
- "Can be created with M(community.crypto.openssl_csr)."
- "The CSR may contain multiple Subject Alternate Names, but each one
will lead to an individual challenge that must be fulfilled for the
CSR to be signed."
- "B(Note): the private key used to create the CSR B(must not) be the
account key. This is a bad idea from a security point of view, and
the CA should not accept the CSR. The ACME server should return an
error in this case."
- Precisely one of O(csr) or O(csr_content) must be specified.
type: path
csr_content:
description:
- "Content of the CSR for the new certificate."
- "Can be created with M(community.crypto.openssl_csr_pipe)."
- "The CSR may contain multiple Subject Alternate Names, but each one
will lead to an individual challenge that must be fulfilled for the
CSR to be signed."
- "B(Note): the private key used to create the CSR B(must not) be the
account key. This is a bad idea from a security point of view, and
the CA should not accept the CSR. The ACME server should return an
error in this case."
- Precisely one of O(csr) or O(csr_content) must be specified.
type: str
'''

View File

@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
# Standard documentation fragment
DOCUMENTATION = r'''
options: {}
attributes:
check_mode:
description: Can run in C(check_mode) and return changed status prediction without modifying target.
diff_mode:
description: Will return details on what has changed (or possibly needs changing in C(check_mode)), when in diff mode.
'''
# Should be used together with the standard fragment
INFO_MODULE = r'''
options: {}
attributes:
check_mode:
support: full
details:
- This action does not modify state.
diff_mode:
support: N/A
details:
- This action does not modify state.
'''
ACTIONGROUP_ACME = r'''
options: {}
attributes:
action_group:
description: Use C(group/acme) or C(group/community.crypto.acme) in C(module_defaults) to set defaults for this module.
support: full
membership:
- community.crypto.acme
- acme
'''
FACTS = r'''
options: {}
attributes:
facts:
description: Action returns an C(ansible_facts) dictionary that will update existing host facts.
'''
# Should be used together with the standard fragment and the FACTS fragment
FACTS_MODULE = r'''
options: {}
attributes:
check_mode:
support: full
details:
- This action does not modify state.
diff_mode:
support: N/A
details:
- This action does not modify state.
facts:
support: full
'''
FILES = r'''
options: {}
attributes:
safe_file_operations:
description: Uses Ansible's strict file operation functions to ensure proper permissions and avoid data corruption.
'''
FLOW = r'''
options: {}
attributes:
action:
description: Indicates this has a corresponding action plugin so some parts of the options can be executed on the controller.
async:
description: Supports being used with the C(async) keyword.
'''

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright (c), Entrust Datacard Corporation, 2019
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -14,63 +15,65 @@ class ModuleDocFragment(object):
DOCUMENTATION = r'''
description:
- This module allows one to (re)generate OpenSSL certificates.
- It uses the pyOpenSSL or cryptography python library to interact with OpenSSL.
- If both the cryptography and PyOpenSSL libraries are available (and meet the minimum version requirements)
cryptography will be preferred as a backend over PyOpenSSL (unless the backend is forced with C(select_crypto_backend)).
Please note that the PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0.
- It uses the cryptography python library to interact with OpenSSL.
requirements:
- PyOpenSSL >= 0.15 or cryptography >= 1.6 (if using C(selfsigned), C(ownca) or C(assertonly) provider)
- cryptography >= 1.6 (if using V(selfsigned) or V(ownca) provider)
options:
force:
description:
- Generate the certificate, even if it already exists.
type: bool
default: no
default: false
csr_path:
description:
- Path to the Certificate Signing Request (CSR) used to generate this certificate.
- This is mutually exclusive with I(csr_content).
- This is mutually exclusive with O(csr_content).
type: path
csr_content:
description:
- Content of the Certificate Signing Request (CSR) used to generate this certificate.
- This is mutually exclusive with I(csr_path).
- This is mutually exclusive with O(csr_path).
type: str
privatekey_path:
description:
- Path to the private key to use when signing the certificate.
- This is mutually exclusive with I(privatekey_content).
- This is mutually exclusive with O(privatekey_content).
type: path
privatekey_content:
description:
- Content of the private key to use when signing the certificate.
- This is mutually exclusive with I(privatekey_path).
- This is mutually exclusive with O(privatekey_path).
type: str
privatekey_passphrase:
description:
- The passphrase for the I(privatekey_path) resp. I(privatekey_content).
- The passphrase for the O(privatekey_path) resp. O(privatekey_content).
- This is required if the private key is password protected.
type: str
ignore_timestamps:
description:
- Whether the "not before" and "not after" timestamps should be ignored for idempotency checks.
- It is better to keep the default value V(true) when using relative timestamps (like V(+0s) for now).
type: bool
default: true
version_added: 2.0.0
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
From that point on, only the C(cryptography) backend will be available.
- The default choice is V(auto), which tries to use C(cryptography) if available.
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
choices: [ auto, cryptography ]
notes:
- All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern.
- Date specified should be UTC. Minutes and seconds are mandatory.
- For security reason, when you use C(ownca) provider, you should NOT run
- For security reason, when you use V(ownca) provider, you should NOT run
M(community.crypto.x509_certificate) on a target machine, but on a dedicated CA machine. It
is recommended not to store the CA private key on the target machine. Once signed, the
certificate can be moved to the target machine.
@@ -88,28 +91,28 @@ seealso:
description:
- This module allows one to (re)generate OpenSSL certificates.
requirements:
- acme-tiny >= 4.0.0 (if using the C(acme) provider)
- acme-tiny >= 4.0.0 (if using the V(acme) provider)
options:
acme_accountkey_path:
description:
- The path to the accountkey for the C(acme) provider.
- This is only used by the C(acme) provider.
- The path to the accountkey for the V(acme) provider.
- This is only used by the V(acme) provider.
type: path
acme_challenge_path:
description:
- The path to the ACME challenge directory that is served on U(http://<HOST>:80/.well-known/acme-challenge/)
- This is only used by the C(acme) provider.
- This is only used by the V(acme) provider.
type: path
acme_chain:
description:
- Include the intermediate certificate to the generated certificate
- This is only used by the C(acme) provider.
- This is only used by the V(acme) provider.
- Note that this is only available for older versions of C(acme-tiny).
New versions include the chain automatically, and setting I(acme_chain) to C(yes) results in an error.
New versions include the chain automatically, and setting O(acme_chain) to V(true) results in an error.
type: bool
default: no
default: false
acme_directory:
description:
@@ -119,207 +122,12 @@ options:
default: https://acme-v02.api.letsencrypt.org/directory
'''
BACKEND_ASSERTONLY_DOCUMENTATION = r'''
description:
- The C(assertonly) provider is intended for use cases where one is only interested in
checking properties of a supplied certificate. Please note that this provider has been
deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0. See the examples on how
to emulate C(assertonly) usage with M(community.crypto.x509_certificate_info),
M(community.crypto.openssl_csr_info), M(community.crypto.openssl_privatekey_info) and
M(ansible.builtin.assert). This also allows more flexible checks than
the ones offered by the C(assertonly) provider.
- Many properties that can be specified in this module are for validation of an
existing or newly generated certificate. The proper place to specify them, if you
want to receive a certificate with these properties is a CSR (Certificate Signing Request).
options:
csr_path:
description:
- This is not required for the C(assertonly) provider.
csr_content:
description:
- This is not required for the C(assertonly) provider.
signature_algorithms:
description:
- A list of algorithms that you would accept the certificate to be signed with
(e.g. ['sha256WithRSAEncryption', 'sha512WithRSAEncryption']).
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: list
elements: str
issuer:
description:
- The key/value pairs that must be present in the issuer name field of the certificate.
- If you need to specify more than one value with the same key, use a list as value.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: dict
issuer_strict:
description:
- If set to C(yes), the I(issuer) field must contain only these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: bool
default: no
subject:
description:
- The key/value pairs that must be present in the subject name field of the certificate.
- If you need to specify more than one value with the same key, use a list as value.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: dict
subject_strict:
description:
- If set to C(yes), the I(subject) field must contain only these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: bool
default: no
has_expired:
description:
- Checks if the certificate is expired/not expired at the time the module is executed.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: bool
default: no
version:
description:
- The version of the certificate.
- Nowadays it should almost always be 3.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: int
valid_at:
description:
- The certificate must be valid at this point in time.
- The timestamp is formatted as an ASN.1 TIME.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: str
invalid_at:
description:
- The certificate must be invalid at this point in time.
- The timestamp is formatted as an ASN.1 TIME.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: str
not_before:
description:
- The certificate must start to become valid at this point in time.
- The timestamp is formatted as an ASN.1 TIME.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: str
aliases: [ notBefore ]
not_after:
description:
- The certificate must expire at this point in time.
- The timestamp is formatted as an ASN.1 TIME.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: str
aliases: [ notAfter ]
valid_in:
description:
- The certificate must still be valid at this relative time offset from now.
- Valid format is C([+-]timespec | number_of_seconds) where timespec can be an integer
+ C([w | d | h | m | s]) (e.g. C(+32w1d2h).
- Note that if using this parameter, this module is NOT idempotent.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: str
key_usage:
description:
- The I(key_usage) extension field must contain all these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: list
elements: str
aliases: [ keyUsage ]
key_usage_strict:
description:
- If set to C(yes), the I(key_usage) extension field must contain only these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: bool
default: no
aliases: [ keyUsage_strict ]
extended_key_usage:
description:
- The I(extended_key_usage) extension field must contain all these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: list
elements: str
aliases: [ extendedKeyUsage ]
extended_key_usage_strict:
description:
- If set to C(yes), the I(extended_key_usage) extension field must contain only these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: bool
default: no
aliases: [ extendedKeyUsage_strict ]
subject_alt_name:
description:
- The I(subject_alt_name) extension field must contain these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: list
elements: str
aliases: [ subjectAltName ]
subject_alt_name_strict:
description:
- If set to C(yes), the I(subject_alt_name) extension field must contain only these values.
- This is only used by the C(assertonly) provider.
- This option is deprecated since Ansible 2.9 and will be removed with the C(assertonly) provider in community.crypto 2.0.0.
For alternatives, see the example on replacing C(assertonly).
type: bool
default: no
aliases: [ subjectAltName_strict ]
'''
BACKEND_ENTRUST_DOCUMENTATION = r'''
options:
entrust_cert_type:
description:
- Specify the type of certificate requested.
- This is only used by the C(entrust) provider.
- This is only used by the V(entrust) provider.
type: str
default: STANDARD_SSL
choices: [ 'STANDARD_SSL', 'ADVANTAGE_SSL', 'UC_SSL', 'EV_SSL', 'WILDCARD_SSL', 'PRIVATE_SSL', 'PD_SSL', 'CDS_ENT_LITE', 'CDS_ENT_PRO', 'SMIME_ENT' ]
@@ -327,65 +135,66 @@ options:
entrust_requester_email:
description:
- The email of the requester of the certificate (for tracking purposes).
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: str
entrust_requester_name:
description:
- The name of the requester of the certificate (for tracking purposes).
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: str
entrust_requester_phone:
description:
- The phone number of the requester of the certificate (for tracking purposes).
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: str
entrust_api_user:
description:
- The username for authentication to the Entrust Certificate Services (ECS) API.
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: str
entrust_api_key:
description:
- The key (password) for authentication to the Entrust Certificate Services (ECS) API.
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: str
entrust_api_client_cert_path:
description:
- The path to the client certificate used to authenticate to the Entrust Certificate Services (ECS) API.
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: path
entrust_api_client_cert_key_path:
description:
- The path to the private key of the client certificate used to authenticate to the Entrust Certificate Services (ECS) API.
- This is only used by the C(entrust) provider.
- This is required if the provider is C(entrust).
- This is only used by the V(entrust) provider.
- This is required if the provider is V(entrust).
type: path
entrust_not_after:
description:
- The point in time at which the certificate stops being valid.
- Time can be specified either as relative time or as an absolute timestamp.
- A valid absolute time format is C(ASN.1 TIME) such as C(2019-06-18).
- A valid relative time format is C([+-]timespec) where timespec can be an integer + C([w | d | h | m | s]), such as C(+365d) or C(+32w1d2h)).
- A valid absolute time format is C(ASN.1 TIME) such as V(2019-06-18).
- A valid relative time format is V([+-]timespec) where timespec can be an integer + C([w | d | h | m | s]), such as V(+365d) or V(+32w1d2h)).
- Time will always be interpreted as UTC.
- Note that only the date (day, month, year) is supported for specifying the expiry date of the issued certificate.
- The full date-time is adjusted to EST (GMT -5:00) before issuance, which may result in a certificate with an expiration date one day
earlier than expected if a relative time is used.
- The minimum certificate lifetime is 90 days, and maximum is three years.
- If this value is not specified, the certificate will stop being valid 365 days the date of issue.
- This is only used by the C(entrust) provider.
- This is only used by the V(entrust) provider.
- Please note that this value is B(not) covered by the O(ignore_timestamps) option.
type: str
default: +365d
@@ -393,60 +202,60 @@ options:
description:
- The path to the specification file defining the Entrust Certificate Services (ECS) API configuration.
- You can use this to keep a local copy of the specification to avoid downloading it every time the module is used.
- This is only used by the C(entrust) provider.
- This is only used by the V(entrust) provider.
type: path
default: https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml
'''
BACKEND_OWNCA_DOCUMENTATION = r'''
description:
- The C(ownca) provider is intended for generating an OpenSSL certificate signed with your own
- The V(ownca) provider is intended for generating an OpenSSL certificate signed with your own
CA (Certificate Authority) certificate (self-signed certificate).
options:
ownca_path:
description:
- Remote absolute path of the CA (Certificate Authority) certificate.
- This is only used by the C(ownca) provider.
- This is mutually exclusive with I(ownca_content).
- This is only used by the V(ownca) provider.
- This is mutually exclusive with O(ownca_content).
type: path
ownca_content:
description:
- Content of the CA (Certificate Authority) certificate.
- This is only used by the C(ownca) provider.
- This is mutually exclusive with I(ownca_path).
- This is only used by the V(ownca) provider.
- This is mutually exclusive with O(ownca_path).
type: str
ownca_privatekey_path:
description:
- Path to the CA (Certificate Authority) private key to use when signing the certificate.
- This is only used by the C(ownca) provider.
- This is mutually exclusive with I(ownca_privatekey_content).
- This is only used by the V(ownca) provider.
- This is mutually exclusive with O(ownca_privatekey_content).
type: path
ownca_privatekey_content:
description:
- Content of the CA (Certificate Authority) private key to use when signing the certificate.
- This is only used by the C(ownca) provider.
- This is mutually exclusive with I(ownca_privatekey_path).
- This is only used by the V(ownca) provider.
- This is mutually exclusive with O(ownca_privatekey_path).
type: str
ownca_privatekey_passphrase:
description:
- The passphrase for the I(ownca_privatekey_path) resp. I(ownca_privatekey_content).
- This is only used by the C(ownca) provider.
- The passphrase for the O(ownca_privatekey_path) resp. O(ownca_privatekey_content).
- This is only used by the V(ownca) provider.
type: str
ownca_digest:
description:
- The digest algorithm to be used for the C(ownca) certificate.
- This is only used by the C(ownca) provider.
- The digest algorithm to be used for the V(ownca) certificate.
- This is only used by the V(ownca) provider.
type: str
default: sha256
ownca_version:
description:
- The version of the C(ownca) certificate.
- Nowadays it should almost always be C(3).
- This is only used by the C(ownca) provider.
- The version of the V(ownca) certificate.
- Nowadays it should almost always be V(3).
- This is only used by the V(ownca) provider.
type: int
default: 3
@@ -456,10 +265,12 @@ options:
- Time can be specified either as relative time or as absolute timestamp.
- 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).
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
- If this value is not specified, the certificate will start being valid from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(ownca) provider.
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
avoid relative timestamps when setting O(ignore_timestamps=false).
- This is only used by the V(ownca) provider.
type: str
default: +0s
@@ -469,10 +280,12 @@ options:
- Time can be specified either as relative time or as absolute timestamp.
- 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).
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
- If this value is not specified, the certificate will stop being valid 10 years from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(ownca) provider.
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
avoid relative timestamps when setting O(ignore_timestamps=false).
- This is only used by the V(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.
type: str
@@ -481,12 +294,12 @@ options:
ownca_create_subject_key_identifier:
description:
- Whether to create the Subject Key Identifier (SKI) from the public key.
- A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not
- A value of V(create_if_not_provided) (default) only creates a SKI when the CSR does not
provide one.
- A value of C(always_create) always creates a SKI. If the CSR provides one, that one is
- A value of V(always_create) always creates a SKI. If the CSR provides one, that one is
ignored.
- A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used.
- This is only used by the C(ownca) provider.
- A value of V(never_create) never creates a SKI. If the CSR provides one, that one is used.
- This is only used by the V(ownca) provider.
- Note that this is only supported if the C(cryptography) backend is used!
type: str
choices: [create_if_not_provided, always_create, never_create]
@@ -498,15 +311,15 @@ options:
a authority key identifier, it is ignored.
- The Authority Key Identifier is generated from the CA certificate's Subject Key Identifier,
if available. If it is not available, the CA certificate's public key will be used.
- This is only used by the C(ownca) provider.
- This is only used by the V(ownca) provider.
- Note that this is only supported if the C(cryptography) backend is used!
type: bool
default: yes
default: true
'''
BACKEND_SELFSIGNED_DOCUMENTATION = r'''
notes:
- For the C(selfsigned) provider, I(csr_path) and I(csr_content) are optional. If not provided, a
- For the V(selfsigned) provider, O(csr_path) and O(csr_content) are optional. If not provided, a
certificate without any information (Subject, Subject Alternative Names, Key Usage, etc.) is created.
options:
@@ -516,28 +329,28 @@ options:
# csr_path:
# description:
# - This is optional for the C(selfsigned) provider. If not provided, a certificate
# - This is optional for the V(selfsigned) provider. If not provided, a certificate
# without any information (Subject, Subject Alternative Names, Key Usage, etc.) is
# created.
# csr_content:
# description:
# - This is optional for the C(selfsigned) provider. If not provided, a certificate
# - This is optional for the V(selfsigned) provider. If not provided, a certificate
# without any information (Subject, Subject Alternative Names, Key Usage, etc.) is
# created.
selfsigned_version:
description:
- Version of the C(selfsigned) certificate.
- Nowadays it should almost always be C(3).
- This is only used by the C(selfsigned) provider.
- Version of the V(selfsigned) certificate.
- Nowadays it should almost always be V(3).
- This is only used by the V(selfsigned) provider.
type: int
default: 3
selfsigned_digest:
description:
- Digest algorithm to be used when self-signing the certificate.
- This is only used by the C(selfsigned) provider.
- This is only used by the V(selfsigned) provider.
type: str
default: sha256
@@ -547,10 +360,12 @@ options:
- Time can be specified either as relative time or as absolute timestamp.
- 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).
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
- If this value is not specified, the certificate will start being valid from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(selfsigned) provider.
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
avoid relative timestamps when setting O(ignore_timestamps=false).
- This is only used by the V(selfsigned) provider.
type: str
default: +0s
aliases: [ selfsigned_notBefore ]
@@ -561,10 +376,12 @@ options:
- Time can be specified either as relative time or as absolute timestamp.
- 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).
+ C([w | d | h | m | s]) (for example V(+32w1d2h)).
- If this value is not specified, the certificate will stop being valid 10 years from now.
- Note that this value is B(not used to determine whether an existing certificate should be regenerated).
- This is only used by the C(selfsigned) provider.
This can be changed by setting the O(ignore_timestamps) option to V(false). Please note that you should
avoid relative timestamps when setting O(ignore_timestamps=false).
- This is only used by the V(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.
type: str
@@ -574,12 +391,12 @@ options:
selfsigned_create_subject_key_identifier:
description:
- Whether to create the Subject Key Identifier (SKI) from the public key.
- A value of C(create_if_not_provided) (default) only creates a SKI when the CSR does not
- A value of V(create_if_not_provided) (default) only creates a SKI when the CSR does not
provide one.
- A value of C(always_create) always creates a SKI. If the CSR provides one, that one is
- A value of V(always_create) always creates a SKI. If the CSR provides one, that one is
ignored.
- A value of C(never_create) never creates a SKI. If the CSR provides one, that one is used.
- This is only used by the C(selfsigned) provider.
- A value of V(never_create) never creates a SKI. If the CSR provides one, that one is used.
- This is only used by the V(selfsigned) provider.
- Note that this is only supported if the C(cryptography) backend is used!
type: str
choices: [create_if_not_provided, always_create, never_create]

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Copyrigt: (c) 2017, Yanis Guenane <yanis+ansible@guenane.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2017, Yanis Guenane <yanis+ansible@guenane.org>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -15,13 +16,8 @@ description:
- This module allows one to (re)generate OpenSSL certificate signing requests.
- This module supports the subjectAltName, keyUsage, extendedKeyUsage, basicConstraints and OCSP Must Staple
extensions.
- "The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
requirements:
- Either cryptography >= 1.3
- Or pyOpenSSL >= 0.15
- cryptography >= 1.3
options:
digest:
description:
@@ -31,12 +27,12 @@ options:
privatekey_path:
description:
- The path to the private key to use when signing the certificate signing request.
- Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both.
- Either O(privatekey_path) or O(privatekey_content) must be specified if O(state) is V(present), but not both.
type: path
privatekey_content:
description:
- The content of the private key to use when signing the certificate signing request.
- Either I(privatekey_path) or I(privatekey_content) must be specified if I(state) is C(present), but not both.
- Either O(privatekey_path) or O(privatekey_content) must be specified if O(state) is V(present), but not both.
type: str
privatekey_passphrase:
description:
@@ -48,14 +44,29 @@ options:
- The version of the certificate signing request.
- "The only allowed value according to L(RFC 2986,https://tools.ietf.org/html/rfc2986#section-4.1)
is 1."
- This option will no longer accept unsupported values from community.crypto 2.0.0 on.
- This option no longer accepts unsupported values since community.crypto 2.0.0.
type: int
default: 1
choices:
- 1
subject:
description:
- Key/value pairs that will be present in the subject name field of the certificate signing request.
- If you need to specify more than one value with the same key, use a list as value.
- If the order of the components is important, use O(subject_ordered).
- Mutually exclusive with O(subject_ordered).
type: dict
subject_ordered:
description:
- A list of dictionaries, where every dictionary must contain one key/value pair. This key/value pair
will be present in the subject name field of the certificate signing request.
- If you want to specify more than one value with the same key in a row, you can use a list as value.
- Mutually exclusive with O(subject), and any other subject field option, such as O(country_name),
O(state_or_province_name), O(locality_name), O(organization_name), O(organizational_unit_name),
O(common_name), or O(email_address).
type: list
elements: dict
version_added: 2.0.0
country_name:
description:
- The countryName field of the certificate signing request subject.
@@ -94,12 +105,11 @@ options:
subject_alt_name:
description:
- Subject Alternative Name (SAN) extension to attach to the certificate signing request.
- This can either be a 'comma separated string' or a YAML list.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA).
- Values must be prefixed by their options. (These are C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName), and the ones specific to your CA).
- Note that if no SAN is specified, but a common name, the common
name will be added as a SAN except if C(useCommonNameForSAN) is
set to I(false).
name will be added as a SAN except if O(use_common_name_for_san) is
set to V(false).
- More at U(https://tools.ietf.org/html/rfc5280#section-4.2.1.6).
type: list
elements: str
@@ -112,14 +122,14 @@ options:
aliases: [ subjectAltName_critical ]
use_common_name_for_san:
description:
- If set to C(yes), the module will fill the common name in for
C(subject_alt_name) with C(DNS:) prefix if no SAN is specified.
- If set to V(true), the module will fill the common name in for
O(subject_alt_name) with C(DNS:) prefix if no SAN is specified.
type: bool
default: yes
default: true
aliases: [ useCommonNameForSAN ]
key_usage:
description:
- This defines the purpose (e.g. encipherment, signature, certificate signing)
- This defines the purpose (for example encipherment, signature, certificate signing)
of the key contained in the certificate.
type: list
elements: str
@@ -132,7 +142,7 @@ options:
aliases: [ keyUsage_critical ]
extended_key_usage:
description:
- Additional restrictions (e.g. client authentication, server authentication)
- Additional restrictions (for example client authentication, server authentication)
on the allowed purposes for which the public key may be used.
type: list
elements: str
@@ -176,16 +186,16 @@ options:
description:
- For CA certificates, this specifies a list of identifiers which describe
subtrees of names that this CA is allowed to issue certificates for.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA).
- Values must be prefixed by their options. (That is, C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName), and the ones specific to your CA).
type: list
elements: str
name_constraints_excluded:
description:
- For CA certificates, this specifies a list of identifiers which describe
subtrees of names that this CA is B(not) allowed to issue certificates for.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA).
- Values must be prefixed by their options. (That is, C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName), and the ones specific to your CA).
type: list
elements: str
name_constraints_critical:
@@ -196,14 +206,11 @@ options:
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
From that point on, only the C(cryptography) backend will be available.
- The default choice is V(auto), which tries to use C(cryptography) if available.
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
choices: [ auto, cryptography ]
create_subject_key_identifier:
description:
- Create the Subject Key Identifier from the public key.
@@ -212,53 +219,55 @@ options:
certificates or for own CAs."
- Note that this is only supported if the C(cryptography) backend is used!
type: bool
default: no
default: false
subject_key_identifier:
description:
- The subject key identifier as a hex string, where two bytes are separated by colons.
- "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
- "Example: V(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- Note that this option can only be used if I(create_subject_key_identifier) is C(no).
- Note that this option can only be used if O(create_subject_key_identifier) is V(false).
- Note that this is only supported if the C(cryptography) backend is used!
type: str
authority_key_identifier:
description:
- The authority key identifier as a hex string, where two bytes are separated by colons.
- "Example: C(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
- "Example: V(00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33)"
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- Note that this is only supported if the C(cryptography) backend is used!
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of I(authority_key_identifier),
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier),
O(authority_cert_issuer) and O(authority_cert_serial_number) is specified.
type: str
authority_cert_issuer:
description:
- Names that will be present in the authority cert issuer field of the certificate signing request.
- Values must be prefixed by their options. (i.e., C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName) and the ones specific to your CA)
- "Example: C(DNS:ca.example.org)"
- If specified, I(authority_cert_serial_number) must also be specified.
- Values must be prefixed by their options. (That is, C(email), C(URI), C(DNS), C(RID), C(IP), C(dirName),
C(otherName), and the ones specific to your CA)
- "Example: V(DNS:ca.example.org)"
- If specified, O(authority_cert_serial_number) must also be specified.
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- Note that this is only supported if the C(cryptography) backend is used!
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of I(authority_key_identifier),
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier),
O(authority_cert_issuer) and O(authority_cert_serial_number) is specified.
type: list
elements: str
authority_cert_serial_number:
description:
- The authority cert serial number.
- If specified, I(authority_cert_issuer) must also be specified.
- If specified, O(authority_cert_issuer) must also be specified.
- Note that this is only supported if the C(cryptography) backend is used!
- "Please note that commercial CAs ignore this value, respectively use a value of their
own choice. Specifying this option is mostly useful for self-signed certificates
or for own CAs."
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of I(authority_key_identifier),
I(authority_cert_issuer) and I(authority_cert_serial_number) is specified.
- The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier),
O(authority_cert_issuer) and O(authority_cert_serial_number) is specified.
- This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings,
such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter).
type: int
crl_distribution_points:
description:
@@ -270,15 +279,15 @@ options:
full_name:
description:
- Describes how the CRL can be retrieved.
- Mutually exclusive with I(relative_name).
- "Example: C(URI:https://ca.example.com/revocations.crl)."
- Mutually exclusive with O(crl_distribution_points[].relative_name).
- "Example: V(URI:https://ca.example.com/revocations.crl)."
type: list
elements: str
relative_name:
description:
- Describes how the CRL can be retrieved relative to the CRL issuer.
- Mutually exclusive with I(full_name).
- "Example: C(/CN=example.com)."
- Mutually exclusive with O(crl_distribution_points[].full_name).
- "Example: V(/CN=example.com)."
- Can only be used when cryptography >= 1.6 is installed.
type: list
elements: str
@@ -315,4 +324,6 @@ seealso:
- module: community.crypto.openssl_privatekey_pipe
- module: community.crypto.openssl_publickey
- module: community.crypto.openssl_csr_info
- plugin: community.crypto.parse_serial
plugin_type: filter
'''

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -17,18 +18,8 @@ description:
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography) or
L(EdDSA,https://en.wikipedia.org/wiki/EdDSA) private keys.
- Keys are generated in PEM format.
- "Please note that the module regenerates private keys if they don't match
the module's options. In particular, if you provide another passphrase
(or specify none), change the keysize, etc., the private key will be
regenerated. If you are concerned that this could B(overwrite your private key),
consider using the I(backup) option."
- "The module can use the cryptography Python library, or the pyOpenSSL Python
library. By default, it tries to detect which one is available. This can be
overridden with the I(select_crypto_backend) option. Please note that the
PyOpenSSL backend was deprecated in Ansible 2.9 and will be removed in community.crypto 2.0.0."
requirements:
- Either cryptography >= 1.2.3 (older versions might work as well)
- Or pyOpenSSL
- cryptography >= 1.2.3 (older versions might work as well)
options:
size:
description:
@@ -38,20 +29,20 @@ options:
type:
description:
- The algorithm used to generate the TLS/SSL private key.
- Note that C(ECC), C(X25519), C(X448), C(Ed25519) and C(Ed448) require the C(cryptography) backend.
C(X25519) needs cryptography 2.5 or newer, while C(X448), C(Ed25519) and C(Ed448) require
cryptography 2.6 or newer. For C(ECC), the minimal cryptography version required depends on the
I(curve) option.
- Note that V(ECC), V(X25519), V(X448), V(Ed25519), and V(Ed448) require the C(cryptography) backend.
V(X25519) needs cryptography 2.5 or newer, while V(X448), V(Ed25519), and V(Ed448) require
cryptography 2.6 or newer. For V(ECC), the minimal cryptography version required depends on the
O(curve) option.
type: str
default: RSA
choices: [ DSA, ECC, Ed25519, Ed448, RSA, X25519, X448 ]
curve:
description:
- Note that not all curves are supported by all versions of C(cryptography).
- For maximal interoperability, C(secp384r1) or C(secp256r1) should be used.
- For maximal interoperability, V(secp384r1) or V(secp256r1) should be used.
- We use the curve names as defined in the
L(IANA registry for TLS,https://www.iana.org/assignments/tls-parameters/tls-parameters.xhtml#tls-parameters-8).
- Please note that all curves except C(secp224r1), C(secp256k1), C(secp256r1), C(secp384r1) and C(secp521r1)
- Please note that all curves except V(secp224r1), V(secp256k1), V(secp256r1), V(secp384r1), and V(secp521r1)
are discouraged for new private keys.
type: str
choices:
@@ -80,33 +71,25 @@ options:
type: str
cipher:
description:
- The cipher to encrypt the private key. (Valid values can be found by
running `openssl list -cipher-algorithms` or `openssl list-cipher-algorithms`,
depending on your OpenSSL version.)
- When using the C(cryptography) backend, use C(auto).
- The cipher to encrypt the private key. Must be V(auto).
type: str
select_crypto_backend:
description:
- Determines which crypto backend to use.
- The default choice is C(auto), which tries to use C(cryptography) if available, and falls back to C(pyopenssl).
- If set to C(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- If set to C(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- Please note that the C(pyopenssl) backend has been deprecated in Ansible 2.9, and will be removed in community.crypto 2.0.0.
From that point on, only the C(cryptography) backend will be available.
- The default choice is V(auto), which tries to use C(cryptography) if available.
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
type: str
default: auto
choices: [ auto, cryptography, pyopenssl ]
choices: [ auto, cryptography ]
format:
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 format based on the key format. The value C(auto_ignore) does the same,
- The value V(auto) selects a format based on the key format. The value V(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.
To change this behavior, use the I(format_mismatch) option.
- The I(format) option is only supported by the C(cryptography) backend. The C(pyopenssl) backend will
fail if a value different from C(auto_ignore) is used.
To change this behavior, use the O(format_mismatch) option.
type: str
default: auto_ignore
choices: [ pkcs1, pkcs8, raw, auto, auto_ignore ]
@@ -114,8 +97,8 @@ options:
description:
- Determines behavior of the module if the format of a private key does not match the expected format, but all
other parameters are as expected.
- If set to C(regenerate) (default), generates a new private key.
- If set to C(convert), the key will be converted to the new format instead.
- If set to V(regenerate) (default), generates a new private key.
- If set to V(convert), the key will be converted to the new format instead.
- Only supported by the C(cryptography) backend.
type: str
default: regenerate
@@ -124,26 +107,26 @@ options:
description:
- Allows to configure in which situations the module is allowed to regenerate private keys.
The module will always generate a new key if the destination file does not exist.
- By default, the key will be regenerated when it doesn't match the module's options,
- By default, the key will be regenerated when it does not match the module's options,
except when the key cannot be read or the passphrase does not match. Please note that
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if C(full_idempotence)
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if V(full_idempotence)
is specified.
- If set to C(never), the module will fail if the key cannot be read or the passphrase
isn't matching, and will never regenerate an existing key.
- If set to C(fail), the module will fail if the key does not correspond to the module's
- If set to V(never), the module will fail if the key cannot be read or the passphrase
is not matching, and will never regenerate an existing key.
- If set to V(fail), the module will fail if the key does not correspond to the module's
options.
- If set to C(partial_idempotence), the key will be regenerated if it does not conform to
- If set to V(partial_idempotence), the key will be regenerated if it does not conform to
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
the key is protected by an unknown passphrase, or when they key is not protected by a
passphrase, but a passphrase is specified.
- If set to C(full_idempotence), the key will be regenerated if it does not conform to the
- If set to V(full_idempotence), the key will be regenerated if it does not conform to the
module's options. This is also the case if the key cannot be read (broken file), the key
is protected by an unknown passphrase, or when they key is not protected by a passphrase,
but a passphrase is specified. Make sure you have a B(backup) when using this option!
- If set to C(always), the module will always regenerate the key. This is equivalent to
setting I(force) to C(yes).
- Note that if I(format_mismatch) is set to C(convert) and everything matches except the
format, the key will always be converted, except if I(regenerate) is set to C(always).
- If set to V(always), the module will always regenerate the key. This is equivalent to
setting O(force) to V(true).
- Note that if O(format_mismatch) is set to V(convert) and everything matches except the
format, the key will always be converted, except if O(regenerate) is set to V(always).
type: str
choices:
- never

View File

@@ -0,0 +1,48 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r'''
requirements:
- cryptography >= 1.2.3 (older versions might work as well)
options:
src_path:
description:
- Name of the file containing the OpenSSL private key to convert.
- Exactly one of O(src_path) or O(src_content) must be specified.
type: path
src_content:
description:
- The content of the file containing the OpenSSL private key to convert.
- Exactly one of O(src_path) or O(src_content) must be specified.
type: str
src_passphrase:
description:
- The passphrase for the private key to load.
type: str
dest_passphrase:
description:
- The passphrase for the private key to store.
type: str
format:
description:
- Determines which format the destination private key should be written in.
- Please note that not every key can be exported in any format, and that not every
format supports encryption.
type: str
choices: [ pkcs1, pkcs8, raw ]
required: true
seealso:
- module: community.crypto.openssl_privatekey
- module: community.crypto.openssl_privatekey_pipe
- module: community.crypto.openssl_publickey
'''

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class ModuleDocFragment(object):
DOCUMENTATION = r'''
options:
name_encoding:
description:
- How to encode names (DNS names, URIs, email addresses) in return values.
- V(ignore) will use the encoding returned by the backend.
- V(idna) will convert all labels of domain names to IDNA encoding.
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 encoding fails.
- V(unicode) will convert all labels of domain names to Unicode.
IDNA2008 will be preferred, and IDNA2003 will be used if IDNA2008 decoding fails.
- B(Note) that V(idna) and V(unicode) require the L(idna Python library,https://pypi.org/project/idna/) to be installed.
type: str
default: ignore
choices:
- ignore
- idna
- unicode
requirements:
- If O(name_encoding) is set to another value than V(ignore), the L(idna Python library,https://pypi.org/project/idna/) needs to be installed.
'''

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2023, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: gpg_fingerprint
short_description: Retrieve a GPG fingerprint from a GPG public or private key
author: Felix Fontein (@felixfontein)
version_added: 2.15.0
description:
- "Takes the content of a private or public GPG key as input and returns its fingerprint."
options:
_input:
description:
- The content of a GPG public or private key.
type: string
required: true
requirements:
- GnuPG (C(gpg) executable)
seealso:
- plugin: community.crypto.gpg_fingerprint
plugin_type: lookup
"""
EXAMPLES = """
- name: Show fingerprint of GPG public key
ansible.builtin.debug:
msg: "{{ lookup('file', '/path/to/public_key.gpg') | community.crypto.gpg_fingerprint }}"
"""
RETURN = """
_value:
description:
- The fingerprint of the provided public or private GPG key.
type: string
"""
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible.module_utils.six import string_types
from ansible_collections.community.crypto.plugins.module_utils.gnupg.cli import GPGError, get_fingerprint_from_bytes
from ansible_collections.community.crypto.plugins.plugin_utils.gnupg import PluginGPGRunner
def gpg_fingerprint(input):
if not isinstance(input, string_types):
raise AnsibleFilterError(
'The input for the community.crypto.gpg_fingerprint filter must be a string; got {type} instead'.format(type=type(input))
)
try:
gpg = PluginGPGRunner()
return get_fingerprint_from_bytes(gpg, to_bytes(input))
except GPGError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'gpg_fingerprint': gpg_fingerprint,
}

View File

@@ -0,0 +1,318 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
name: openssl_csr_info
short_description: Retrieve information from OpenSSL Certificate Signing Requests (CSR)
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Provided an OpenSSL Certificate Signing Requests (CSR), retrieve information.
- This is a filter version of the M(community.crypto.openssl_csr_info) module.
options:
_input:
description:
- The content of the OpenSSL CSR.
type: string
required: true
extends_documentation_fragment:
- community.crypto.name_encoding
seealso:
- module: community.crypto.openssl_csr_info
- plugin: community.crypto.to_serial
plugin_type: filter
'''
EXAMPLES = '''
- name: Show the Subject Alt Names of the CSR
ansible.builtin.debug:
msg: >-
{{
(
lookup('ansible.builtin.file', '/path/to/cert.csr')
| community.crypto.openssl_csr_info
).subject_alt_name | join(', ')
}}
'''
RETURN = '''
_value:
description:
- Information on the certificate.
type: dict
contains:
signature_valid:
description:
- Whether the CSR's signature is valid.
- In case the check returns V(false), the module will fail.
returned: success
type: bool
basic_constraints:
description: Entries in the C(basic_constraints) extension, or V(none) if extension is not present.
returned: success
type: list
elements: str
sample: ['CA:TRUE', 'pathlen:1']
basic_constraints_critical:
description: Whether the C(basic_constraints) extension is critical.
returned: success
type: bool
extended_key_usage:
description: Entries in the C(extended_key_usage) extension, or V(none) if extension is not present.
returned: success
type: list
elements: str
sample: [Biometric Info, DVCS, Time Stamping]
extended_key_usage_critical:
description: Whether the C(extended_key_usage) extension is critical.
returned: success
type: bool
extensions_by_oid:
description: Returns a dictionary for every extension OID
returned: success
type: dict
contains:
critical:
description: Whether the extension is critical.
returned: success
type: bool
value:
description:
- The Base64 encoded value (in DER format) of the extension.
- B(Note) that depending on the C(cryptography) version used, it is
not possible to extract the ASN.1 content of the extension, but only
to provide the re-encoded content of the extension in case it was
parsed by C(cryptography). This should usually result in exactly the
same value, except if the original extension value was malformed.
returned: success
type: str
sample: "MAMCAQU="
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
key_usage:
description: Entries in the C(key_usage) extension, or V(none) if extension is not present.
returned: success
type: str
sample: [Key Agreement, Data Encipherment]
key_usage_critical:
description: Whether the C(key_usage) extension is critical.
returned: success
type: bool
subject_alt_name:
description:
- Entries in the C(subject_alt_name) extension, or V(none) if extension is not present.
- See O(name_encoding) for how IDNs are handled.
returned: success
type: list
elements: str
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
subject_alt_name_critical:
description: Whether the C(subject_alt_name) extension is critical.
returned: success
type: bool
ocsp_must_staple:
description: V(true) if the OCSP Must Staple extension is present, V(none) otherwise.
returned: success
type: bool
ocsp_must_staple_critical:
description: Whether the C(ocsp_must_staple) extension is critical.
returned: success
type: bool
name_constraints_permitted:
description: List of permitted subtrees to sign certificates for.
returned: success
type: list
elements: str
sample: ['email:.somedomain.com']
name_constraints_excluded:
description:
- List of excluded subtrees the CA cannot sign certificates for.
- Is V(none) if extension is not present.
- See O(name_encoding) for how IDNs are handled.
returned: success
type: list
elements: str
sample: ['email:.com']
name_constraints_critical:
description:
- Whether the C(name_constraints) extension is critical.
- Is V(none) if extension is not present.
returned: success
type: bool
subject:
description:
- The CSR's subject as a dictionary.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: {"commonName": "www.example.com", "emailAddress": "test@example.com"}
subject_ordered:
description: The CSR's subject as an ordered list of tuples.
returned: success
type: list
elements: list
sample: [["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]
public_key:
description: CSR's public key in PEM format
returned: success
type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_type:
description:
- The CSR's public key's type.
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
- Will start with C(unknown) if the key type cannot be determined.
returned: success
type: str
sample: RSA
public_key_data:
description:
- Public key data. Depends on the public key's type.
returned: success
type: dict
contains:
size:
description:
- Bit size of modulus (RSA) or prime number (DSA).
type: int
returned: When RV(_value.public_key_type=RSA) or RV(_value.public_key_type=DSA)
modulus:
description:
- The RSA key's modulus.
type: int
returned: When RV(_value.public_key_type=RSA)
exponent:
description:
- The RSA key's public exponent.
type: int
returned: When RV(_value.public_key_type=RSA)
p:
description:
- The C(p) value for DSA.
- This is the prime modulus upon which arithmetic takes place.
type: int
returned: When RV(_value.public_key_type=DSA)
q:
description:
- The C(q) value for DSA.
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
multiplicative group of the prime field used.
type: int
returned: When RV(_value.public_key_type=DSA)
g:
description:
- The C(g) value for DSA.
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
type: int
returned: When RV(_value.public_key_type=DSA)
curve:
description:
- The curve's name for ECC.
type: str
returned: When RV(_value.public_key_type=ECC)
exponent_size:
description:
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
type: int
returned: When RV(_value.public_key_type=ECC)
x:
description:
- The C(x) coordinate for the public point on the elliptic curve.
type: int
returned: When RV(_value.public_key_type=ECC)
y:
description:
- For RV(_value.public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
- For RV(_value.public_key_type=DSA), this is the publicly known group element whose discrete logarithm with
respect to C(g) is the private key.
type: int
returned: When RV(_value.public_key_type=DSA) or RV(_value.public_key_type=ECC)
public_key_fingerprints:
description:
- Fingerprints of CSR's public key.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
subject_key_identifier:
description:
- The CSR's subject key identifier.
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
- Is V(none) if the C(SubjectKeyIdentifier) extension is not present.
returned: success
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
authority_key_identifier:
description:
- The CSR's authority key identifier.
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
authority_cert_issuer:
description:
- The CSR's authority cert issuer as a list of general names.
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
- See O(name_encoding) for how IDNs are handled.
returned: success
type: list
elements: str
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
authority_cert_serial_number:
description:
- The CSR's authority cert serial number.
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
returned: success
type: int
sample: 12345
'''
from ansible.errors import AnsibleFilterError
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr_info import (
get_csr_info,
)
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
def openssl_csr_info_filter(data, name_encoding='ignore'):
'''Extract information from X.509 PEM certificate.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.openssl_csr_info input must be a text type, not %s' % type(data))
if not isinstance(name_encoding, string_types):
raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding))
name_encoding = to_native(name_encoding)
if name_encoding not in ('ignore', 'idna', 'unicode'):
raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding)
module = FilterModuleMock({'name_encoding': name_encoding})
try:
return get_csr_info(module, 'cryptography', content=to_bytes(data), validate_signature=True)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'openssl_csr_info': openssl_csr_info_filter,
}

View File

@@ -0,0 +1,194 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
name: openssl_privatekey_info
short_description: Retrieve information from OpenSSL private keys
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Provided an OpenSSL private keys, retrieve information.
- This is a filter version of the M(community.crypto.openssl_privatekey_info) module.
options:
_input:
description:
- The content of the OpenSSL private key.
type: string
required: true
passphrase:
description:
- The passphrase for the private key.
type: str
return_private_key_data:
description:
- Whether to return private key data.
- Only set this to V(true) when you want private information about this key to
be extracted.
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!"
type: bool
default: false
extends_documentation_fragment:
- community.crypto.name_encoding
seealso:
- module: community.crypto.openssl_privatekey_info
'''
EXAMPLES = '''
- name: Show the Subject Alt Names of the CSR
ansible.builtin.debug:
msg: >-
{{
(
lookup('ansible.builtin.file', '/path/to/cert.csr')
| community.crypto.openssl_privatekey_info
).subject_alt_name | join(', ')
}}
'''
RETURN = '''
_value:
description:
- Information on the certificate.
type: dict
contains:
public_key:
description: Private key's public key in PEM format.
returned: success
type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_fingerprints:
description:
- Fingerprints of private key's public key.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
type:
description:
- The key's type.
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
- Will start with V(unknown) if the key type cannot be determined.
returned: success
type: str
sample: RSA
public_data:
description:
- Public key data. Depends on key type.
returned: success
type: dict
contains:
size:
description:
- Bit size of modulus (RSA) or prime number (DSA).
type: int
returned: When RV(_value.type=RSA) or RV(_value.type=DSA)
modulus:
description:
- The RSA key's modulus.
type: int
returned: When RV(_value.type=RSA)
exponent:
description:
- The RSA key's public exponent.
type: int
returned: When RV(_value.type=RSA)
p:
description:
- The C(p) value for DSA.
- This is the prime modulus upon which arithmetic takes place.
type: int
returned: When RV(_value.type=DSA)
q:
description:
- The C(q) value for DSA.
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
multiplicative group of the prime field used.
type: int
returned: When RV(_value.type=DSA)
g:
description:
- The C(g) value for DSA.
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
type: int
returned: When RV(_value.type=DSA)
curve:
description:
- The curve's name for ECC.
type: str
returned: When RV(_value.type=ECC)
exponent_size:
description:
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
type: int
returned: When RV(_value.type=ECC)
x:
description:
- The C(x) coordinate for the public point on the elliptic curve.
type: int
returned: When RV(_value.type=ECC)
y:
description:
- For RV(_value.type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
- For RV(_value.type=DSA), this is the publicly known group element whose discrete logarithm with
respect to C(g) is the private key.
type: int
returned: When RV(_value.type=DSA) or RV(_value.type=ECC)
private_data:
description:
- Private key data. Depends on key type.
returned: success and when O(return_private_key_data) is set to V(true)
type: dict
'''
from ansible.errors import AnsibleFilterError
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.privatekey_info import (
PrivateKeyParseError,
get_privatekey_info,
)
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
def openssl_privatekey_info_filter(data, passphrase=None, return_private_key_data=False):
'''Extract information from X.509 PEM certificate.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.openssl_privatekey_info input must be a text type, not %s' % type(data))
if passphrase is not None and not isinstance(passphrase, string_types):
raise AnsibleFilterError('The passphrase option must be a text type, not %s' % type(passphrase))
if not isinstance(return_private_key_data, bool):
raise AnsibleFilterError('The return_private_key_data option must be a boolean, not %s' % type(return_private_key_data))
module = FilterModuleMock({})
try:
result = get_privatekey_info(module, 'cryptography', content=to_bytes(data), passphrase=passphrase, return_private_key_data=return_private_key_data)
result.pop('can_parse_key', None)
result.pop('key_is_consistent', None)
return result
except PrivateKeyParseError as exc:
raise AnsibleFilterError(exc.error_message)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'openssl_privatekey_info': openssl_privatekey_info_filter,
}

View File

@@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
name: openssl_publickey_info
short_description: Retrieve information from OpenSSL public keys in PEM format
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Provided a public key in OpenSSL PEM format, retrieve information.
- This is a filter version of the M(community.crypto.openssl_publickey_info) module.
options:
_input:
description:
- The content of the OpenSSL PEM public key.
type: string
required: true
seealso:
- module: community.crypto.openssl_publickey_info
'''
EXAMPLES = '''
- name: Show the type of a public key
ansible.builtin.debug:
msg: >-
{{
(
lookup('ansible.builtin.file', '/path/to/public-key.pem')
| community.crypto.openssl_publickey_info
).type
}}
'''
RETURN = '''
_value:
description:
- Information on the public key.
type: dict
contains:
fingerprints:
description:
- Fingerprints of public key.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
type:
description:
- The key's type.
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
- Will start with V(unknown) if the key type cannot be determined.
returned: success
type: str
sample: RSA
public_data:
description:
- Public key data. Depends on key type.
returned: success
type: dict
contains:
size:
description:
- Bit size of modulus (RSA) or prime number (DSA).
type: int
returned: When RV(_value.type=RSA) or RV(_value.type=DSA)
modulus:
description:
- The RSA key's modulus.
type: int
returned: When RV(_value.type=RSA)
exponent:
description:
- The RSA key's public exponent.
type: int
returned: When RV(_value.type=RSA)
p:
description:
- The C(p) value for DSA.
- This is the prime modulus upon which arithmetic takes place.
type: int
returned: When RV(_value.type=DSA)
q:
description:
- The C(q) value for DSA.
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
multiplicative group of the prime field used.
type: int
returned: When RV(_value.type=DSA)
g:
description:
- The C(g) value for DSA.
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
type: int
returned: When RV(_value.type=DSA)
curve:
description:
- The curve's name for ECC.
type: str
returned: When RV(_value.type=ECC)
exponent_size:
description:
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
type: int
returned: When RV(_value.type=ECC)
x:
description:
- The C(x) coordinate for the public point on the elliptic curve.
type: int
returned: When RV(_value.type=ECC)
y:
description:
- For RV(_value.type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
- For RV(_value.type=DSA), this is the publicly known group element whose discrete logarithm with
respect to C(g) is the private key.
type: int
returned: When RV(_value.type=DSA) or RV(_value.type=ECC)
'''
from ansible.errors import AnsibleFilterError
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
PublicKeyParseError,
get_publickey_info,
)
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
def openssl_publickey_info_filter(data):
'''Extract information from OpenSSL PEM public key.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.openssl_publickey_info input must be a text type, not %s' % type(data))
module = FilterModuleMock({})
try:
return get_publickey_info(module, 'cryptography', content=to_bytes(data))
except PublicKeyParseError as exc:
raise AnsibleFilterError(exc.error_message)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'openssl_publickey_info': openssl_publickey_info_filter,
}

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: parse_serial
short_description: Convert a serial number as a colon-separated list of hex numbers to an integer
author: Felix Fontein (@felixfontein)
version_added: 2.18.0
description:
- "Parses a colon-separated list of hex numbers of the form C(00:11:22:33) and returns the corresponding integer."
options:
_input:
description:
- A serial number represented as a colon-separated list of hex numbers between 0 and 255.
- These numbers are interpreted as the byte presentation of an unsigned integer in network byte order.
That is, C(01:00) is interpreted as the integer 256.
type: string
required: true
seealso:
- plugin: community.crypto.to_serial
plugin_type: filter
"""
EXAMPLES = """
- name: Parse serial number
ansible.builtin.debug:
msg: "{{ '11:22:33' | community.crypto.parse_serial }}"
"""
RETURN = """
_value:
description:
- The serial number as an integer.
type: int
"""
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.six import string_types
from ansible_collections.community.crypto.plugins.module_utils.serial import parse_serial
def parse_serial_filter(input):
if not isinstance(input, string_types):
raise AnsibleFilterError(
'The input for the community.crypto.parse_serial filter must be a string; got {type} instead'.format(type=type(input))
)
try:
return parse_serial(to_native(input))
except ValueError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'parse_serial': parse_serial_filter,
}

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
name: split_pem
short_description: Split PEM file contents into multiple objects
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Split PEM file contents into multiple PEM objects. Comments or invalid parts are ignored.
options:
_input:
description:
- The PEM contents to split.
type: string
required: true
'''
EXAMPLES = '''
- name: Print all CA certificates
ansible.builtin.debug:
msg: '{{ item }}'
loop: >-
{{ lookup('ansible.builtin.file', '/path/to/ca-bundle.pem') | community.crypto.split_pem }}
'''
RETURN = '''
_value:
description:
- A list of PEM file contents.
type: list
elements: string
'''
from ansible.errors import AnsibleFilterError
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_text
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import split_pem_list
def split_pem_filter(data):
'''Split PEM file.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.split_pem input must be a text type, not %s' % type(data))
data = to_text(data)
return split_pem_list(data)
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'split_pem': split_pem_filter,
}

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2024, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: to_serial
short_description: Convert an integer to a colon-separated list of hex numbers
author: Felix Fontein (@felixfontein)
version_added: 2.18.0
description:
- "Converts an integer to a colon-separated list of hex numbers of the form C(00:11:22:33)."
options:
_input:
description:
- The non-negative integer to convert.
type: int
required: true
seealso:
- plugin: community.crypto.to_serial
plugin_type: filter
"""
EXAMPLES = """
- name: Convert integer to serial number
ansible.builtin.debug:
msg: "{{ 1234567 | community.crypto.to_serial }}"
"""
RETURN = """
_value:
description:
- A colon-separated list of hexadecimal numbers.
- Letters are upper-case, and all numbers have exactly two digits.
- The string is never empty. The representation of C(0) is C("00").
type: string
"""
from ansible.errors import AnsibleFilterError
from ansible.module_utils.common.text.converters import to_native
from ansible.module_utils.six import integer_types
from ansible_collections.community.crypto.plugins.module_utils.serial import to_serial
def to_serial_filter(input):
if not isinstance(input, integer_types):
raise AnsibleFilterError(
'The input for the community.crypto.to_serial filter must be an integer; got {type} instead'.format(type=type(input))
)
if input < 0:
raise AnsibleFilterError('The input for the community.crypto.to_serial filter must not be negative')
try:
return to_serial(input)
except ValueError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'to_serial': to_serial_filter,
}

View File

@@ -0,0 +1,354 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
name: x509_certificate_info
short_description: Retrieve information from X.509 certificates in PEM format
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Provided a X.509 certificate in PEM format, retrieve information.
- This is a filter version of the M(community.crypto.x509_certificate_info) module.
options:
_input:
description:
- The content of the X.509 certificate in PEM format.
type: string
required: true
extends_documentation_fragment:
- community.crypto.name_encoding
seealso:
- module: community.crypto.x509_certificate_info
- plugin: community.crypto.to_serial
plugin_type: filter
'''
EXAMPLES = '''
- name: Show the Subject Alt Names of the certificate
ansible.builtin.debug:
msg: >-
{{
(
lookup('ansible.builtin.file', '/path/to/cert.pem')
| community.crypto.x509_certificate_info
).subject_alt_name | join(', ')
}}
'''
RETURN = '''
_value:
description:
- Information on the certificate.
type: dict
contains:
expired:
description: Whether the certificate is expired (in other words, C(notAfter) is in the past).
returned: success
type: bool
basic_constraints:
description: Entries in the C(basic_constraints) extension, or V(none) if extension is not present.
returned: success
type: list
elements: str
sample: ["CA:TRUE", "pathlen:1"]
basic_constraints_critical:
description: Whether the C(basic_constraints) extension is critical.
returned: success
type: bool
extended_key_usage:
description: Entries in the C(extended_key_usage) extension, or V(none) if extension is not present.
returned: success
type: list
elements: str
sample: [Biometric Info, DVCS, Time Stamping]
extended_key_usage_critical:
description: Whether the C(extended_key_usage) extension is critical.
returned: success
type: bool
extensions_by_oid:
description: Returns a dictionary for every extension OID.
returned: success
type: dict
contains:
critical:
description: Whether the extension is critical.
returned: success
type: bool
value:
description:
- The Base64 encoded value (in DER format) of the extension.
- B(Note) that depending on the C(cryptography) version used, it is
not possible to extract the ASN.1 content of the extension, but only
to provide the re-encoded content of the extension in case it was
parsed by C(cryptography). This should usually result in exactly the
same value, except if the original extension value was malformed.
returned: success
type: str
sample: "MAMCAQU="
sample: {"1.3.6.1.5.5.7.1.24": { "critical": false, "value": "MAMCAQU="}}
key_usage:
description: Entries in the C(key_usage) extension, or V(none) if extension is not present.
returned: success
type: str
sample: [Key Agreement, Data Encipherment]
key_usage_critical:
description: Whether the C(key_usage) extension is critical.
returned: success
type: bool
subject_alt_name:
description:
- Entries in the C(subject_alt_name) extension, or V(none) if extension is not present.
- See O(name_encoding) for how IDNs are handled.
returned: success
type: list
elements: str
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
subject_alt_name_critical:
description: Whether the C(subject_alt_name) extension is critical.
returned: success
type: bool
ocsp_must_staple:
description: V(true) if the OCSP Must Staple extension is present, V(none) otherwise.
returned: success
type: bool
ocsp_must_staple_critical:
description: Whether the C(ocsp_must_staple) extension is critical.
returned: success
type: bool
issuer:
description:
- The certificate's issuer.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
issuer_ordered:
description: The certificate's issuer as an ordered list of tuples.
returned: success
type: list
elements: list
sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
subject:
description:
- The certificate's subject as a dictionary.
- Note that for repeated values, only the last one will be returned.
returned: success
type: dict
sample: {"commonName": "www.example.com", "emailAddress": "test@example.com"}
subject_ordered:
description: The certificate's subject as an ordered list of tuples.
returned: success
type: list
elements: list
sample: [["commonName", "www.example.com"], ["emailAddress": "test@example.com"]]
not_after:
description: C(notAfter) date as ASN.1 TIME.
returned: success
type: str
sample: '20190413202428Z'
not_before:
description: C(notBefore) date as ASN.1 TIME.
returned: success
type: str
sample: '20190331202428Z'
public_key:
description: Certificate's public key in PEM format.
returned: success
type: str
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..."
public_key_type:
description:
- The certificate's public key's type.
- One of V(RSA), V(DSA), V(ECC), V(Ed25519), V(X25519), V(Ed448), or V(X448).
- Will start with V(unknown) if the key type cannot be determined.
returned: success
type: str
sample: RSA
public_key_data:
description:
- Public key data. Depends on the public key's type.
returned: success
type: dict
contains:
size:
description:
- Bit size of modulus (RSA) or prime number (DSA).
type: int
returned: When RV(_value.public_key_type=RSA) or RV(_value.public_key_type=DSA)
modulus:
description:
- The RSA key's modulus.
type: int
returned: When RV(_value.public_key_type=RSA)
exponent:
description:
- The RSA key's public exponent.
type: int
returned: When RV(_value.public_key_type=RSA)
p:
description:
- The C(p) value for DSA.
- This is the prime modulus upon which arithmetic takes place.
type: int
returned: When RV(_value.public_key_type=DSA)
q:
description:
- The C(q) value for DSA.
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the
multiplicative group of the prime field used.
type: int
returned: When RV(_value.public_key_type=DSA)
g:
description:
- The C(g) value for DSA.
- This is the element spanning the subgroup of the multiplicative group of the prime field used.
type: int
returned: When RV(_value.public_key_type=DSA)
curve:
description:
- The curve's name for ECC.
type: str
returned: When RV(_value.public_key_type=ECC)
exponent_size:
description:
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used.
type: int
returned: When RV(_value.public_key_type=ECC)
x:
description:
- The C(x) coordinate for the public point on the elliptic curve.
type: int
returned: When RV(_value.public_key_type=ECC)
y:
description:
- For RV(_value.public_key_type=ECC), this is the C(y) coordinate for the public point on the elliptic curve.
- For RV(_value.public_key_type=DSA), this is the publicly known group element whose discrete logarithm with
respect to C(g) is the private key.
type: int
returned: When RV(_value.public_key_type=DSA) or RV(_value.public_key_type=ECC)
public_key_fingerprints:
description:
- Fingerprints of certificate's public key.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
fingerprints:
description:
- Fingerprints of the DER-encoded form of the whole certificate.
- For every hash algorithm available, the fingerprint is computed.
returned: success
type: dict
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63',
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..."
signature_algorithm:
description: The signature algorithm used to sign the certificate.
returned: success
type: str
sample: sha256WithRSAEncryption
serial_number:
description:
- The certificate's serial number.
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
returned: success
type: int
sample: 1234
version:
description: The certificate version.
returned: success
type: int
sample: 3
subject_key_identifier:
description:
- The certificate's subject key identifier.
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
- Is V(none) if the C(SubjectKeyIdentifier) extension is not present.
returned: success
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
authority_key_identifier:
description:
- The certificate's authority key identifier.
- The identifier is returned in hexadecimal, with V(:) used to separate bytes.
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
returned: success
type: str
sample: '00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff:00:11:22:33'
authority_cert_issuer:
description:
- The certificate's authority cert issuer as a list of general names.
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
- See O(name_encoding) for how IDNs are handled.
returned: success
type: list
elements: str
sample: ["DNS:www.ansible.com", "IP:1.2.3.4"]
authority_cert_serial_number:
description:
- The certificate's authority cert serial number.
- Is V(none) if the C(AuthorityKeyIdentifier) extension is not present.
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
returned: success
type: int
sample: 12345
ocsp_uri:
description: The OCSP responder URI, if included in the certificate. Will be
V(none) if no OCSP responder URI is included.
returned: success
type: str
issuer_uri:
description: The Issuer URI, if included in the certificate. Will be
V(none) if no issuer URI is included.
returned: success
type: str
'''
from ansible.errors import AnsibleFilterError
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
get_certificate_info,
)
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
def x509_certificate_info_filter(data, name_encoding='ignore'):
'''Extract information from X.509 PEM certificate.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.x509_certificate_info input must be a text type, not %s' % type(data))
if not isinstance(name_encoding, string_types):
raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding))
name_encoding = to_native(name_encoding)
if name_encoding not in ('ignore', 'idna', 'unicode'):
raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding)
module = FilterModuleMock({'name_encoding': name_encoding})
try:
return get_certificate_info(module, 'cryptography', content=to_bytes(data))
except OpenSSLObjectError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'x509_certificate_info': x509_certificate_info_filter,
}

View File

@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
name: x509_crl_info
short_description: Retrieve information from X.509 CRLs in PEM format
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Provided a X.509 crl in PEM format, retrieve information.
- This is a filter version of the M(community.crypto.x509_crl_info) module.
options:
_input:
description:
- The content of the X.509 CRL in PEM format.
type: string
required: true
list_revoked_certificates:
description:
- If set to V(false), the list of revoked certificates is not included in the result.
- This is useful when retrieving information on large CRL files. Enumerating all revoked
certificates can take some time, including serializing the result as JSON, sending it to
the Ansible controller, and decoding it again.
type: bool
default: true
version_added: 1.7.0
extends_documentation_fragment:
- community.crypto.name_encoding
seealso:
- module: community.crypto.x509_crl_info
- plugin: community.crypto.to_serial
plugin_type: filter
'''
EXAMPLES = '''
- name: Show the Organization Name of the CRL's subject
ansible.builtin.debug:
msg: >-
{{
(
lookup('ansible.builtin.file', '/path/to/cert.pem')
| community.crypto.x509_crl_info
).issuer.organizationName
}}
'''
RETURN = '''
_value:
description:
- Information on the CRL.
type: dict
contains:
format:
description:
- Whether the CRL is in PEM format (V(pem)) or in DER format (V(der)).
returned: success
type: str
sample: pem
choices:
- pem
- der
issuer:
description:
- The CRL's issuer.
- Note that for repeated values, only the last one will be returned.
- See O(name_encoding) for how IDNs are handled.
returned: success
type: dict
sample: {"organizationName": "Ansible", "commonName": "ca.example.com"}
issuer_ordered:
description: The CRL's issuer as an ordered list of tuples.
returned: success
type: list
elements: list
sample: [["organizationName", "Ansible"], ["commonName": "ca.example.com"]]
last_update:
description: The point in time from which this CRL can be trusted as ASN.1 TIME.
returned: success
type: str
sample: '20190413202428Z'
next_update:
description: The point in time from which a new CRL will be issued and the client has to check for it as ASN.1 TIME.
returned: success
type: str
sample: '20190413202428Z'
digest:
description: The signature algorithm used to sign the CRL.
returned: success
type: str
sample: sha256WithRSAEncryption
revoked_certificates:
description: List of certificates to be revoked.
returned: success if O(list_revoked_certificates=true)
type: list
elements: dict
contains:
serial_number:
description:
- Serial number of the certificate.
- This return value is an B(integer). If you need the serial numbers as a colon-separated hex string,
such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter).
type: int
sample: 1234
revocation_date:
description: The point in time the certificate was revoked as ASN.1 TIME.
type: str
sample: '20190413202428Z'
issuer:
description:
- The certificate's issuer.
- See O(name_encoding) for how IDNs are handled.
type: list
elements: str
sample: ["DNS:ca.example.org"]
issuer_critical:
description: Whether the certificate issuer extension is critical.
type: bool
sample: false
reason:
description:
- The value for the revocation reason extension.
type: str
sample: key_compromise
choices:
- unspecified
- key_compromise
- ca_compromise
- affiliation_changed
- superseded
- cessation_of_operation
- certificate_hold
- privilege_withdrawn
- aa_compromise
- remove_from_crl
reason_critical:
description: Whether the revocation reason extension is critical.
type: bool
sample: false
invalidity_date:
description: |
The point in time it was known/suspected that the private key was compromised
or that the certificate otherwise became invalid as ASN.1 TIME.
type: str
sample: '20190413202428Z'
invalidity_date_critical:
description: Whether the invalidity date extension is critical.
type: bool
sample: false
'''
import base64
import binascii
from ansible.errors import AnsibleFilterError
from ansible.module_utils.six import string_types
from ansible.module_utils.common.text.converters import to_bytes, to_native
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
identify_pem_format,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.crl_info import (
get_crl_info,
)
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock
def x509_crl_info_filter(data, name_encoding='ignore', list_revoked_certificates=True):
'''Extract information from X.509 PEM certificate.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.x509_crl_info input must be a text type, not %s' % type(data))
if not isinstance(name_encoding, string_types):
raise AnsibleFilterError('The name_encoding option must be of a text type, not %s' % type(name_encoding))
if not isinstance(list_revoked_certificates, bool):
raise AnsibleFilterError('The list_revoked_certificates option must be a boolean, not %s' % type(list_revoked_certificates))
name_encoding = to_native(name_encoding)
if name_encoding not in ('ignore', 'idna', 'unicode'):
raise AnsibleFilterError('The name_encoding option must be one of the values "ignore", "idna", or "unicode", not "%s"' % name_encoding)
data = to_bytes(data)
if not identify_pem_format(data):
try:
data = base64.b64decode(to_native(data))
except (binascii.Error, TypeError, ValueError, UnicodeEncodeError) as e:
pass
module = FilterModuleMock({'name_encoding': name_encoding})
try:
return get_crl_info(module, content=data, list_revoked_certificates=list_revoked_certificates)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(to_native(exc))
class FilterModule(object):
'''Ansible jinja2 filters'''
def filters(self):
return {
'x509_crl_info': x509_crl_info_filter,
}

View File

@@ -0,0 +1,64 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2023, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: gpg_fingerprint
short_description: Retrieve a GPG fingerprint from a GPG public or private key file
author: Felix Fontein (@felixfontein)
version_added: 2.15.0
description:
- "Takes a list of filenames pointing to GPG public or private key files. Returns the fingerprints for each of these keys."
options:
_terms:
description:
- A path to a GPG public or private key.
type: list
elements: path
required: true
requirements:
- GnuPG (C(gpg) executable)
seealso:
- plugin: community.crypto.gpg_fingerprint
plugin_type: filter
"""
EXAMPLES = """
- name: Show fingerprint of GPG public key
ansible.builtin.debug:
msg: "{{ lookup('community.crypto.gpg_fingerprint', '/path/to/public_key.gpg') }}"
"""
RETURN = """
_value:
description:
- The fingerprints of the provided public or private GPG keys.
- The list has one entry for every path provided.
type: list
elements: string
"""
from ansible.plugins.lookup import LookupBase
from ansible.errors import AnsibleLookupError
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.gnupg.cli import GPGError, get_fingerprint_from_file
from ansible_collections.community.crypto.plugins.plugin_utils.gnupg import PluginGPGRunner
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
self.set_options(direct=kwargs)
try:
gpg = PluginGPGRunner(cwd=self._loader.get_basedir())
result = []
for path in terms:
result.append(get_fingerprint_from_file(gpg, path))
return result
except GPGError as exc:
raise AnsibleLookupError(to_native(exc))

View File

@@ -3,7 +3,9 @@
# Implements multiple version numbering conventions for the
# Python Module Distribution Utilities.
#
# PSF License (see PSF-license.txt or https://opensource.org/licenses/Python-2.0)
# Copyright (c) 2001-2022 Python Software Foundation. All rights reserved.
# PSF License (see LICENSES/PSF-2.0.txt or https://opensource.org/licenses/Python-2.0)
# SPDX-License-Identifier: PSF-2.0
#
"""Provides classes to represent module version numbers (one class for

View File

@@ -1,90 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
import binascii
import copy
import datetime
import hashlib
import json
import locale
import os
import re
import shutil
import sys
import tempfile
import traceback
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.urls import fetch_url
from ansible.module_utils.six.moves.urllib.parse import unquote
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
get_default_argspec,
ACMEDirectory,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
CryptographyBackend,
CRYPTOGRAPHY_VERSION,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
OpenSSLCLIBackend,
)
from ansible_collections.community.crypto.plugins.module_utils.acme._compatibility import (
handle_standard_module_arguments,
set_crypto_backend,
HAS_CURRENT_CRYPTOGRAPHY,
)
from ansible_collections.community.crypto.plugins.module_utils.acme._compatibility import ACMELegacyAccount as ACMEAccount
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
read_file,
write_file,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
nopad_b64,
pem_to_der,
process_links,
)
def openssl_get_csr_identifiers(openssl_binary, module, csr_filename, csr_content=None):
module.deprecate(
'Please adjust your custom module/plugin to the ACME module_utils refactor '
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
'your code', version='2.0.0', collection_name='community.crypto')
return OpenSSLCLIBackend(module, openssl_binary=openssl_binary).get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
def cryptography_get_csr_identifiers(module, csr_filename, csr_content=None):
module.deprecate(
'Please adjust your custom module/plugin to the ACME module_utils refactor '
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
'your code', version='2.0.0', collection_name='community.crypto')
return CryptographyBackend(module).get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
def cryptography_get_cert_days(module, cert_file, now=None):
module.deprecate(
'Please adjust your custom module/plugin to the ACME module_utils refactor '
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
'your code', version='2.0.0', collection_name='community.crypto')
return CryptographyBackend(module).get_cert_days(cert_filename=cert_file, now=now)

View File

@@ -1,267 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import locale
from ansible.module_utils.basic import missing_required_lib
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import HAS_CURRENT_CRYPTOGRAPHY as _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
CryptographyBackend,
CRYPTOGRAPHY_VERSION,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
OpenSSLCLIBackend,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
ACMEClient,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.account import (
ACMEAccount,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
create_key_authorization,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
KeyParsingError,
)
HAS_CURRENT_CRYPTOGRAPHY = _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY
def set_crypto_backend(module):
'''
Sets which crypto backend to use (default: auto detection).
Does not care whether a new enough cryptoraphy is available or not. Must
be called before any real stuff is done which might evaluate
``HAS_CURRENT_CRYPTOGRAPHY``.
'''
global HAS_CURRENT_CRYPTOGRAPHY
module.deprecate(
'Please adjust your custom module/plugin to the ACME module_utils refactor '
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
'your code', version='2.0.0', collection_name='community.crypto')
# Choose backend
backend = module.params['select_crypto_backend']
if backend == 'auto':
pass
elif backend == 'openssl':
HAS_CURRENT_CRYPTOGRAPHY = False
elif backend == 'cryptography':
if not _ORIGINAL_HAS_CURRENT_CRYPTOGRAPHY:
module.fail_json(msg=missing_required_lib('cryptography'))
HAS_CURRENT_CRYPTOGRAPHY = True
else:
module.fail_json(msg='Unknown crypto backend "{0}"!'.format(backend))
# Inform about choices
if HAS_CURRENT_CRYPTOGRAPHY:
module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION))
return 'cryptography'
else:
module.debug('Using OpenSSL binary backend')
return 'openssl'
def handle_standard_module_arguments(module, needs_acme_v2=False):
'''
Do standard module setup, argument handling and warning emitting.
'''
backend = set_crypto_backend(module)
if not module.params['validate_certs']:
module.warn(
'Disabling certificate validation for communications with ACME endpoint. '
'This should only be done for testing against a local ACME server for '
'development purposes, but *never* for production purposes.'
)
if module.params['acme_version'] is None:
module.params['acme_version'] = 1
module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on",
version='2.0.0', collection_name='community.crypto')
if module.params['acme_directory'] is None:
module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory'
module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on",
version='2.0.0', collection_name='community.crypto')
if needs_acme_v2 and module.params['acme_version'] < 2:
module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name))
# AnsibleModule() changes the locale, so change it back to C because we rely on time.strptime() when parsing certificate dates.
module.run_command_environ_update = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
locale.setlocale(locale.LC_ALL, 'C')
return backend
def get_compatibility_backend(module):
if HAS_CURRENT_CRYPTOGRAPHY:
return CryptographyBackend(module)
else:
return OpenSSLCLIBackend(module)
class ACMELegacyAccount(object):
'''
ACME account object. Handles the authorized communication with the
ACME server. Provides access to account bound information like
the currently active authorizations and valid certificates
'''
def __init__(self, module):
module.deprecate(
'Please adjust your custom module/plugin to the ACME module_utils refactor '
'(https://github.com/ansible-collections/community.crypto/pull/184). The '
'compatibility layer will be removed in community.crypto 2.0.0, thus breaking '
'your code', version='2.0.0', collection_name='community.crypto')
backend = get_compatibility_backend(module)
self.client = ACMEClient(module, backend)
self.account = ACMEAccount(self.client)
self.key = self.client.account_key_file
self.key_content = self.client.account_key_content
self.uri = self.client.account_uri
self.key_data = self.client.account_key_data
self.jwk = self.client.account_jwk
self.jws_header = self.client.account_jws_header
self.directory = self.client.directory
def get_keyauthorization(self, token):
'''
Returns the key authorization for the given token
https://tools.ietf.org/html/rfc8555#section-8.1
'''
return create_key_authorization(self.client, token)
def parse_key(self, key_file=None, key_content=None):
'''
Parses an RSA or Elliptic Curve key file in PEM format and returns a pair
(error, key_data).
'''
try:
return None, self.client.parse_key(key_file=key_file, key_content=key_content)
except KeyParsingError as e:
return e.msg, {}
def sign_request(self, protected, payload, key_data, encode_payload=True):
return self.client.sign_request(protected, payload, key_data, encode_payload=encode_payload)
def send_signed_request(self, url, payload, key_data=None, jws_header=None, parse_json_result=True, encode_payload=True):
'''
Sends a JWS signed HTTP POST request to the ACME server and returns
the response as dictionary
https://tools.ietf.org/html/rfc8555#section-6.2
If payload is None, a POST-as-GET is performed.
(https://tools.ietf.org/html/rfc8555#section-6.3)
'''
return self.client.send_signed_request(
url,
payload,
key_data=key_data,
jws_header=jws_header,
parse_json_result=parse_json_result,
encode_payload=encode_payload,
fail_on_error=False,
)
def get_request(self, uri, parse_json_result=True, headers=None, get_only=False, fail_on_error=True):
'''
Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback
to GET if server replies with a status code of 405.
'''
return self.client.get_request(
uri,
parse_json_result=parse_json_result,
headers=headers,
get_only=get_only,
fail_on_error=fail_on_error,
)
def set_account_uri(self, uri):
'''
Set account URI. For ACME v2, it needs to be used to sending signed
requests.
'''
self.client.set_account_uri(uri)
self.uri = self.client.account_uri
def get_account_data(self):
'''
Retrieve account information. Can only be called when the account
URI is already known (such as after calling setup_account).
Return None if the account was deactivated, or a dict otherwise.
'''
return self.account.get_account_data()
def setup_account(self, contact=None, agreement=None, terms_agreed=False,
allow_creation=True, remove_account_uri_if_not_exists=False,
external_account_binding=None):
'''
Detect or create an account on the ACME server. For ACME v1,
as the only way (without knowing an account URI) to test if an
account exists is to try and create one with the provided account
key, this method will always result in an account being present
(except on error situations). For ACME v2, a new account will
only be created if ``allow_creation`` is set to True.
For ACME v2, ``check_mode`` is fully respected. For ACME v1, the
account might be created if it does not yet exist.
Return a pair ``(created, account_data)``. Here, ``created`` will
be ``True`` in case the account was created or would be created
(check mode). ``account_data`` will be the current account data,
or ``None`` if the account does not exist.
The account URI will be stored in ``self.uri``; if it is ``None``,
the account does not exist.
If specified, ``external_account_binding`` should be a dictionary
with keys ``kid``, ``alg`` and ``key``
(https://tools.ietf.org/html/rfc8555#section-7.3.4).
https://tools.ietf.org/html/rfc8555#section-7.3
'''
result = self.account.setup_account(
contact=contact,
agreement=agreement,
terms_agreed=terms_agreed,
allow_creation=allow_creation,
remove_account_uri_if_not_exists=remove_account_uri_if_not_exists,
external_account_binding=external_account_binding,
)
self.uri = self.client.account_uri
return result
def update_account(self, account_data, contact=None):
'''
Update an account on the ACME server. Check mode is fully respected.
The current account data must be provided as ``account_data``.
Return a pair ``(updated, account_data)``, where ``updated`` is
``True`` in case something changed (contact info updated) or
would be changed (check mode), and ``account_data`` the updated
account data.
https://tools.ietf.org/html/rfc8555#section-7.3.2
'''
return self.account.update_account(account_data, contact=contact)

View File

@@ -1,13 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.common._collections_compat import Mapping
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
ACMEProtocolException,
ModuleFailException,
@@ -61,7 +64,7 @@ class ACMEAccount(object):
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
# to see whether the account already exists.
# Note that we pass contact here: ZeroSSL does not accept regisration calls without contacts, even
# Note that we pass contact here: ZeroSSL does not accept registration calls without contacts, even
# if onlyReturnExisting is set to true.
created, data = self._new_reg(contact=contact, allow_creation=False)
if data:
@@ -95,6 +98,9 @@ class ACMEAccount(object):
)
result, info = self.client.send_signed_request(url, new_reg, fail_on_error=False)
if not isinstance(result, Mapping):
raise ACMEProtocolException(
self.client.module, msg='Invalid account creation reply from ACME server', info=info, content=result)
if info['status'] in ([200, 201] if self.client.version == 1 else [201]):
# Account did not exist
@@ -117,11 +123,13 @@ class ACMEAccount(object):
if 'location' in info:
self.client.set_account_uri(info['location'])
return False, result
elif info['status'] == 400 and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation:
# Account does not exist (and we didn't try to create it)
elif info['status'] in (400, 404) and result['type'] == 'urn:ietf:params:acme:error:accountDoesNotExist' and not allow_creation:
# Account does not exist (and we did not try to create it)
# (According to RFC 8555, Section 7.3.1, the HTTP status code MUST be 400.
# Unfortunately Digicert does not care and sends 404 instead.)
return False, None
elif info['status'] == 403 and result['type'] == 'urn:ietf:params:acme:error:unauthorized' and 'deactivated' in (result.get('detail') or ''):
# Account has been deactivated; currently works for Pebble; hasn't been
# Account has been deactivated; currently works for Pebble; has not been
# implemented for Boulder (https://github.com/letsencrypt/boulder/issues/3971),
# might need adjustment in error detection.
if not allow_creation:
@@ -153,6 +161,9 @@ class ACMEAccount(object):
# retry as a regular POST (with no changed data) for pre-draft-15 ACME servers
data = {}
result, info = self.client.send_signed_request(self.client.account_uri, data, fail_on_error=False)
if not isinstance(result, Mapping):
raise ACMEProtocolException(
self.client.module, msg='Invalid account data retrieved from ACME server', info=info, content=result)
if info['status'] in (400, 403) and result.get('type') == 'urn:ietf:params:acme:error:unauthorized':
# Returned when account is deactivated
return None
@@ -247,5 +258,9 @@ class ACMEAccount(object):
else:
if self.client.version == 1:
update_request['resource'] = 'reg'
account_data, dummy = self.client.send_signed_request(self.client.account_uri, update_request)
account_data, info = self.client.send_signed_request(self.client.account_uri, update_request)
if not isinstance(account_data, Mapping):
raise ACMEProtocolException(
self.client.module, msg='Invalid account updating reply from ACME server', info=info, content=account_data)
return True, account_data

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -12,18 +13,24 @@ import copy
import datetime
import json
import locale
import time
import traceback
from ansible.module_utils.basic import missing_required_lib
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.argspec import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_openssl_cli import (
OpenSSLCLIBackend,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryptography import (
CryptographyBackend,
CRYPTOGRAPHY_ERROR,
CRYPTOGRAPHY_MINIMAL_VERSION,
CRYPTOGRAPHY_VERSION,
HAS_CURRENT_CRYPTOGRAPHY,
)
@@ -33,12 +40,49 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
NetworkException,
ModuleFailException,
KeyParsingError,
format_http_status,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
compute_cert_id,
nopad_b64,
parse_retry_after,
)
try:
import ipaddress # noqa: F401, pylint: disable=unused-import
except ImportError:
HAS_IPADDRESS = False
IPADDRESS_IMPORT_ERROR = traceback.format_exc()
else:
HAS_IPADDRESS = True
IPADDRESS_IMPORT_ERROR = None
# -1 usually means connection problems
RETRY_STATUS_CODES = (-1, 408, 429, 503)
RETRY_COUNT = 10
def _decode_retry(module, response, info, retry_count):
if info['status'] not in RETRY_STATUS_CODES:
return False
if retry_count >= RETRY_COUNT:
raise ACMEProtocolException(
module, msg='Giving up after {retry} retries'.format(retry=RETRY_COUNT), info=info, response=response)
# 429 and 503 should have a Retry-After header (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After)
try:
retry_after = min(max(1, int(info.get('retry-after'))), 60)
except (TypeError, ValueError) as dummy:
retry_after = 10
module.log('Retrieved a %s HTTP status on %s, retrying in %s seconds' % (format_http_status(info['status']), info['url'], retry_after))
time.sleep(retry_after)
return True
def _assert_fetch_url_success(module, response, info, allow_redirect=False, allow_client_error=True, allow_server_error=True):
if info['status'] < 0:
@@ -74,6 +118,8 @@ class ACMEDirectory(object):
self.directory, dummy = account.get_request(self.directory_root, get_only=True)
self.request_timeout = module.params['request_timeout']
# Check whether self.version matches what we expect
if self.version == 1:
for key in ('new-reg', 'new-authz', 'new-cert'):
@@ -94,10 +140,25 @@ class ACMEDirectory(object):
url = self.directory_root if self.version == 1 else self.directory['newNonce']
if resource is not None:
url = resource
dummy, info = fetch_url(self.module, url, method='HEAD')
if info['status'] not in (200, 204):
raise NetworkException("Failed to get replay-nonce, got status {0}".format(info['status']))
return info['replay-nonce']
retry_count = 0
while True:
response, info = fetch_url(self.module, url, method='HEAD', timeout=self.request_timeout)
if _decode_retry(self.module, response, info, retry_count):
retry_count += 1
continue
if info['status'] not in (200, 204):
raise NetworkException("Failed to get replay-nonce, got status {0}".format(format_http_status(info['status'])))
if 'replay-nonce' in info:
return info['replay-nonce']
self.module.log(
'HEAD to {0} did return status {1}, but no replay-nonce header!'.format(url, format_http_status(info['status'])))
if retry_count >= 5:
raise ACMEProtocolException(
self.module, msg='Was not able to obtain nonce, giving up after 5 retries', info=info, response=response)
retry_count += 1
def has_renewal_info_endpoint(self):
return 'renewalInfo' in self.directory
class ACMEClient(object):
@@ -114,14 +175,16 @@ class ACMEClient(object):
self.backend = backend
self.version = module.params['acme_version']
# account_key path and content are mutually exclusive
self.account_key_file = module.params['account_key_src']
self.account_key_content = module.params['account_key_content']
self.account_key_passphrase = module.params['account_key_passphrase']
self.account_key_file = module.params.get('account_key_src')
self.account_key_content = module.params.get('account_key_content')
self.account_key_passphrase = module.params.get('account_key_passphrase')
# Grab account URI from module parameters.
# Make sure empty string is treated as None.
self.account_uri = module.params.get('account_uri') or None
self.request_timeout = module.params['request_timeout']
self.account_key_data = None
self.account_jwk = None
self.account_jws_header = None
@@ -226,7 +289,10 @@ class ACMEClient(object):
headers = {
'Content-Type': 'application/jose+json',
}
resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST')
resp, info = fetch_url(self.module, url, data=data, headers=headers, method='POST', timeout=self.request_timeout)
if _decode_retry(self.module, resp, info, failed_tries):
failed_tries += 1
continue
_assert_fetch_url_success(self.module, resp, info)
result = {}
@@ -285,7 +351,12 @@ class ACMEClient(object):
if get_only:
# Perform unauthenticated GET
resp, info = fetch_url(self.module, uri, method='GET', headers=headers)
retry_count = 0
while True:
resp, info = fetch_url(self.module, uri, method='GET', headers=headers, timeout=self.request_timeout)
if not _decode_retry(self.module, resp, info, retry_count):
break
retry_count += 1
_assert_fetch_url_success(self.module, resp, info)
@@ -319,24 +390,98 @@ class ACMEClient(object):
self.module, msg=error_msg, info=info, content=content, content_json=result if parsed_json_result else None)
return result, info
def get_renewal_info(
self,
cert_id=None,
cert_info=None,
cert_filename=None,
cert_content=None,
include_retry_after=False,
retry_after_relative_with_timezone=True,
):
if not self.directory.has_renewal_info_endpoint():
raise ModuleFailException('The ACME endpoint does not support ACME Renewal Information retrieval')
if cert_id is None:
cert_id = compute_cert_id(self.backend, cert_info=cert_info, cert_filename=cert_filename, cert_content=cert_content)
url = '{base}{cert_id}'.format(base=self.directory.directory['renewalInfo'], cert_id=cert_id)
data, info = self.get_request(url, parse_json_result=True, fail_on_error=True, get_only=True)
# Include Retry-After header if asked for
if include_retry_after and 'retry-after' in info:
try:
data['retryAfter'] = parse_retry_after(
info['retry-after'],
relative_with_timezone=retry_after_relative_with_timezone,
)
except ValueError:
pass
return data
def get_default_argspec():
'''
Provides default argument spec for the options documented in the acme doc fragment.
DEPRECATED: will be removed in community.crypto 3.0.0
'''
return dict(
acme_directory=dict(type='str', required=True),
acme_version=dict(type='int', required=True, choices=[1, 2]),
validate_certs=dict(type='bool', default=True),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
request_timeout=dict(type='int', default=10),
account_key_src=dict(type='path', aliases=['account_key']),
account_key_content=dict(type='str', no_log=True),
account_key_passphrase=dict(type='str', no_log=True),
account_uri=dict(type='str'),
acme_directory=dict(type='str'),
acme_version=dict(type='int', choices=[1, 2]),
validate_certs=dict(type='bool', default=True),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
)
def create_default_argspec(
with_account=True,
require_account_key=True,
with_certificate=False,
):
'''
Provides default argument spec for the options documented in the acme doc fragment.
'''
result = ArgumentSpec(
argument_spec=dict(
acme_directory=dict(type='str', required=True),
acme_version=dict(type='int', required=True, choices=[1, 2]),
validate_certs=dict(type='bool', default=True),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'openssl', 'cryptography']),
request_timeout=dict(type='int', default=10),
),
)
if with_account:
result.update_argspec(
account_key_src=dict(type='path', aliases=['account_key']),
account_key_content=dict(type='str', no_log=True),
account_key_passphrase=dict(type='str', no_log=True),
account_uri=dict(type='str'),
)
if require_account_key:
result.update(required_one_of=[['account_key_src', 'account_key_content']])
result.update(mutually_exclusive=[['account_key_src', 'account_key_content']])
if with_certificate:
result.update_argspec(
csr=dict(type='path'),
csr_content=dict(type='str'),
)
result.update(
required_one_of=[['csr', 'csr_content']],
mutually_exclusive=[['csr', 'csr_content']],
)
return result
def create_backend(module, needs_acme_v2):
if not HAS_IPADDRESS:
module.fail_json(msg=missing_required_lib('ipaddress'), exception=IPADDRESS_IMPORT_ERROR)
backend = module.params['select_crypto_backend']
# Backend autodetect
@@ -345,8 +490,19 @@ def create_backend(module, needs_acme_v2):
# Create backend object
if backend == 'cryptography':
if CRYPTOGRAPHY_ERROR is not None:
# Either we couldn't import cryptography at all, or there was an unexpected error
if CRYPTOGRAPHY_VERSION is None:
msg = missing_required_lib('cryptography')
else:
msg = 'Unexpected error while preparing cryptography: {0}'.format(CRYPTOGRAPHY_ERROR.splitlines()[-1])
module.fail_json(msg=msg, exception=CRYPTOGRAPHY_ERROR)
if not HAS_CURRENT_CRYPTOGRAPHY:
module.fail_json(msg=missing_required_lib('cryptography'))
# We succeeded importing cryptography, but its version is too old.
module.fail_json(
msg='Found cryptography, but only version {0}. {1}'.format(
CRYPTOGRAPHY_VERSION,
missing_required_lib('cryptography >= {0}'.format(CRYPTOGRAPHY_MINIMAL_VERSION))))
module.debug('Using cryptography backend (library version {0})'.format(CRYPTOGRAPHY_VERSION))
module_backend = CryptographyBackend(module)
elif backend == 'openssl':
@@ -363,19 +519,13 @@ def create_backend(module, needs_acme_v2):
'development purposes, but *never* for production purposes.'
)
if module.params['acme_version'] is None:
module.params['acme_version'] = 1
module.deprecate("The option 'acme_version' will be required from community.crypto 2.0.0 on",
version='2.0.0', collection_name='community.crypto')
if module.params['acme_directory'] is None:
module.params['acme_directory'] = 'https://acme-staging.api.letsencrypt.org/directory'
module.deprecate("The option 'acme_directory' will be required from community.crypto 2.0.0 on",
version='2.0.0', collection_name='community.crypto')
if needs_acme_v2 and module.params['acme_version'] < 2:
module.fail_json(msg='The {0} module requires the ACME v2 protocol!'.format(module._name))
if module.params['acme_version'] == 1:
module.deprecate("The value 1 for 'acme_version' is deprecated. Please switch to ACME v2",
version='3.0.0', collection_name='community.crypto')
# AnsibleModule() changes the locale, so change it back to C because we rely
# on datetime.datetime.strptime() when parsing certificate dates.
locale.setlocale(locale.LC_ALL, 'C')

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -12,14 +13,16 @@ import base64
import binascii
import datetime
import os
import sys
import traceback
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 (
CertificateInformation,
CryptoBackend,
_parse_acme_timestamp,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.certificates import (
@@ -35,18 +38,43 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.io import re
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
parse_name_field,
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
convert_int_to_bytes,
convert_int_to_hex,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_name_to_oid,
cryptography_serial_number_of_cert,
get_not_valid_after,
get_not_valid_before,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
extract_first_pem,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
parse_name_field,
)
from ansible_collections.community.crypto.plugins.module_utils.time import (
ensure_utc_timezone,
from_epoch_seconds,
get_epoch_seconds,
get_now_datetime,
get_relative_time_option,
UTC,
)
CRYPTOGRAPHY_MINIMAL_VERSION = '1.5'
CRYPTOGRAPHY_ERROR = None
try:
import cryptography
import cryptography.hazmat.backends
@@ -59,47 +87,18 @@ try:
import cryptography.hazmat.primitives.serialization
import cryptography.x509
import cryptography.x509.oid
CRYPTOGRAPHY_VERSION = cryptography.__version__
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion('1.5'))
if HAS_CURRENT_CRYPTOGRAPHY:
_cryptography_backend = cryptography.hazmat.backends.default_backend()
except Exception as dummy:
except ImportError as dummy:
HAS_CURRENT_CRYPTOGRAPHY = False
CRYPTOGRAPHY_VERSION = None
if sys.version_info[0] >= 3:
# Python 3 (and newer)
def _count_bytes(n):
return (n.bit_length() + 7) // 8 if n > 0 else 0
def _convert_int_to_bytes(count, no):
return no.to_bytes(count, byteorder='big')
def _pad_hex(n, digits):
res = hex(n)[2:]
if len(res) < digits:
res = '0' * (digits - len(res)) + res
return res
CRYPTOGRAPHY_ERROR = traceback.format_exc()
else:
# Python 2
def _count_bytes(n):
if n <= 0:
return 0
h = '%x' % n
return (len(h) + 1) // 2
def _convert_int_to_bytes(count, n):
h = '%x' % n
if len(h) > 2 * count:
raise Exception('Number {1} needs more than {0} bytes!'.format(count, n))
return ('0' * (2 * count - len(h)) + h).decode('hex')
def _pad_hex(n, digits):
h = '%x' % n
if len(h) < digits:
h = '0' * (digits - len(h)) + h
return h
CRYPTOGRAPHY_VERSION = cryptography.__version__
HAS_CURRENT_CRYPTOGRAPHY = (LooseVersion(CRYPTOGRAPHY_VERSION) >= LooseVersion(CRYPTOGRAPHY_MINIMAL_VERSION))
try:
if HAS_CURRENT_CRYPTOGRAPHY:
_cryptography_backend = cryptography.hazmat.backends.default_backend()
except Exception as dummy:
CRYPTOGRAPHY_ERROR = traceback.format_exc()
class CryptographyChainMatcher(ChainMatcher):
@@ -123,11 +122,11 @@ class CryptographyChainMatcher(ChainMatcher):
self.issuer = []
if criterium.subject:
self.subject = [
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject)
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.subject, 'subject')
]
if criterium.issuer:
self.issuer = [
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer)
(cryptography_name_to_oid(k), to_native(v)) for k, v in parse_name_field(criterium.issuer, 'issuer')
]
self.subject_key_identifier = CryptographyChainMatcher._parse_key_identifier(
criterium.subject_key_identifier, 'subject_key_identifier', criterium.index, module)
@@ -187,12 +186,38 @@ class CryptographyBackend(CryptoBackend):
def __init__(self, module):
super(CryptographyBackend, self).__init__(module)
def get_now(self):
return get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
def parse_acme_timestamp(self, timestamp_str):
return _parse_acme_timestamp(timestamp_str, with_timezone=CRYPTOGRAPHY_TIMEZONE)
def parse_module_parameter(self, value, name):
try:
return get_relative_time_option(value, name, backend='cryptography', with_timezone=CRYPTOGRAPHY_TIMEZONE)
except OpenSSLObjectError as exc:
raise BackendException(to_native(exc))
def interpolate_timestamp(self, timestamp_start, timestamp_end, percentage):
start = get_epoch_seconds(timestamp_start)
end = get_epoch_seconds(timestamp_end)
return from_epoch_seconds(start + percentage * (end - start), with_timezone=CRYPTOGRAPHY_TIMEZONE)
def get_utc_datetime(self, *args, **kwargs):
kwargs_ext = dict(kwargs)
if CRYPTOGRAPHY_TIMEZONE and ('tzinfo' not in kwargs_ext and len(args) < 8):
kwargs_ext['tzinfo'] = UTC
result = datetime.datetime(*args, **kwargs_ext)
if CRYPTOGRAPHY_TIMEZONE and ('tzinfo' in kwargs or len(args) >= 8):
result = ensure_utc_timezone(result)
return result
def parse_key(self, key_file=None, key_content=None, passphrase=None):
'''
Parses an RSA or Elliptic Curve key file in PEM format and returns key_data.
Raises KeyParsingError in case of errors.
'''
# If key_content isn't given, read key_file
# If key_content is not given, read key_file
if key_content is None:
key_content = read_file(key_file)
else:
@@ -213,8 +238,8 @@ class CryptographyBackend(CryptoBackend):
'alg': 'RS256',
'jwk': {
"kty": "RSA",
"e": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.e), pk.e)),
"n": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.n), pk.n)),
"e": nopad_b64(convert_int_to_bytes(pk.e)),
"n": nopad_b64(convert_int_to_bytes(pk.n)),
},
'hash': 'sha256',
}
@@ -250,8 +275,8 @@ class CryptographyBackend(CryptoBackend):
'jwk': {
"kty": "EC",
"crv": curve,
"x": nopad_b64(_convert_int_to_bytes(num_bytes, pk.x)),
"y": nopad_b64(_convert_int_to_bytes(num_bytes, pk.y)),
"x": nopad_b64(convert_int_to_bytes(pk.x, count=num_bytes)),
"y": nopad_b64(convert_int_to_bytes(pk.y, count=num_bytes)),
},
'hash': hashalg,
'point_size': point_size,
@@ -278,8 +303,8 @@ class CryptographyBackend(CryptoBackend):
hashalg = cryptography.hazmat.primitives.hashes.SHA512
ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hashalg())
r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key_data['key_obj'].sign(sign_payload, ecdsa))
rr = _pad_hex(r, 2 * key_data['point_size'])
ss = _pad_hex(s, 2 * key_data['point_size'])
rr = convert_int_to_hex(r, 2 * key_data['point_size'])
ss = convert_int_to_hex(s, 2 * key_data['point_size'])
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
return {
@@ -318,31 +343,51 @@ class CryptographyBackend(CryptoBackend):
},
}
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
'''
Return a list of requested identifiers (CN and SANs) for the CSR.
Each identifier is a pair (type, identifier), where type is either
'dns' or 'ip'.
The list is deduplicated, and if a CNAME is present, it will be returned
as the first element in the result.
'''
if csr_content is None:
csr_content = read_file(csr_filename)
else:
csr_content = to_bytes(csr_content)
csr = cryptography.x509.load_pem_x509_csr(csr_content, _cryptography_backend)
identifiers = set()
result = []
def add_identifier(identifier):
if identifier in identifiers:
return
identifiers.add(identifier)
result.append(identifier)
for sub in csr.subject:
if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME:
add_identifier(('dns', sub.value))
for extension in csr.extensions:
if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
for name in extension.value:
if isinstance(name, cryptography.x509.DNSName):
add_identifier(('dns', name.value))
elif isinstance(name, cryptography.x509.IPAddress):
add_identifier(('ip', name.value.compressed))
else:
raise BackendException('Found unsupported SAN identifier {0}'.format(name))
return result
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
'''
Return a set of requested identifiers (CN and SANs) for the CSR.
Each identifier is a pair (type, identifier), where type is either
'dns' or 'ip'.
'''
identifiers = set([])
if csr_content is None:
csr_content = read_file(csr_filename)
else:
csr_content = to_bytes(csr_content)
csr = cryptography.x509.load_pem_x509_csr(csr_content, _cryptography_backend)
for sub in csr.subject:
if sub.oid == cryptography.x509.oid.NameOID.COMMON_NAME:
identifiers.add(('dns', sub.value))
for extension in csr.extensions:
if extension.oid == cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
for name in extension.value:
if isinstance(name, cryptography.x509.DNSName):
identifiers.add(('dns', name.value))
elif isinstance(name, cryptography.x509.IPAddress):
identifiers.add(('ip', name.value.compressed))
else:
raise BackendException('Found unsupported SAN identifier {0}'.format(name))
return identifiers
return set(self.get_ordered_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content))
def get_cert_days(self, cert_filename=None, cert_content=None, now=None):
'''
@@ -373,11 +418,54 @@ class CryptographyBackend(CryptoBackend):
raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e))
if now is None:
now = datetime.datetime.now()
return (cert.not_valid_after - now).days
now = self.get_now()
elif CRYPTOGRAPHY_TIMEZONE:
now = ensure_utc_timezone(now)
return (get_not_valid_after(cert) - now).days
def create_chain_matcher(self, criterium):
'''
Given a Criterium object, creates a ChainMatcher object.
'''
return CryptographyChainMatcher(criterium, self.module)
def get_cert_information(self, cert_filename=None, cert_content=None):
'''
Return some information on a X.509 certificate as a CertificateInformation object.
'''
if cert_filename is not None:
cert_content = read_file(cert_filename)
else:
cert_content = to_bytes(cert_content)
# 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:
if cert_filename is None:
raise BackendException('Cannot parse certificate: {0}'.format(e))
raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e))
ski = None
try:
ext = cert.extensions.get_extension_for_class(cryptography.x509.SubjectKeyIdentifier)
ski = ext.value.digest
except cryptography.x509.ExtensionNotFound:
pass
aki = None
try:
ext = cert.extensions.get_extension_for_class(cryptography.x509.AuthorityKeyIdentifier)
aki = ext.value.key_identifier
except cryptography.x509.ExtensionNotFound:
pass
return CertificateInformation(
not_valid_after=get_not_valid_after(cert),
not_valid_before=get_not_valid_before(cert),
serial_number=cryptography_serial_number_of_cert(cert),
subject_key_identifier=ski,
authority_key_identifier=aki,
)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -19,6 +20,7 @@ import traceback
from ansible.module_utils.common.text.converters import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
CertificateInformation,
CryptoBackend,
)
@@ -29,12 +31,44 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import convert_bytes_to_int
try:
import ipaddress
except ImportError:
pass
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG='C', LC_ALL='C', LC_MESSAGES='C', LC_CTYPE='C')
def _extract_date(out_text, name, cert_filename_suffix=""):
try:
date_str = re.search(r"\s+%s\s*:\s+(.*)" % name, out_text).group(1)
return datetime.datetime.strptime(date_str, '%b %d %H:%M:%S %Y %Z')
except AttributeError:
raise BackendException("No '{0}' date found{1}".format(name, cert_filename_suffix))
except ValueError as exc:
raise BackendException("Failed to parse '{0}' date{1}: {2}".format(name, cert_filename_suffix, exc))
def _decode_octets(octets_text):
return binascii.unhexlify(re.sub(r"(\s|:)", "", octets_text).encode("utf-8"))
def _extract_octets(out_text, name, required=True, potential_prefixes=None):
regexp = r"\s+%s:\s*\n\s+%s([A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2})*)\s*\n" % (
name,
('(?:%s)' % '|'.join(re.escape(pp) for pp in potential_prefixes)) if potential_prefixes else '',
)
match = re.search(regexp, out_text, re.MULTILINE | re.DOTALL)
if match is not None:
return _decode_octets(match.group(1))
if not required:
return None
raise BackendException("No '{0}' octet string found".format(name))
class OpenSSLCLIBackend(CryptoBackend):
def __init__(self, module, openssl_binary=None):
super(OpenSSLCLIBackend, self).__init__(module)
@@ -49,7 +83,7 @@ class OpenSSLCLIBackend(CryptoBackend):
'''
if passphrase is not None:
raise KeyParsingError('openssl backend does not support key passphrases')
# If key_file isn't given, but key_content, write that to a temporary file
# If key_file is not given, but key_content, write that to a temporary file
if key_file is None:
fd, tmpsrc = tempfile.mkstemp()
self.module.add_cleanup_file(tmpsrc) # Ansible will delete the file on exit
@@ -85,10 +119,12 @@ class OpenSSLCLIBackend(CryptoBackend):
dummy, out, dummy = self.module.run_command(
openssl_keydump_cmd, check_rc=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
out_text = to_text(out, errors='surrogate_or_strict')
if account_key_type == 'rsa':
pub_hex, pub_exp = re.search(
r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)",
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL).groups()
pub_hex = re.search(r"modulus:\n\s+00:([a-f0-9\:\s]+?)\npublicExponent", out_text, re.MULTILINE | re.DOTALL).group(1)
pub_exp = re.search(r"\npublicExponent: ([0-9]+)", out_text, re.MULTILINE | re.DOTALL).group(1)
pub_exp = "{0:x}".format(int(pub_exp))
if len(pub_exp) % 2:
pub_exp = "0{0}".format(pub_exp)
@@ -100,17 +136,19 @@ class OpenSSLCLIBackend(CryptoBackend):
'jwk': {
"kty": "RSA",
"e": nopad_b64(binascii.unhexlify(pub_exp.encode("utf-8"))),
"n": nopad_b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))),
"n": nopad_b64(_decode_octets(pub_hex)),
},
'hash': 'sha256',
}
elif account_key_type == 'ec':
pub_data = re.search(
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
out_text,
re.MULTILINE | re.DOTALL,
)
if pub_data is None:
raise KeyParsingError('cannot parse elliptic curve key')
pub_hex = binascii.unhexlify(re.sub(r"(\s|:)", "", pub_data.group(1)).encode("utf-8"))
pub_hex = _decode_octets(pub_data.group(1))
asn1_oid_curve = pub_data.group(2).lower()
nist_curve = pub_data.group(3).lower() if pub_data.group(3) else None
if asn1_oid_curve == 'prime256v1' or nist_curve == 'p-256':
@@ -216,16 +254,19 @@ class OpenSSLCLIBackend(CryptoBackend):
@staticmethod
def _normalize_ip(ip):
try:
return to_native(compat_ipaddress.ip_address(to_text(ip)).compressed)
return to_native(ipaddress.ip_address(to_text(ip)).compressed)
except ValueError:
# We don't want to error out on something IPAddress() can't parse
# We do not want to error out on something IPAddress() cannot parse
return ip
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
'''
Return a set of requested identifiers (CN and SANs) for the CSR.
Return a list of requested identifiers (CN and SANs) for the CSR.
Each identifier is a pair (type, identifier), where type is either
'dns' or 'ip'.
The list is deduplicated, and if a CNAME is present, it will be returned
as the first element in the result.
'''
filename = csr_filename
data = None
@@ -237,24 +278,40 @@ class OpenSSLCLIBackend(CryptoBackend):
dummy, out, dummy = self.module.run_command(
openssl_csr_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
identifiers = set([])
identifiers = set()
result = []
def add_identifier(identifier):
if identifier in identifiers:
return
identifiers.add(identifier)
result.append(identifier)
common_name = re.search(r"Subject:.* CN\s?=\s?([^\s,;/]+)", to_text(out, errors='surrogate_or_strict'))
if common_name is not None:
identifiers.add(('dns', common_name.group(1)))
add_identifier(('dns', common_name.group(1)))
subject_alt_names = re.search(
r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n",
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
if subject_alt_names is not None:
for san in subject_alt_names.group(1).split(", "):
if san.lower().startswith("dns:"):
identifiers.add(('dns', san[4:]))
add_identifier(('dns', san[4:]))
elif san.lower().startswith("ip:"):
identifiers.add(('ip', self._normalize_ip(san[3:])))
add_identifier(('ip', self._normalize_ip(san[3:])))
elif san.lower().startswith("ip address:"):
identifiers.add(('ip', self._normalize_ip(san[11:])))
add_identifier(('ip', self._normalize_ip(san[11:])))
else:
raise BackendException('Found unsupported SAN identifier "{0}"'.format(san))
return identifiers
return result
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
'''
Return a set of requested identifiers (CN and SANs) for the CSR.
Each identifier is a pair (type, identifier), where type is either
'dns' or 'ip'.
'''
return set(self.get_ordered_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content))
def get_cert_days(self, cert_filename=None, cert_content=None, now=None):
'''
@@ -280,13 +337,8 @@ class OpenSSLCLIBackend(CryptoBackend):
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
dummy, out, dummy = self.module.run_command(
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
try:
not_after_str = re.search(r"\s+Not After\s*:\s+(.*)", to_text(out, errors='surrogate_or_strict')).group(1)
not_after = datetime.datetime.strptime(not_after_str, '%b %d %H:%M:%S %Y %Z')
except AttributeError:
raise BackendException("No 'Not after' date found{0}".format(cert_filename_suffix))
except ValueError:
raise BackendException("Failed to parse 'Not after' date{0}".format(cert_filename_suffix))
out_text = to_text(out, errors='surrogate_or_strict')
not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix)
if now is None:
now = datetime.datetime.now()
return (not_after - now).days
@@ -296,3 +348,43 @@ class OpenSSLCLIBackend(CryptoBackend):
Given a Criterium object, creates a ChainMatcher object.
'''
raise BackendException('Alternate chain matching can only be used with the "cryptography" backend.')
def get_cert_information(self, cert_filename=None, cert_content=None):
'''
Return some information on a X.509 certificate as a CertificateInformation object.
'''
filename = cert_filename
data = None
if cert_filename is not None:
cert_filename_suffix = ' in {0}'.format(cert_filename)
else:
filename = '/dev/stdin'
data = to_bytes(cert_content)
cert_filename_suffix = ''
openssl_cert_cmd = [self.openssl_binary, "x509", "-in", filename, "-noout", "-text"]
dummy, out, dummy = self.module.run_command(
openssl_cert_cmd, data=data, check_rc=True, binary_data=True, environ_update=_OPENSSL_ENVIRONMENT_UPDATE)
out_text = to_text(out, errors='surrogate_or_strict')
not_after = _extract_date(out_text, 'Not After', cert_filename_suffix=cert_filename_suffix)
not_before = _extract_date(out_text, 'Not Before', cert_filename_suffix=cert_filename_suffix)
sn = re.search(
r" Serial Number: ([0-9]+)",
to_text(out, errors='surrogate_or_strict'), re.MULTILINE | re.DOTALL)
if sn:
serial = int(sn.group(1))
else:
serial = convert_bytes_to_int(_extract_octets(out_text, 'Serial Number', required=True))
ski = _extract_octets(out_text, 'X509v3 Subject Key Identifier', required=False)
aki = _extract_octets(out_text, 'X509v3 Authority Key Identifier', required=False, potential_prefixes=['keyid:', ''])
return CertificateInformation(
not_valid_after=not_after,
not_valid_before=not_before,
serial_number=serial,
subject_key_identifier=ski,
authority_key_identifier=aki,
)

View File

@@ -1,16 +1,86 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from collections import namedtuple
import abc
import datetime
import re
from ansible.module_utils import six
from ansible.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
BackendException,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.time import (
ensure_utc_timezone,
from_epoch_seconds,
get_epoch_seconds,
get_now_datetime,
get_relative_time_option,
remove_timezone,
)
CertificateInformation = namedtuple(
'CertificateInformation',
(
'not_valid_after',
'not_valid_before',
'serial_number',
'subject_key_identifier',
'authority_key_identifier',
),
)
_FRACTIONAL_MATCHER = re.compile(r'^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(|\.\d+)(Z|[+-]\d{2}:?\d{2}.*)$')
def _reduce_fractional_digits(timestamp_str):
"""
Given a RFC 3339 timestamp that includes too many digits for the fractional seconds part, reduces these to at most 6.
"""
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
m = _FRACTIONAL_MATCHER.match(timestamp_str)
if not m:
raise BackendException('Cannot parse ISO 8601 timestamp {0!r}'.format(timestamp_str))
timestamp, fractional, timezone = m.groups()
if len(fractional) > 7:
# Python does not support anything smaller than microseconds
# (Golang supports nanoseconds, Boulder often emits more fractional digits, which Python chokes on)
fractional = fractional[:7]
return '%s%s%s' % (timestamp, fractional, timezone)
def _parse_acme_timestamp(timestamp_str, with_timezone):
"""
Parses a RFC 3339 timestamp.
"""
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
timestamp_str = _reduce_fractional_digits(timestamp_str)
for format in ('%Y-%m-%dT%H:%M:%SZ', '%Y-%m-%dT%H:%M:%S.%fZ', '%Y-%m-%dT%H:%M:%S%z', '%Y-%m-%dT%H:%M:%S.%f%z'):
# Note that %z won't work with Python 2... https://stackoverflow.com/a/27829491
try:
result = datetime.datetime.strptime(timestamp_str, format)
except ValueError:
pass
else:
return ensure_utc_timezone(result) if with_timezone else remove_timezone(result)
raise BackendException('Cannot parse ISO 8601 timestamp {0!r}'.format(timestamp_str))
@six.add_metaclass(abc.ABCMeta)
@@ -18,6 +88,30 @@ class CryptoBackend(object):
def __init__(self, module):
self.module = module
def get_now(self):
return get_now_datetime(with_timezone=False)
def parse_acme_timestamp(self, timestamp_str):
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
return _parse_acme_timestamp(timestamp_str, with_timezone=False)
def parse_module_parameter(self, value, name):
try:
return get_relative_time_option(value, name, backend='cryptography', with_timezone=False)
except OpenSSLObjectError as exc:
raise BackendException(to_native(exc))
def interpolate_timestamp(self, timestamp_start, timestamp_end, percentage):
start = get_epoch_seconds(timestamp_start)
end = get_epoch_seconds(timestamp_end)
return from_epoch_seconds(start + percentage * (end - start), with_timezone=False)
def get_utc_datetime(self, *args, **kwargs):
result = datetime.datetime(*args, **kwargs)
if 'tzinfo' in kwargs or len(args) >= 8:
result = remove_timezone(result)
return result
@abc.abstractmethod
def parse_key(self, key_file=None, key_content=None, passphrase=None):
'''
@@ -33,6 +127,23 @@ class CryptoBackend(object):
def create_mac_key(self, alg, key):
'''Create a MAC key.'''
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
'''
Return a list of requested identifiers (CN and SANs) for the CSR.
Each identifier is a pair (type, identifier), where type is either
'dns' or 'ip'.
The list is deduplicated, and if a CNAME is present, it will be returned
as the first element in the result.
'''
self.module.deprecate(
"Every backend must override the get_ordered_csr_identifiers() method."
" The default implementation will be removed in 3.0.0 and this method will be marked as `abstractmethod` by then.",
version='3.0.0',
collection_name='community.crypto',
)
return sorted(self.get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content))
@abc.abstractmethod
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
'''
@@ -56,3 +167,12 @@ class CryptoBackend(object):
'''
Given a Criterium object, creates a ChainMatcher object.
'''
def get_cert_information(self, cert_filename=None, cert_content=None):
'''
Return some information on a X.509 certificate as a CertificateInformation object.
'''
# Not implementing this method in a backend is DEPRECATED and will be
# disallowed in community.crypto 3.0.0. This method will be marked as
# @abstractmethod by then.
raise BackendException('This backend does not support get_cert_information()')

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -16,8 +17,6 @@ import time
from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.compat import ipaddress as compat_ipaddress
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
nopad_b64,
)
@@ -28,6 +27,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
ModuleFailException,
)
try:
import ipaddress
except ImportError:
pass
def create_key_authorization(client, token):
'''
@@ -99,7 +103,7 @@ class Challenge(object):
# https://tools.ietf.org/html/rfc8555#section-8.4
resource = '_acme-challenge'
value = nopad_b64(hashlib.sha256(to_bytes(key_authorization)).digest())
record = (resource + identifier[1:]) if identifier.startswith('*.') else '{0}.{1}'.format(resource, identifier)
record = '{0}.{1}'.format(resource, identifier[2:] if identifier.startswith('*.') else identifier)
return {
'resource': resource,
'resource_value': value,
@@ -110,7 +114,7 @@ class Challenge(object):
# https://www.rfc-editor.org/rfc/rfc8737.html#section-3
if identifier_type == 'ip':
# IPv4/IPv6 address: use reverse mapping (RFC1034, RFC3596)
resource = compat_ipaddress.ip_address(identifier).reverse_pointer
resource = ipaddress.ip_address(identifier).reverse_pointer
if not resource.endswith('.'):
resource += '.'
else:
@@ -279,13 +283,21 @@ class Authorization(object):
return self.status == 'valid'
return self.wait_for_validation(client, challenge_type)
def can_deactivate(self):
'''
Deactivates this authorization.
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
https://tools.ietf.org/html/rfc8555#section-7.5.2
'''
return self.status in ('valid', 'pending')
def deactivate(self, client):
'''
Deactivates this authorization.
https://community.letsencrypt.org/t/authorization-deactivation/19860/2
https://tools.ietf.org/html/rfc8555#section-7.5.2
'''
if self.status != 'valid':
if not self.can_deactivate():
return
authz_deactivate = {
'status': 'deactivated'
@@ -297,3 +309,21 @@ class Authorization(object):
self.status = 'deactivated'
return True
return False
def wait_for_validation(authzs, client):
'''
Wait until a list of authz is valid. Fail if at least one of them is invalid or revoked.
'''
while authzs:
authzs_next = []
for authz in authzs:
authz.refresh(client)
if authz.status in ['valid', 'invalid', 'revoked']:
if authz.status != 'valid':
authz.raise_error('Status is not "valid"', module=client.module)
else:
authzs_next.append(authz)
if authzs_next:
time.sleep(2)
authzs = authzs_next

View File

@@ -1,24 +1,34 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.common.text.converters import to_text
from ansible.module_utils.six import binary_type, PY3
from ansible.module_utils.six.moves.http_client import responses as http_responses
def format_http_status(status_code):
expl = http_responses.get(status_code)
if not expl:
return str(status_code)
return '%d %s' % (status_code, expl)
def format_error_problem(problem, subproblem_prefix=''):
error_type = problem.get('type', 'about:blank') # https://www.rfc-editor.org/rfc/rfc7807#section-3.1
if 'title' in problem:
msg = 'Error "{title}" ({type})'.format(
type=problem['type'],
type=error_type,
title=problem['title'],
)
else:
msg = 'Error {type}'.format(type=problem['type'])
msg = 'Error {type}'.format(type=error_type)
if 'detail' in problem:
msg += ': "{detail}"'.format(detail=problem['detail'])
subproblems = problem.get('subproblems')
@@ -86,9 +96,12 @@ class ACMEProtocolException(ModuleFailException):
extras['http_status'] = code
if code is not None and code >= 400 and content_json is not None and 'type' in content_json:
if 'status' in content_json and content_json['status'] != code:
code = 'status {problem_code} (HTTP status: {http_code})'.format(http_code=code, problem_code=content_json['status'])
code_msg = 'status {problem_code} (HTTP status: {http_code})'.format(
http_code=format_http_status(code), problem_code=content_json['status'])
else:
code = 'status {problem_code}'.format(problem_code=code)
code_msg = 'status {problem_code}'.format(problem_code=format_http_status(code))
if code == -1 and info.get('msg'):
code_msg = 'error: {msg}'.format(msg=info['msg'])
subproblems = content_json.pop('subproblems', None)
add_msg = ' {problem}.'.format(problem=format_error_problem(content_json))
extras['problem'] = content_json
@@ -102,12 +115,14 @@ class ACMEProtocolException(ModuleFailException):
problem=format_error_problem(problem, subproblem_prefix='{0}.'.format(index)),
)
else:
code = 'HTTP status {code}'.format(code=code)
code_msg = 'HTTP status {code}'.format(code=format_http_status(code))
if code == -1 and info.get('msg'):
code_msg = 'error: {msg}'.format(msg=info['msg'])
if content_json is not None:
add_msg = ' The JSON error result: {content}'.format(content=content_json)
elif content is not None:
add_msg = ' The raw error result: {content}'.format(content=to_text(content))
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=code)
msg = '{msg} for {url} with {code}'.format(msg=msg, url=url, code=code_msg)
elif content_json is not None:
add_msg = ' The JSON result: {content}'.format(content=content_json)
elif content is not None:

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2013, Romeo Theriault <romeot () hawaii.edu>
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2013, Romeo Theriault <romeot () hawaii.edu>
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -31,6 +32,7 @@ class Order(object):
self.identifiers = []
for identifier in data['identifiers']:
self.identifiers.append((identifier['type'], identifier['value']))
self.replaces_cert_id = data.get('replaces')
self.finalize_uri = data.get('finalize')
self.certificate_uri = data.get('certificate')
self.authorization_uris = data['authorizations']
@@ -43,6 +45,7 @@ class Order(object):
self.status = None
self.identifiers = []
self.replaces_cert_id = None
self.finalize_uri = None
self.certificate_uri = None
self.authorization_uris = []
@@ -61,7 +64,7 @@ class Order(object):
return result
@classmethod
def create(cls, client, identifiers):
def create(cls, client, identifiers, replaces_cert_id=None):
'''
Start a new certificate order (ACME v2 protocol).
https://tools.ietf.org/html/rfc8555#section-7.4
@@ -75,6 +78,8 @@ class Order(object):
new_order = {
"identifiers": acme_identifiers
}
if replaces_cert_id is not None:
new_order["replaces"] = replaces_cert_id
result, info = client.send_signed_request(
client.directory['newOrder'], new_order, error_msg='Failed to start new order', expected_status_codes=[201])
return cls.from_json(client, result, info['location'])

View File

@@ -1,14 +1,16 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# 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)
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import base64
import datetime
import re
import textwrap
import traceback
@@ -18,6 +20,10 @@ from ansible.module_utils.six.moves.urllib.parse import unquote
from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
from ansible_collections.community.crypto.plugins.module_utils.crypto.math import convert_int_to_bytes
from ansible_collections.community.crypto.plugins.module_utils.time import get_now_datetime
def nopad_b64(data):
return base64.urlsafe_b64encode(data).decode('utf8').replace("=", "")
@@ -64,8 +70,61 @@ def pem_to_der(pem_filename=None, pem_content=None):
def process_links(info, callback):
'''
Process link header, calls callback for every link header with the URL and relation as options.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link
'''
if 'link' in info:
link = info['link']
for url, relation in re.findall(r'<([^>]+)>;\s*rel="(\w+)"', link):
callback(unquote(url), relation)
def parse_retry_after(value, relative_with_timezone=True, now=None):
'''
Parse the value of a Retry-After header and return a timestamp.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
'''
# First try a number of seconds
try:
delta = datetime.timedelta(seconds=int(value))
if now is None:
now = get_now_datetime(relative_with_timezone)
return now + delta
except ValueError:
pass
try:
return datetime.datetime.strptime(value, '%a, %d %b %Y %H:%M:%S GMT')
except ValueError:
pass
raise ValueError('Cannot parse Retry-After header value %s' % repr(value))
def compute_cert_id(
backend,
cert_info=None,
cert_filename=None,
cert_content=None,
none_if_required_information_is_missing=False,
):
# Obtain certificate info if not provided
if cert_info is None:
cert_info = backend.get_cert_information(cert_filename=cert_filename, cert_content=cert_content)
# Convert Authority Key Identifier to string
if cert_info.authority_key_identifier is None:
if none_if_required_information_is_missing:
return None
raise ModuleFailException('Certificate has no Authority Key Identifier extension')
aki = to_native(base64.urlsafe_b64encode(cert_info.authority_key_identifier)).replace('=', '')
# Convert serial number to string
serial_bytes = convert_int_to_bytes(cert_info.serial_number)
if ord(serial_bytes[:1]) >= 128:
serial_bytes = b'\x00' + serial_bytes
serial = to_native(base64.urlsafe_b64encode(serial_bytes)).replace('=', '')
# Compose cert ID
return '{aki}.{serial}'.format(aki=aki, serial=serial)

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
def _ensure_list(value):
if value is None:
return []
return list(value)
class ArgumentSpec:
def __init__(self, argument_spec=None, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
self.argument_spec = argument_spec or {}
self.mutually_exclusive = _ensure_list(mutually_exclusive)
self.required_together = _ensure_list(required_together)
self.required_one_of = _ensure_list(required_one_of)
self.required_if = _ensure_list(required_if)
self.required_by = required_by or {}
def update_argspec(self, **kwargs):
self.argument_spec.update(kwargs)
return self
def update(self, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
if mutually_exclusive:
self.mutually_exclusive.extend(mutually_exclusive)
if required_together:
self.required_together.extend(required_together)
if required_one_of:
self.required_one_of.extend(required_one_of)
if required_if:
self.required_if.extend(required_if)
if required_by:
for k, v in required_by.items():
if k in self.required_by:
v = list(self.required_by[k]) + list(v)
self.required_by[k] = v
return self
def merge(self, other):
self.update_argspec(**other.argument_spec)
self.update(
mutually_exclusive=other.mutually_exclusive,
required_together=other.required_together,
required_one_of=other.required_one_of,
required_if=other.required_if,
required_by=other.required_by,
)
return self
def create_ansible_module_helper(self, clazz, args, **kwargs):
return clazz(
*args,
argument_spec=self.argument_spec,
mutually_exclusive=self.mutually_exclusive,
required_together=self.required_together,
required_one_of=self.required_one_of,
required_if=self.required_if,
required_by=self.required_by,
**kwargs)
def create_ansible_module(self, **kwargs):
return self.create_ansible_module_helper(AnsibleModule, (), **kwargs)
__all__ = ('ArgumentSpec', )

File diff suppressed because it is too large Load Diff

View File

@@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
#
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# THIS FILE IS FOR COMPATIBILITY ONLY! YOU SHALL NOT IMPORT IT!
#
# This fill will be removed eventually, so if you're using it,
# please stop doing so.
from .basic import (
HAS_PYOPENSSL,
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL,
CRYPTOGRAPHY_HAS_X448,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
HAS_CRYPTOGRAPHY,
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from .cryptography_crl import (
REVOCATION_REASON_MAP,
REVOCATION_REASON_MAP_INVERSE,
cryptography_decode_revoked_certificate,
)
from .cryptography_support import (
cryptography_get_extensions_from_cert,
cryptography_get_extensions_from_csr,
cryptography_name_to_oid,
cryptography_oid_to_name,
cryptography_get_name,
cryptography_decode_name,
cryptography_parse_key_usage_params,
cryptography_get_basic_constraints,
cryptography_key_needs_digest_for_signing,
cryptography_compare_public_keys,
)
from .pem import (
identify_private_key_format,
)
from .math import (
binary_exp_mod,
simple_gcd,
quick_is_not_prime,
count_bits,
)
from ._obj2txt import obj2txt as _obj2txt
from ._objects_data import OID_MAP as _OID_MAP
from ._objects import OID_LOOKUP as _OID_LOOKUP
from ._objects import NORMALIZE_NAMES as _NORMALIZE_NAMES
from ._objects import NORMALIZE_NAMES_SHORT as _NORMALIZE_NAMES_SHORT
from .pyopenssl_support import (
pyopenssl_normalize_name,
pyopenssl_get_extensions_from_cert,
pyopenssl_get_extensions_from_csr,
)
from .support import (
get_fingerprint_of_bytes,
get_fingerprint,
load_privatekey,
load_certificate,
load_certificate_request,
parse_name_field,
convert_relative_to_datetime,
get_relative_time_option,
select_message_digest,
OpenSSLObject,
)
from ..io import (
load_file_if_exists,
write_file,
)

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
# (c) 2020, Jordan Borean <jborean93@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2020, Jordan Borean <jborean93@gmail.com>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type

View File

@@ -1,8 +1,16 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is licensed under the
# Apache 2.0 License. Modules you write using this snippet, which is embedded
# dynamically by Ansible, still belong to the author of the module, and may assign
# their own license to the complete work.
# This excerpt is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file at
# https://github.com/pyca/cryptography/blob/master/LICENSE for complete details.
#
# The Apache 2.0 license has been included as Apache-2.0.txt in this collection.
# The Apache 2.0 license has been included as LICENSES/Apache-2.0.txt in this collection.
# The BSD License license has been included as LICENSES/BSD-3-Clause.txt in this collection.
# SPDX-License-Identifier: Apache-2.0 OR BSD-3-Clause
#
# Adapted from cryptography's hazmat/backends/openssl/decode_asn1.py
#

View File

@@ -1,19 +1,8 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type

View File

@@ -1,3 +1,9 @@
# This code is part of Ansible, but is an independent component.
# This particular file snippet, and this file snippet only, is licensed under the
# Apache 2.0 License. Modules you write using this snippet, which is embedded
# dynamically by Ansible, still belong to the author of the module, and may assign
# their own license to the complete work.
# This has been extracted from the OpenSSL project's objects.txt:
# https://github.com/openssl/openssl/blob/9537fe5757bb07761fa275d779bbd40bcf5530e4/crypto/objects/objects.txt
# Extracted with https://gist.github.com/felixfontein/376748017ad65ead093d56a45a5bf376
@@ -5,7 +11,8 @@
# In case the following data structure has any copyrightable content, note that it is licensed as follows:
# Copyright (c) the OpenSSL contributors
# Licensed under the Apache License 2.0
# https://github.com/openssl/openssl/blob/master/LICENSE.txt or Apache-2.0.txt
# SPDX-License-Identifier: Apache-2.0
# https://github.com/openssl/openssl/blob/master/LICENSE.txt or LICENSES/Apache-2.0.txt
from __future__ import absolute_import, division, print_function
__metaclass__ = type

View File

@@ -1,20 +1,9 @@
# -*- coding: utf-8 -*-
#
# (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# (c) 2020, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -22,14 +11,6 @@ __metaclass__ = type
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
try:
import OpenSSL # noqa
from OpenSSL import crypto # noqa
HAS_PYOPENSSL = True
except (ImportError, AttributeError):
# Error handled in the calling module.
HAS_PYOPENSSL = False
try:
import cryptography
from cryptography import x509

View File

@@ -1,19 +1,8 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -30,6 +19,7 @@ from .basic import (
)
from .cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_decode_name,
)
@@ -38,6 +28,11 @@ from ._obj2txt import (
)
# TODO: once cryptography has a _utc variant of InvalidityDate.invalidity_date, set this
# to True and adjust get_invalidity_date() accordingly.
# (https://github.com/pyca/cryptography/issues/10818)
CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE = False
TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
@@ -66,7 +61,7 @@ else:
def cryptography_decode_revoked_certificate(cert):
result = {
'serial_number': cert.serial_number,
'revocation_date': cert.revocation_date,
'revocation_date': get_revocation_date(cert),
'issuer': None,
'issuer_critical': False,
'reason': None,
@@ -88,19 +83,19 @@ def cryptography_decode_revoked_certificate(cert):
pass
try:
ext = cert.extensions.get_extension_for_class(x509.InvalidityDate)
result['invalidity_date'] = ext.value.invalidity_date
result['invalidity_date'] = get_invalidity_date(ext.value)
result['invalidity_date_critical'] = ext.critical
except x509.ExtensionNotFound:
pass
return result
def cryptography_dump_revoked(entry):
def cryptography_dump_revoked(entry, idn_rewrite='ignore'):
return {
'serial_number': entry['serial_number'],
'revocation_date': entry['revocation_date'].strftime(TIMESTAMP_FORMAT),
'issuer':
[cryptography_decode_name(issuer) for issuer in entry['issuer']]
[cryptography_decode_name(issuer, idn_rewrite=idn_rewrite) for issuer in entry['issuer']]
if entry['issuer'] is not None else None,
'issuer_critical': entry['issuer_critical'],
'reason': REVOCATION_REASON_MAP_INVERSE.get(entry['reason']) if entry['reason'] is not None else None,
@@ -116,10 +111,45 @@ def cryptography_get_signature_algorithm_oid_from_crl(crl):
try:
return crl.signature_algorithm_oid
except AttributeError:
# Older cryptography versions don't have signature_algorithm_oid yet
# Older cryptography versions do not have signature_algorithm_oid yet
dotted = obj2txt(
crl._backend._lib,
crl._backend._ffi,
crl._x509_crl.sig_alg.algorithm
)
return x509.oid.ObjectIdentifier(dotted)
def get_next_update(obj):
if CRYPTOGRAPHY_TIMEZONE:
return obj.next_update_utc
return obj.next_update
def get_last_update(obj):
if CRYPTOGRAPHY_TIMEZONE:
return obj.last_update_utc
return obj.last_update
def get_revocation_date(obj):
if CRYPTOGRAPHY_TIMEZONE:
return obj.revocation_date_utc
return obj.revocation_date
def get_invalidity_date(obj):
# TODO: special handling if CRYPTOGRAPHY_TIMEZONE_INVALIDITY_DATE is True
return obj.invalidity_date
def set_next_update(builder, value):
return builder.next_update(value)
def set_last_update(builder, value):
return builder.last_update(value)
def set_revocation_date(builder, value):
return builder.revocation_date(value)

View File

@@ -1,19 +1,8 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -22,8 +11,12 @@ __metaclass__ = type
import base64
import binascii
import re
import sys
import traceback
from ansible.module_utils.common.text.converters import to_text, to_bytes, to_native
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlunparse, ParseResult
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
@@ -36,7 +29,9 @@ try:
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import padding
import ipaddress
_HAS_CRYPTOGRAPHY = True
except ImportError:
_HAS_CRYPTOGRAPHY = False
# Error handled in the calling module.
pass
@@ -79,6 +74,16 @@ except ImportError:
# Error handled in the calling module.
_load_pkcs12 = None
try:
import idna
HAS_IDNA = True
except ImportError:
HAS_IDNA = False
IDNA_IMP_ERROR = traceback.format_exc()
from ansible.module_utils.basic import missing_required_lib
from .basic import (
CRYPTOGRAPHY_HAS_DSA_SIGN,
CRYPTOGRAPHY_HAS_EC_SIGN,
@@ -87,6 +92,9 @@ from .basic import (
CRYPTOGRAPHY_HAS_ED448,
CRYPTOGRAPHY_HAS_ED448_SIGN,
CRYPTOGRAPHY_HAS_RSA_SIGN,
CRYPTOGRAPHY_HAS_X25519,
CRYPTOGRAPHY_HAS_X25519_FULL,
CRYPTOGRAPHY_HAS_X448,
OpenSSLObjectError,
)
@@ -100,15 +108,20 @@ from ._objects import (
from ._obj2txt import obj2txt
CRYPTOGRAPHY_TIMEZONE = False
if _HAS_CRYPTOGRAPHY:
CRYPTOGRAPHY_TIMEZONE = LooseVersion(cryptography.__version__) >= LooseVersion('42.0.0')
DOTTED_OID = re.compile(r'^\d+(?:\.\d+)+$')
def cryptography_get_extensions_from_cert(cert):
result = dict()
try:
# Since cryptography won't give us the DER value for an extension
# Since cryptography will not give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
# the extension parsing ourselves.
backend = default_backend()
try:
# For certain old versions of cryptography, backend is a MultiBackend object,
@@ -132,7 +145,7 @@ def cryptography_get_extensions_from_cert(cert):
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der),
value=to_native(base64.b64encode(der)),
)
try:
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
@@ -149,7 +162,7 @@ def cryptography_get_extensions_from_cert(cert):
for ext in cert.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()),
value=to_native(base64.b64encode(ext.value.public_bytes())),
)
return result
@@ -158,9 +171,9 @@ def cryptography_get_extensions_from_cert(cert):
def cryptography_get_extensions_from_csr(csr):
result = dict()
try:
# Since cryptography won't give us the DER value for an extension
# Since cryptography will not give us the DER value for an extension
# (that is only stored for unrecognized extensions), we have to re-do
# the extension parsing outselves.
# the extension parsing ourselves.
backend = default_backend()
try:
# For certain old versions of cryptography, backend is a MultiBackend object,
@@ -192,7 +205,7 @@ def cryptography_get_extensions_from_csr(csr):
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der),
value=to_native(base64.b64encode(der)),
)
try:
oid = obj2txt(backend._lib, backend._ffi, backend._lib.X509_EXTENSION_get_object(ext))
@@ -209,7 +222,7 @@ def cryptography_get_extensions_from_csr(csr):
for ext in csr.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()),
value=to_native(base64.b64encode(ext.value.public_bytes())),
)
return result
@@ -255,34 +268,68 @@ def _parse_hex(bytesstr):
return data
DN_COMPONENT_START_RE = re.compile(r'^ *([a-zA-z0-9]+) *= *')
DN_COMPONENT_START_RE = re.compile(b'^ *([a-zA-z0-9.]+) *= *')
DN_HEX_LETTER = b'0123456789abcdef'
def _parse_dn_component(name, sep=',', sep_str='\\', decode_remainder=True):
if sys.version_info[0] < 3:
_int_to_byte = chr
else:
def _int_to_byte(value):
return bytes((value, ))
def _parse_dn_component(name, sep=b',', decode_remainder=True):
m = DN_COMPONENT_START_RE.match(name)
if not m:
raise OpenSSLObjectError('cannot start part in "{0}"'.format(name))
oid = cryptography_name_to_oid(m.group(1))
raise OpenSSLObjectError(u'cannot start part in "{0}"'.format(to_text(name)))
oid = cryptography_name_to_oid(to_text(m.group(1)))
idx = len(m.group(0))
decoded_name = []
sep_str = sep + b'\\'
if decode_remainder:
length = len(name)
while idx < length:
i = idx
while i < length and name[i] not in sep_str:
i += 1
if i > idx:
decoded_name.append(name[idx:i])
idx = i
while idx + 1 < length and name[idx] == '\\':
decoded_name.append(name[idx + 1])
if length > idx and name[idx:idx + 1] == b'#':
# Decoding a hex string
idx += 1
while idx + 1 < length:
ch1 = name[idx:idx + 1]
ch2 = name[idx + 1:idx + 2]
idx1 = DN_HEX_LETTER.find(ch1.lower())
idx2 = DN_HEX_LETTER.find(ch2.lower())
if idx1 < 0 or idx2 < 0:
raise OpenSSLObjectError(u'Invalid hex sequence entry "{0}"'.format(to_text(ch1 + ch2)))
idx += 2
if idx < length and name[idx] == sep:
break
decoded_name.append(_int_to_byte(idx1 * 16 + idx2))
else:
# Decoding a regular string
while idx < length:
i = idx
while i < length and name[i:i + 1] not in sep_str:
i += 1
if i > idx:
decoded_name.append(name[idx:i])
idx = i
while idx + 1 < length and name[idx:idx + 1] == b'\\':
ch = name[idx + 1:idx + 2]
idx1 = DN_HEX_LETTER.find(ch.lower())
if idx1 >= 0:
if idx + 2 >= length:
raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" incomplete at end of string'.format(to_text(ch)))
ch2 = name[idx + 2:idx + 3]
idx2 = DN_HEX_LETTER.find(ch2.lower())
if idx2 < 0:
raise OpenSSLObjectError(u'Hex escape sequence "\\{0}" has invalid second letter'.format(to_text(ch + ch2)))
ch = _int_to_byte(idx1 * 16 + idx2)
idx += 1
idx += 2
decoded_name.append(ch)
if idx < length and name[idx:idx + 1] == sep:
break
else:
decoded_name.append(name[idx:])
idx = len(name)
return x509.NameAttribute(oid, ''.join(decoded_name)), name[idx:]
return x509.NameAttribute(oid, to_text(b''.join(decoded_name))), name[idx:]
def _parse_dn(name):
@@ -293,21 +340,20 @@ def _parse_dn(name):
'''
original_name = name
name = name.lstrip()
sep = ','
if name.startswith('/'):
sep = '/'
sep = b','
if name.startswith(b'/'):
sep = b'/'
name = name[1:]
sep_str = sep + '\\'
result = []
while name:
try:
attribute, name = _parse_dn_component(name, sep=sep, sep_str=sep_str)
attribute, name = _parse_dn_component(name, sep=sep)
except OpenSSLObjectError as e:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": {1}'.format(original_name, e))
raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": {1}'.format(to_text(original_name), e))
result.append(attribute)
if name:
if name[0] != sep or len(name) < 2:
raise OpenSSLObjectError('Error while parsing distinguished name "{0}": unexpected end of string'.format(original_name))
if name[0:1] != sep or len(name) < 2:
raise OpenSSLObjectError(u'Error while parsing distinguished name "{0}": unexpected end of string'.format(to_text(original_name)))
name = name[1:]
return result
@@ -316,12 +362,86 @@ def cryptography_parse_relative_distinguished_name(rdn):
names = []
for part in rdn:
try:
names.append(_parse_dn_component(to_text(part), decode_remainder=False)[0])
names.append(_parse_dn_component(to_bytes(part), decode_remainder=False)[0])
except OpenSSLObjectError as e:
raise OpenSSLObjectError('Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
raise OpenSSLObjectError(u'Error while parsing relative distinguished name "{0}": {1}'.format(part, e))
return cryptography.x509.RelativeDistinguishedName(names)
def _is_ascii(value):
'''Check whether the Unicode string `value` contains only ASCII characters.'''
try:
value.encode("ascii")
return True
except UnicodeEncodeError:
return False
def _adjust_idn(value, idn_rewrite):
if idn_rewrite == 'ignore' or not value:
return value
if idn_rewrite == 'idna' and _is_ascii(value):
return value
if idn_rewrite not in ('idna', 'unicode'):
raise ValueError('Invalid value for idn_rewrite: "{0}"'.format(idn_rewrite))
if not HAS_IDNA:
raise OpenSSLObjectError(
missing_required_lib('idna', reason='to transform {what} DNS name "{name}" to {dest}'.format(
name=value,
what='IDNA' if idn_rewrite == 'unicode' else 'Unicode',
dest='Unicode' if idn_rewrite == 'unicode' else 'IDNA',
)))
# Since IDNA does not like '*' or empty labels (except one empty label at the end),
# we split and let IDNA only handle labels that are neither empty or '*'.
parts = value.split(u'.')
for index, part in enumerate(parts):
if part in (u'', u'*'):
continue
try:
if idn_rewrite == 'idna':
parts[index] = idna.encode(part).decode('ascii')
elif idn_rewrite == 'unicode' and part.startswith(u'xn--'):
parts[index] = idna.decode(part)
except idna.IDNAError as exc2008:
try:
if idn_rewrite == 'idna':
parts[index] = part.encode('idna').decode('ascii')
elif idn_rewrite == 'unicode' and part.startswith(u'xn--'):
parts[index] = part.encode('ascii').decode('idna')
except Exception as exc2003:
raise OpenSSLObjectError(
u'Error while transforming part "{part}" of {what} DNS name "{name}" to {dest}.'
u' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'.format(
part=part,
name=value,
what='IDNA' if idn_rewrite == 'unicode' else 'Unicode',
dest='Unicode' if idn_rewrite == 'unicode' else 'IDNA',
exc2003=exc2003,
exc2008=exc2008,
))
return u'.'.join(parts)
def _adjust_idn_email(value, idn_rewrite):
idx = value.find(u'@')
if idx < 0:
return value
return u'{0}@{1}'.format(value[:idx], _adjust_idn(value[idx + 1:], idn_rewrite))
def _adjust_idn_url(value, idn_rewrite):
url = urlparse(value)
host = _adjust_idn(url.hostname, idn_rewrite)
if url.username is not None and url.password is not None:
host = u'{0}:{1}@{2}'.format(url.username, url.password, host)
elif url.username is not None:
host = u'{0}@{1}'.format(url.username, host)
if url.port is not None:
host = u'{0}:{1}'.format(host, url.port)
return urlunparse(
ParseResult(scheme=url.scheme, netloc=host, path=url.path, params=url.params, query=url.query, fragment=url.fragment))
def cryptography_get_name(name, what='Subject Alternative Name'):
'''
Given a name string, returns a cryptography x509.GeneralName object.
@@ -329,16 +449,16 @@ def cryptography_get_name(name, what='Subject Alternative Name'):
'''
try:
if name.startswith('DNS:'):
return x509.DNSName(to_text(name[4:]))
return x509.DNSName(_adjust_idn(to_text(name[4:]), 'idna'))
if name.startswith('IP:'):
address = to_text(name[3:])
if '/' in address:
return x509.IPAddress(ipaddress.ip_network(address))
return x509.IPAddress(ipaddress.ip_address(address))
if name.startswith('email:'):
return x509.RFC822Name(to_text(name[6:]))
return x509.RFC822Name(_adjust_idn_email(to_text(name[6:]), 'idna'))
if name.startswith('URI:'):
return x509.UniformResourceIdentifier(to_text(name[4:]))
return x509.UniformResourceIdentifier(_adjust_idn_url(to_text(name[4:]), 'idna'))
if name.startswith('RID:'):
m = re.match(r'^([0-9]+(?:\.[0-9]+)*)$', to_text(name[4:]))
if not m:
@@ -362,7 +482,7 @@ def cryptography_get_name(name, what='Subject Alternative Name'):
b_value = serialize_asn1_string_as_der(value)
return x509.OtherName(x509.ObjectIdentifier(oid), b_value)
if name.startswith('dirName:'):
return x509.DirectoryName(x509.Name(_parse_dn(to_text(name[8:]))))
return x509.DirectoryName(x509.Name(reversed(_parse_dn(to_bytes(name[8:])))))
except Exception as e:
raise OpenSSLObjectError('Cannot parse {what} "{name}": {error}'.format(name=name, what=what, error=e))
if ':' not in name:
@@ -375,32 +495,39 @@ def _dn_escape_value(value):
Escape Distinguished Name's attribute value.
'''
value = value.replace(u'\\', u'\\\\')
for ch in [u',', u'#', u'+', u'<', u'>', u';', u'"', u'=', u'/']:
for ch in [u',', u'+', u'<', u'>', u';', u'"']:
value = value.replace(ch, u'\\%s' % ch)
if value.startswith(u' '):
value = u'\\ ' + value[1:]
value = value.replace(u'\0', u'\\00')
if value.startswith((u' ', u'#')):
value = u'\\%s' % value[0] + value[1:]
if value.endswith(u' '):
value = value[:-1] + u'\\ '
return value
def cryptography_decode_name(name):
def cryptography_decode_name(name, idn_rewrite='ignore'):
'''
Given a cryptography x509.GeneralName object, returns a string.
Raises an OpenSSLObjectError if the name is not supported.
'''
if idn_rewrite not in ('ignore', 'idna', 'unicode'):
raise AssertionError('idn_rewrite must be one of "ignore", "idna", or "unicode"')
if isinstance(name, x509.DNSName):
return u'DNS:{0}'.format(name.value)
return u'DNS:{0}'.format(_adjust_idn(name.value, idn_rewrite))
if isinstance(name, x509.IPAddress):
if isinstance(name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network)):
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 u'email:{0}'.format(name.value)
return u'email:{0}'.format(_adjust_idn_email(name.value, idn_rewrite))
if isinstance(name, x509.UniformResourceIdentifier):
return u'URI:{0}'.format(name.value)
return u'URI:{0}'.format(_adjust_idn_url(name.value, idn_rewrite))
if isinstance(name, x509.DirectoryName):
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
# According to https://datatracker.ietf.org/doc/html/rfc4514.html#section-2.1 the
# list needs to be reversed, and joined by commas
return u'dirName:' + ','.join([
u'{0}={1}'.format(to_text(cryptography_oid_to_name(attribute.oid, short=True)), _dn_escape_value(attribute.value))
for attribute in reversed(list(name.value))
])
if isinstance(name, x509.RegisteredID):
return u'RID:{0}'.format(name.value.dotted_string)
@@ -495,32 +622,75 @@ def cryptography_key_needs_digest_for_signing(key):
return True
def _compare_public_keys(key1, key2, clazz):
a = isinstance(key1, clazz)
b = isinstance(key2, clazz)
if not (a or b):
return None
if not a or not b:
return False
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
return a == b
def cryptography_compare_public_keys(key1, key2):
'''Tests whether two public keys are the same.
Needs special logic for Ed25519 and Ed448 keys, since they do not have public_numbers().
'''
if CRYPTOGRAPHY_HAS_ED25519:
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
if a or b:
if not a or not b:
return False
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
return a == b
res = _compare_public_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PublicKey)
if res is not None:
return res
if CRYPTOGRAPHY_HAS_ED448:
a = isinstance(key1, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
b = isinstance(key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
if a or b:
if not a or not b:
return False
a = key1.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
b = key2.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
return a == b
res = _compare_public_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PublicKey)
if res is not None:
return res
return key1.public_numbers() == key2.public_numbers()
def _compare_private_keys(key1, key2, clazz, has_no_private_bytes=False):
a = isinstance(key1, clazz)
b = isinstance(key2, clazz)
if not (a or b):
return None
if not a or not b:
return False
if has_no_private_bytes:
# We do not have the private_bytes() function - compare associated public keys
return cryptography_compare_public_keys(a.public_key(), b.public_key())
encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption()
a = key1.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, encryption_algorithm=encryption_algorithm)
b = key2.private_bytes(serialization.Encoding.Raw, serialization.PrivateFormat.Raw, encryption_algorithm=encryption_algorithm)
return a == b
def cryptography_compare_private_keys(key1, key2):
'''Tests whether two private keys are the same.
Needs special logic for Ed25519, X25519, and Ed448 keys, since they do not have private_numbers().
'''
if CRYPTOGRAPHY_HAS_ED25519:
res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey)
if res is not None:
return res
if CRYPTOGRAPHY_HAS_X25519:
res = _compare_private_keys(
key1, key2, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey, has_no_private_bytes=not CRYPTOGRAPHY_HAS_X25519_FULL)
if res is not None:
return res
if CRYPTOGRAPHY_HAS_ED448:
res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey)
if res is not None:
return res
if CRYPTOGRAPHY_HAS_X448:
res = _compare_private_keys(key1, key2, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey)
if res is not None:
return res
return key1.private_numbers() == key2.private_numbers()
def cryptography_serial_number_of_cert(cert):
'''Returns cert.serial_number.
@@ -644,3 +814,23 @@ def cryptography_verify_certificate_signature(certificate, signer_public_key):
certificate.signature_hash_algorithm,
signer_public_key
)
def get_not_valid_after(obj):
if CRYPTOGRAPHY_TIMEZONE:
return obj.not_valid_after_utc
return obj.not_valid_after
def get_not_valid_before(obj):
if CRYPTOGRAPHY_TIMEZONE:
return obj.not_valid_before_utc
return obj.not_valid_before
def set_not_valid_after(builder, value):
return builder.not_valid_after(value)
def set_not_valid_before(builder, value):
return builder.not_valid_before(value)

View File

@@ -1,19 +1,8 @@
# -*- coding: utf-8 -*-
#
# (c) 2019, Felix Fontein <felix@fontein.de>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Copyright (c) 2019, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -50,7 +39,7 @@ def quick_is_not_prime(n):
'''Does some quick checks to see if we can poke a hole into the primality of n.
A result of `False` does **not** mean that the number is prime; it just means
that we couldn't detect quickly whether it is not prime.
that we could not detect quickly whether it is not prime.
'''
if n <= 2:
return n < 2
@@ -74,17 +63,111 @@ def quick_is_not_prime(n):
python_version = (sys.version_info[0], sys.version_info[1])
if python_version >= (2, 7) or python_version >= (3, 1):
# Ansible still supports Python 2.6 on remote nodes
def count_bytes(no):
"""
Given an integer, compute the number of bytes necessary to store its absolute value.
"""
no = abs(no)
if no == 0:
return 0
return (no.bit_length() + 7) // 8
def count_bits(no):
"""
Given an integer, compute the number of bits necessary to store its absolute value.
"""
no = abs(no)
if no == 0:
return 0
return no.bit_length()
else:
# Slow, but works
def count_bytes(no):
"""
Given an integer, compute the number of bytes necessary to store its absolute value.
"""
no = abs(no)
count = 0
while no > 0:
no >>= 8
count += 1
return count
def count_bits(no):
"""
Given an integer, compute the number of bits necessary to store its absolute value.
"""
no = abs(no)
count = 0
while no > 0:
no >>= 1
count += 1
return count
if sys.version_info[0] >= 3:
# Python 3 (and newer)
def _convert_int_to_bytes(count, no):
return no.to_bytes(count, byteorder='big')
def _convert_bytes_to_int(data):
return int.from_bytes(data, byteorder='big', signed=False)
def _to_hex(no):
return hex(no)[2:]
else:
# Python 2
def _convert_int_to_bytes(count, n):
if n == 0 and count == 0:
return ''
h = '%x' % n
if len(h) > 2 * count:
raise Exception('Number {1} needs more than {0} bytes!'.format(count, n))
return ('0' * (2 * count - len(h)) + h).decode('hex')
def _convert_bytes_to_int(data):
v = 0
for x in data:
v = (v << 8) | ord(x)
return v
def _to_hex(no):
return '%x' % no
def convert_int_to_bytes(no, count=None):
"""
Convert the absolute value of an integer to a byte string in network byte order.
If ``count`` is provided, it must be sufficiently large so that the integer's
absolute value can be represented with these number of bytes. The resulting byte
string will have length exactly ``count``.
The value zero will be converted to an empty byte string if ``count`` is provided.
"""
no = abs(no)
if count is None:
count = count_bytes(no)
return _convert_int_to_bytes(count, no)
def convert_int_to_hex(no, digits=None):
"""
Convert the absolute value of an integer to a string of hexadecimal digits.
If ``digits`` is provided, the string will be padded on the left with ``0``s so
that the returned value has length ``digits``. If ``digits`` is not sufficient,
the string will be longer.
"""
no = abs(no)
value = _to_hex(no)
if digits is not None and len(value) < digits:
value = '0' * (digits - len(value)) + value
return value
def convert_bytes_to_int(data):
"""
Convert a byte string to an unsigned integer in network byte order.
"""
return _convert_bytes_to_int(data)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -14,9 +15,9 @@ import traceback
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.argspec import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
@@ -31,6 +32,8 @@ 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,
get_not_valid_after,
get_not_valid_before,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_info import (
@@ -38,18 +41,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_PYOPENSSL_VERSION = '0.15'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
CRYPTOGRAPHY_VERSION = None
@@ -75,6 +66,7 @@ class CertificateBackend(object):
self.backend = backend
self.force = module.params['force']
self.ignore_timestamps = module.params['ignore_timestamps']
self.privatekey_path = module.params['privatekey_path']
self.privatekey_content = module.params['privatekey_content']
if self.privatekey_content is not None:
@@ -173,43 +165,12 @@ class CertificateBackend(object):
def _check_privatekey(self):
"""Check whether provided parameters match, assuming self.existing_certificate and self.privatekey have been populated."""
if self.backend == 'pyopenssl':
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
ctx.use_privatekey(self.privatekey)
ctx.use_certificate(self.existing_certificate)
try:
ctx.check_privatekey()
return True
except OpenSSL.SSL.Error:
return False
elif self.backend == 'cryptography':
if self.backend == 'cryptography':
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
def _check_csr(self):
"""Check whether provided parameters match, assuming self.existing_certificate and self.csr have been populated."""
if self.backend == 'pyopenssl':
# Verify that CSR is signed by certificate's private key
try:
self.csr.verify(self.existing_certificate.get_pubkey())
except OpenSSL.crypto.Error:
return False
# Check subject
if self.check_csr_subject and self.csr.get_subject() != self.existing_certificate.get_subject():
return False
# Check extensions
if not self.check_csr_extensions:
return True
csr_extensions = self.csr.get_extensions()
cert_extension_count = self.existing_certificate.get_extension_count()
if len(csr_extensions) != cert_extension_count:
return False
for extension_number in range(0, cert_extension_count):
cert_extension = self.existing_certificate.get_extension(extension_number)
csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions)
if cert_extension.get_data() != list(csr_extension)[0].get_data():
return False
return True
elif self.backend == 'cryptography':
if self.backend == 'cryptography':
# Verify that CSR is signed by certificate's private key
if not self.csr.is_signature_valid:
return False
@@ -244,10 +205,6 @@ class CertificateBackend(object):
def _check_subject_key_identifier(self):
"""Check whether Subject Key Identifier matches, assuming self.existing_certificate has been populated."""
if self.backend != 'cryptography':
# We do not support SKI with pyOpenSSL backend
return True
# Get hold of certificate's SKI
try:
ext = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier)
@@ -265,12 +222,12 @@ class CertificateBackend(object):
if ext.value.digest != x509.SubjectKeyIdentifier.from_public_key(self.existing_certificate.public_key()).digest:
return False
else:
# If CSR had SKI and we didn't ignore it ('create_if_not_provided'), compare SKIs
# If CSR had SKI and we did not ignore it ('create_if_not_provided'), compare SKIs
if ext.value.digest != csr_ext.value.digest:
return False
return True
def needs_regeneration(self):
def needs_regeneration(self, not_before=None, not_after=None):
"""Check whether a regeneration is necessary."""
if self.force or self.existing_certificate_bytes is None:
return True
@@ -294,6 +251,15 @@ class CertificateBackend(object):
if self.create_subject_key_identifier != 'never_create' and not self._check_subject_key_identifier():
return True
# Check not before
if not_before is not None and not self.ignore_timestamps:
if get_not_valid_before(self.existing_certificate) != not_before:
return True
# Check not after
if not_after is not None and not self.ignore_timestamps:
if get_not_valid_after(self.existing_certificate) != not_after:
return True
return False
def dump(self, include_certificate):
@@ -328,10 +294,6 @@ class CertificateProvider(object):
def needs_version_two_certs(self, module):
"""Whether the provider needs to create a version 2 certificate."""
def needs_pyopenssl_get_extensions(self, module):
"""Whether the provider needs to use get_extensions() with pyOpenSSL."""
return True
@abc.abstractmethod
def create_backend(self, module, backend):
"""Create an implementation for a backend.
@@ -352,45 +314,22 @@ def select_backend(module, backend, provider):
if backend == 'auto':
# Detect what backend we can use
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# If cryptography is available we'll use it
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
if provider.needs_version_two_certs(module):
module.warn('crypto backend forced to pyopenssl. The cryptography library does not support v2 certificates')
backend = 'pyopenssl'
# Fail if no backend has been found
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == 'pyopenssl':
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
if provider.needs_pyopenssl_get_extensions(module):
try:
getattr(crypto.X509Req, 'get_extensions')
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15')
elif backend == 'cryptography':
if backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
if provider.needs_version_two_certs(module):
module.fail_json(msg='The cryptography backend does not support v2 certificates, '
'use select_crypto_backend=pyopenssl for v2 certificates')
module.fail_json(msg='The cryptography backend does not support v2 certificates')
return provider.create_backend(module, backend)
@@ -402,7 +341,8 @@ def get_certificate_argument_spec():
force=dict(type='bool', default=False,),
csr_path=dict(type='path'),
csr_content=dict(type='str'),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
ignore_timestamps=dict(type='bool', default=True),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
# General properties of a certificate
privatekey_path=dict(type='path'),

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type

View File

@@ -1,671 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import abc
import datetime
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
parse_name_field,
get_relative_time_option,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_compare_public_keys,
cryptography_get_name,
cryptography_name_to_oid,
cryptography_parse_key_usage_params,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_normalize_name_attribute,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
CertificateBackend,
CertificateProvider,
)
try:
import OpenSSL
from OpenSSL import crypto
except (ImportError, AttributeError):
pass
try:
import cryptography
from cryptography import x509
from cryptography.x509 import NameAttribute, Name
except ImportError:
pass
def compare_sets(subset, superset, equality=False):
if equality:
return set(subset) == set(superset)
else:
return all(x in superset for x in subset)
def compare_dicts(subset, superset, equality=False):
if equality:
return subset == superset
else:
return all(superset.get(x) == v for x, v in subset.items())
NO_EXTENSION = 'no extension'
class AssertOnlyCertificateBackend(CertificateBackend):
def __init__(self, module, backend):
super(AssertOnlyCertificateBackend, self).__init__(module, backend)
self.signature_algorithms = module.params['signature_algorithms']
if module.params['subject']:
self.subject = parse_name_field(module.params['subject'])
else:
self.subject = []
self.subject_strict = module.params['subject_strict']
if module.params['issuer']:
self.issuer = parse_name_field(module.params['issuer'])
else:
self.issuer = []
self.issuer_strict = module.params['issuer_strict']
self.has_expired = module.params['has_expired']
self.version = module.params['version']
self.key_usage = module.params['key_usage']
self.key_usage_strict = module.params['key_usage_strict']
self.extended_key_usage = module.params['extended_key_usage']
self.extended_key_usage_strict = module.params['extended_key_usage_strict']
self.subject_alt_name = module.params['subject_alt_name']
self.subject_alt_name_strict = module.params['subject_alt_name_strict']
self.not_before = module.params['not_before']
self.not_after = module.params['not_after']
self.valid_at = module.params['valid_at']
self.invalid_at = module.params['invalid_at']
self.valid_in = module.params['valid_in']
if self.valid_in and not self.valid_in.startswith("+") and not self.valid_in.startswith("-"):
try:
int(self.valid_in)
except ValueError:
module.fail_json(msg='The supplied value for "valid_in" (%s) is not an integer or a valid timespec' % self.valid_in)
self.valid_in = "+" + self.valid_in + "s"
# Load objects
self._ensure_private_key_loaded()
self._ensure_csr_loaded()
@abc.abstractmethod
def _validate_privatekey(self):
pass
@abc.abstractmethod
def _validate_csr_signature(self):
pass
@abc.abstractmethod
def _validate_csr_subject(self):
pass
@abc.abstractmethod
def _validate_csr_extensions(self):
pass
@abc.abstractmethod
def _validate_signature_algorithms(self):
pass
@abc.abstractmethod
def _validate_subject(self):
pass
@abc.abstractmethod
def _validate_issuer(self):
pass
@abc.abstractmethod
def _validate_has_expired(self):
pass
@abc.abstractmethod
def _validate_version(self):
pass
@abc.abstractmethod
def _validate_key_usage(self):
pass
@abc.abstractmethod
def _validate_extended_key_usage(self):
pass
@abc.abstractmethod
def _validate_subject_alt_name(self):
pass
@abc.abstractmethod
def _validate_not_before(self):
pass
@abc.abstractmethod
def _validate_not_after(self):
pass
@abc.abstractmethod
def _validate_valid_at(self):
pass
@abc.abstractmethod
def _validate_invalid_at(self):
pass
@abc.abstractmethod
def _validate_valid_in(self):
pass
def assertonly(self):
messages = []
if self.privatekey_path is not None or self.privatekey_content is not None:
if not self._validate_privatekey():
messages.append(
'Certificate and private key %s do not match' %
(self.privatekey_path or '(provided in module options)')
)
if self.csr_path is not None or self.csr_content is not None:
if not self._validate_csr_signature():
messages.append(
'Certificate and CSR %s do not match: private key mismatch' %
(self.csr_path or '(provided in module options)')
)
if not self._validate_csr_subject():
messages.append(
'Certificate and CSR %s do not match: subject mismatch' %
(self.csr_path or '(provided in module options)')
)
if not self._validate_csr_extensions():
messages.append(
'Certificate and CSR %s do not match: extensions mismatch' %
(self.csr_path or '(provided in module options)')
)
if self.signature_algorithms is not None:
wrong_alg = self._validate_signature_algorithms()
if wrong_alg:
messages.append(
'Invalid signature algorithm (got %s, expected one of %s)' %
(wrong_alg, self.signature_algorithms)
)
if self.subject is not None:
failure = self._validate_subject()
if failure:
dummy, cert_subject = failure
messages.append(
'Invalid subject component (got %s, expected all of %s to be present)' %
(cert_subject, self.subject)
)
if self.issuer is not None:
failure = self._validate_issuer()
if failure:
dummy, cert_issuer = failure
messages.append(
'Invalid issuer component (got %s, expected all of %s to be present)' % (cert_issuer, self.issuer)
)
if self.has_expired is not None:
cert_expired = self._validate_has_expired()
if cert_expired != self.has_expired:
messages.append(
'Certificate expiration check failed (certificate expiration is %s, expected %s)' %
(cert_expired, self.has_expired)
)
if self.version is not None:
cert_version = self._validate_version()
if cert_version != self.version:
messages.append(
'Invalid certificate version number (got %s, expected %s)' %
(cert_version, self.version)
)
if self.key_usage is not None:
failure = self._validate_key_usage()
if failure == NO_EXTENSION:
messages.append('Found no keyUsage extension')
elif failure:
dummy, cert_key_usage = failure
messages.append(
'Invalid keyUsage components (got %s, expected all of %s to be present)' %
(cert_key_usage, self.key_usage)
)
if self.extended_key_usage is not None:
failure = self._validate_extended_key_usage()
if failure == NO_EXTENSION:
messages.append('Found no extendedKeyUsage extension')
elif failure:
dummy, ext_cert_key_usage = failure
messages.append(
'Invalid extendedKeyUsage component (got %s, expected all of %s to be present)' % (ext_cert_key_usage, self.extended_key_usage)
)
if self.subject_alt_name is not None:
failure = self._validate_subject_alt_name()
if failure == NO_EXTENSION:
messages.append('Found no subjectAltName extension')
elif failure:
dummy, cert_san = failure
messages.append(
'Invalid subjectAltName component (got %s, expected all of %s to be present)' %
(cert_san, self.subject_alt_name)
)
if self.not_before is not None:
cert_not_valid_before = self._validate_not_before()
if cert_not_valid_before != get_relative_time_option(self.not_before, 'not_before', backend=self.backend):
messages.append(
'Invalid not_before component (got %s, expected %s to be present)' %
(cert_not_valid_before, self.not_before)
)
if self.not_after is not None:
cert_not_valid_after = self._validate_not_after()
if cert_not_valid_after != get_relative_time_option(self.not_after, 'not_after', backend=self.backend):
messages.append(
'Invalid not_after component (got %s, expected %s to be present)' %
(cert_not_valid_after, self.not_after)
)
if self.valid_at is not None:
not_before, valid_at, not_after = self._validate_valid_at()
if not (not_before <= valid_at <= not_after):
messages.append(
'Certificate is not valid for the specified date (%s) - not_before: %s - not_after: %s' %
(self.valid_at, not_before, not_after)
)
if self.invalid_at is not None:
not_before, invalid_at, not_after = self._validate_invalid_at()
if not_before <= invalid_at <= not_after:
messages.append(
'Certificate is not invalid for the specified date (%s) - not_before: %s - not_after: %s' %
(self.invalid_at, not_before, not_after)
)
if self.valid_in is not None:
not_before, valid_in, not_after = self._validate_valid_in()
if not not_before <= valid_in <= not_after:
messages.append(
'Certificate is not valid in %s from now (that would be %s) - not_before: %s - not_after: %s' %
(self.valid_in, valid_in, not_before, not_after)
)
return messages
def needs_regeneration(self):
self._ensure_existing_certificate_loaded()
if self.existing_certificate is None:
self.messages = ['Certificate not provided']
else:
self.messages = self.assertonly()
return len(self.messages) != 0
def generate_certificate(self):
self.module.fail_json(msg=' | '.join(self.messages))
def get_certificate_data(self):
return self.existing_certificate_bytes
class AssertOnlyCertificateBackendCryptography(AssertOnlyCertificateBackend):
"""Validate the supplied cert, using the cryptography backend"""
def __init__(self, module):
super(AssertOnlyCertificateBackendCryptography, self).__init__(module, 'cryptography')
def _validate_privatekey(self):
return cryptography_compare_public_keys(self.existing_certificate.public_key(), self.privatekey.public_key())
def _validate_csr_signature(self):
if not self.csr.is_signature_valid:
return False
return cryptography_compare_public_keys(self.csr.public_key(), self.existing_certificate.public_key())
def _validate_csr_subject(self):
return self.csr.subject == self.existing_certificate.subject
def _validate_csr_extensions(self):
cert_exts = self.existing_certificate.extensions
csr_exts = self.csr.extensions
if len(cert_exts) != len(csr_exts):
return False
for cert_ext in cert_exts:
try:
csr_ext = csr_exts.get_extension_for_oid(cert_ext.oid)
if cert_ext != csr_ext:
return False
except cryptography.x509.ExtensionNotFound as dummy:
return False
return True
def _validate_signature_algorithms(self):
if self.existing_certificate.signature_algorithm_oid._name not in self.signature_algorithms:
return self.existing_certificate.signature_algorithm_oid._name
def _validate_subject(self):
expected_subject = Name([NameAttribute(oid=cryptography_name_to_oid(sub[0]), value=to_text(sub[1]))
for sub in self.subject])
cert_subject = self.existing_certificate.subject
if not compare_sets(expected_subject, cert_subject, self.subject_strict):
return expected_subject, cert_subject
def _validate_issuer(self):
expected_issuer = Name([NameAttribute(oid=cryptography_name_to_oid(iss[0]), value=to_text(iss[1]))
for iss in self.issuer])
cert_issuer = self.existing_certificate.issuer
if not compare_sets(expected_issuer, cert_issuer, self.issuer_strict):
return self.issuer, cert_issuer
def _validate_has_expired(self):
cert_not_after = self.existing_certificate.not_valid_after
cert_expired = cert_not_after < datetime.datetime.utcnow()
return cert_expired
def _validate_version(self):
if self.existing_certificate.version == x509.Version.v1:
return 1
if self.existing_certificate.version == x509.Version.v3:
return 3
return "unknown"
def _validate_key_usage(self):
try:
current_key_usage = self.existing_certificate.extensions.get_extension_for_class(x509.KeyUsage).value
test_key_usage = dict(
digital_signature=current_key_usage.digital_signature,
content_commitment=current_key_usage.content_commitment,
key_encipherment=current_key_usage.key_encipherment,
data_encipherment=current_key_usage.data_encipherment,
key_agreement=current_key_usage.key_agreement,
key_cert_sign=current_key_usage.key_cert_sign,
crl_sign=current_key_usage.crl_sign,
encipher_only=False,
decipher_only=False
)
if test_key_usage['key_agreement']:
test_key_usage.update(dict(
encipher_only=current_key_usage.encipher_only,
decipher_only=current_key_usage.decipher_only
))
key_usages = cryptography_parse_key_usage_params(self.key_usage)
if not compare_dicts(key_usages, test_key_usage, self.key_usage_strict):
return self.key_usage, [k for k, v in test_key_usage.items() if v is True]
except cryptography.x509.ExtensionNotFound:
# This is only bad if the user specified a non-empty list
if self.key_usage:
return NO_EXTENSION
def _validate_extended_key_usage(self):
try:
current_ext_keyusage = self.existing_certificate.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value
usages = [cryptography_name_to_oid(usage) for usage in self.extended_key_usage]
expected_ext_keyusage = x509.ExtendedKeyUsage(usages)
if not compare_sets(expected_ext_keyusage, current_ext_keyusage, self.extended_key_usage_strict):
return [eku.value for eku in expected_ext_keyusage], [eku.value for eku in current_ext_keyusage]
except cryptography.x509.ExtensionNotFound:
# This is only bad if the user specified a non-empty list
if self.extended_key_usage:
return NO_EXTENSION
def _validate_subject_alt_name(self):
try:
current_san = self.existing_certificate.extensions.get_extension_for_class(x509.SubjectAlternativeName).value
expected_san = [cryptography_get_name(san) for san in self.subject_alt_name]
if not compare_sets(expected_san, current_san, self.subject_alt_name_strict):
return self.subject_alt_name, current_san
except cryptography.x509.ExtensionNotFound:
# This is only bad if the user specified a non-empty list
if self.subject_alt_name:
return NO_EXTENSION
def _validate_not_before(self):
return self.existing_certificate.not_valid_before
def _validate_not_after(self):
return self.existing_certificate.not_valid_after
def _validate_valid_at(self):
rt = get_relative_time_option(self.valid_at, 'valid_at', backend=self.backend)
return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after
def _validate_invalid_at(self):
rt = get_relative_time_option(self.invalid_at, 'invalid_at', backend=self.backend)
return self.existing_certificate.not_valid_before, rt, self.existing_certificate.not_valid_after
def _validate_valid_in(self):
valid_in_date = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend)
return self.existing_certificate.not_valid_before, valid_in_date, self.existing_certificate.not_valid_after
class AssertOnlyCertificateBackendPyOpenSSL(AssertOnlyCertificateBackend):
"""validate the supplied certificate."""
def __init__(self, module):
super(AssertOnlyCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
# Ensure inputs are properly sanitized before comparison.
for param in ['signature_algorithms', 'key_usage', 'extended_key_usage',
'subject_alt_name', 'subject', 'issuer', 'not_before',
'not_after', 'valid_at', 'invalid_at']:
attr = getattr(self, param)
if isinstance(attr, list) and attr:
if isinstance(attr[0], str):
setattr(self, param, [to_bytes(item) for item in attr])
elif isinstance(attr[0], tuple):
setattr(self, param, [(to_bytes(item[0]), to_bytes(item[1])) for item in attr])
elif isinstance(attr, tuple):
setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items()))
elif isinstance(attr, dict):
setattr(self, param, dict((to_bytes(k), to_bytes(v)) for (k, v) in attr.items()))
elif isinstance(attr, str):
setattr(self, param, to_bytes(attr))
def _validate_privatekey(self):
ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_2_METHOD)
try:
ctx.use_privatekey(self.privatekey)
ctx.use_certificate(self.existing_certificate)
except OpenSSL.SSL.Error as exc:
raise OpenSSLObjectError('Unexpected error while trying to validate private key with certificate: %s' % exc)
try:
ctx.check_privatekey()
return True
except OpenSSL.SSL.Error:
return False
def _validate_csr_signature(self):
try:
self.csr.verify(self.existing_certificate.get_pubkey())
except OpenSSL.crypto.Error:
return False
def _validate_csr_subject(self):
if self.csr.get_subject() != self.existing_certificate.get_subject():
return False
def _validate_csr_extensions(self):
csr_extensions = self.csr.get_extensions()
cert_extension_count = self.existing_certificate.get_extension_count()
if len(csr_extensions) != cert_extension_count:
return False
for extension_number in range(0, cert_extension_count):
cert_extension = self.existing_certificate.get_extension(extension_number)
csr_extension = filter(lambda extension: extension.get_short_name() == cert_extension.get_short_name(), csr_extensions)
if cert_extension.get_data() != list(csr_extension)[0].get_data():
return False
return True
def _validate_signature_algorithms(self):
if self.existing_certificate.get_signature_algorithm() not in self.signature_algorithms:
return self.existing_certificate.get_signature_algorithm()
def _validate_subject(self):
expected_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in self.subject]
cert_subject = self.existing_certificate.get_subject().get_components()
current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(sub[0]), sub[1]) for sub in cert_subject]
if not compare_sets(expected_subject, current_subject, self.subject_strict):
return expected_subject, current_subject
def _validate_issuer(self):
expected_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in self.issuer]
cert_issuer = self.existing_certificate.get_issuer().get_components()
current_issuer = [(OpenSSL._util.lib.OBJ_txt2nid(iss[0]), iss[1]) for iss in cert_issuer]
if not compare_sets(expected_issuer, current_issuer, self.issuer_strict):
return self.issuer, cert_issuer
def _validate_has_expired(self):
# The following 3 lines are the same as the current PyOpenSSL code for cert.has_expired().
# Older version of PyOpenSSL have a buggy implementation,
# to avoid issues with those we added the code from a more recent release here.
time_string = to_native(self.existing_certificate.get_notAfter())
not_after = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
cert_expired = not_after < datetime.datetime.utcnow()
return cert_expired
def _validate_version(self):
# Version numbers in certs are off by one:
# v1: 0, v2: 1, v3: 2 ...
return self.existing_certificate.get_version() + 1
def _validate_key_usage(self):
found = False
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
extension = self.existing_certificate.get_extension(extension_idx)
if extension.get_short_name() == b'keyUsage':
found = True
expected_extension = crypto.X509Extension(b"keyUsage", False, b', '.join(self.key_usage))
key_usage = [usage.strip() for usage in to_text(expected_extension, errors='surrogate_or_strict').split(',')]
current_ku = [usage.strip() for usage in to_text(extension, errors='surrogate_or_strict').split(',')]
if not compare_sets(key_usage, current_ku, self.key_usage_strict):
return self.key_usage, str(extension).split(', ')
if not found:
# This is only bad if the user specified a non-empty list
if self.key_usage:
return NO_EXTENSION
def _validate_extended_key_usage(self):
found = False
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
extension = self.existing_certificate.get_extension(extension_idx)
if extension.get_short_name() == b'extendedKeyUsage':
found = True
extKeyUsage = [OpenSSL._util.lib.OBJ_txt2nid(keyUsage) for keyUsage in self.extended_key_usage]
current_xku = [OpenSSL._util.lib.OBJ_txt2nid(usage.strip()) for usage in
to_bytes(extension, errors='surrogate_or_strict').split(b',')]
if not compare_sets(extKeyUsage, current_xku, self.extended_key_usage_strict):
return self.extended_key_usage, str(extension).split(', ')
if not found:
# This is only bad if the user specified a non-empty list
if self.extended_key_usage:
return NO_EXTENSION
def _validate_subject_alt_name(self):
found = False
for extension_idx in range(0, self.existing_certificate.get_extension_count()):
extension = self.existing_certificate.get_extension(extension_idx)
if extension.get_short_name() == b'subjectAltName':
found = True
l_altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
to_text(extension, errors='surrogate_or_strict').split(', ')]
sans = [pyopenssl_normalize_name_attribute(to_text(san, errors='surrogate_or_strict')) for san in self.subject_alt_name]
if not compare_sets(sans, l_altnames, self.subject_alt_name_strict):
return self.subject_alt_name, l_altnames
if not found:
# This is only bad if the user specified a non-empty list
if self.subject_alt_name:
return NO_EXTENSION
def _validate_not_before(self):
return self.existing_certificate.get_notBefore()
def _validate_not_after(self):
return self.existing_certificate.get_notAfter()
def _validate_valid_at(self):
rt = get_relative_time_option(self.valid_at, "valid_at", backend=self.backend)
rt = to_bytes(rt, errors='surrogate_or_strict')
return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter()
def _validate_invalid_at(self):
rt = get_relative_time_option(self.invalid_at, "invalid_at", backend=self.backend)
rt = to_bytes(rt, errors='surrogate_or_strict')
return self.existing_certificate.get_notBefore(), rt, self.existing_certificate.get_notAfter()
def _validate_valid_in(self):
valid_in_asn1 = get_relative_time_option(self.valid_in, "valid_in", backend=self.backend)
valid_in_date = to_bytes(valid_in_asn1, errors='surrogate_or_strict')
return self.existing_certificate.get_notBefore(), valid_in_date, self.existing_certificate.get_notAfter()
class AssertOnlyCertificateProvider(CertificateProvider):
def validate_module_args(self, module):
module.deprecate("The 'assertonly' provider is deprecated; please see the examples of "
"the 'x509_certificate' module on how to replace it with other modules",
version='2.0.0', collection_name='community.crypto')
def needs_version_two_certs(self, module):
return False
def create_backend(self, module, backend):
if backend == 'cryptography':
return AssertOnlyCertificateBackendCryptography(module)
if backend == 'pyopenssl':
return AssertOnlyCertificateBackendPyOpenSSL(module)
def add_assertonly_provider_to_argument_spec(argument_spec):
argument_spec.argument_spec['provider']['choices'].append('assertonly')
argument_spec.argument_spec.update(dict(
signature_algorithms=dict(type='list', elements='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
subject=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
subject_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
issuer=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
issuer_strict=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
has_expired=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.crypto'),
version=dict(type='int', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
key_usage=dict(type='list', elements='str', aliases=['keyUsage'],
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
key_usage_strict=dict(type='bool', default=False, aliases=['keyUsage_strict'],
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
extended_key_usage=dict(type='list', elements='str', aliases=['extendedKeyUsage'],
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
extended_key_usage_strict=dict(type='bool', default=False, aliases=['extendedKeyUsage_strict'],
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
subject_alt_name=dict(type='list', elements='str', aliases=['subjectAltName'],
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
subject_alt_name_strict=dict(type='bool', default=False, aliases=['subjectAltName_strict'],
removed_in_version='2.0.0', removed_from_collection='community.crypto'),
not_before=dict(type='str', aliases=['notBefore'], removed_in_version='2.0.0', removed_from_collection='community.crypto'),
not_after=dict(type='str', aliases=['notAfter'], removed_in_version='2.0.0', removed_from_collection='community.crypto'),
valid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
invalid_at=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
valid_in=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.crypto'),
))

View File

@@ -1,15 +1,15 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import datetime
import time
import os
from ansible.module_utils.common.text.converters import to_native, to_bytes
@@ -18,11 +18,12 @@ from ansible_collections.community.crypto.plugins.module_utils.ecs.api import EC
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate,
get_relative_time_option,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_serial_number_of_cert,
get_not_valid_after,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -31,6 +32,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
CertificateProvider,
)
from ansible_collections.community.crypto.plugins.module_utils.time import (
get_now_datetime,
get_relative_time_option,
)
try:
from cryptography.x509.oid import NameOID
except ImportError:
@@ -41,7 +47,12 @@ class EntrustCertificateBackend(CertificateBackend):
def __init__(self, module, backend):
super(EntrustCertificateBackend, self).__init__(module, backend)
self.trackingId = None
self.notAfter = get_relative_time_option(module.params['entrust_not_after'], 'entrust_not_after', backend=self.backend)
self.notAfter = get_relative_time_option(
module.params['entrust_not_after'],
'entrust_not_after',
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
if self.csr_content is None and self.csr_path is None:
raise CertificateError(
@@ -58,18 +69,7 @@ class EntrustCertificateBackend(CertificateBackend):
# We want to always force behavior of trying to use the organization provided in the CSR.
# To that end we need to parse out the organization from the CSR.
self.csr_org = None
if self.backend == 'pyopenssl':
csr_subject = self.csr.get_subject()
csr_subject_components = csr_subject.get_components()
for k, v in csr_subject_components:
if k.upper() == 'O':
# Entrust does not support multiple validated organizations in a single certificate
if self.csr_org is not None:
self.module.fail_json(msg=("Entrust provider does not currently support multiple validated organizations. Multiple organizations "
"found in Subject DN: '{0}'. ".format(csr_subject)))
else:
self.csr_org = v
elif self.backend == 'cryptography':
if self.backend == 'cryptography':
csr_subject_orgs = self.csr.subject.get_attributes_for_oid(NameOID.ORGANIZATION_NAME)
if len(csr_subject_orgs) == 1:
self.csr_org = csr_subject_orgs[0].value
@@ -109,7 +109,7 @@ class EntrustCertificateBackend(CertificateBackend):
# Handle expiration (30 days if not specified)
expiry = self.notAfter
if not expiry:
gmt_now = datetime.datetime.fromtimestamp(time.mktime(time.gmtime()))
gmt_now = get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
expiry = gmt_now + datetime.timedelta(days=365)
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")
@@ -162,13 +162,9 @@ class EntrustCertificateBackend(CertificateBackend):
if self.existing_certificate:
serial_number = None
expiry = None
if self.backend == 'pyopenssl':
serial_number = "{0:X}".format(self.existing_certificate.get_serial_number())
time_string = to_native(self.existing_certificate.get_notAfter())
expiry = datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
elif self.backend == 'cryptography':
if self.backend == 'cryptography':
serial_number = "{0:X}".format(cryptography_serial_number_of_cert(self.existing_certificate))
expiry = self.existing_certificate.not_valid_after
expiry = get_not_valid_after(self.existing_certificate)
# get some information about the expiry of this certificate
expiry_iso3339 = expiry.strftime("%Y-%m-%dT%H:%M:%S.00Z")

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -11,13 +12,11 @@ __metaclass__ = type
import abc
import binascii
import datetime
import re
import traceback
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.module_utils.common.text.converters import to_native
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
@@ -27,43 +26,24 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_decode_name,
cryptography_get_extensions_from_cert,
cryptography_oid_to_name,
cryptography_serial_number_of_cert,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_get_extensions_from_cert,
pyopenssl_normalize_name,
pyopenssl_normalize_name_attribute,
get_not_valid_after,
get_not_valid_before,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
get_publickey_info,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
MINIMAL_PYOPENSSL_VERSION = '0.15'
from ansible_collections.community.crypto.plugins.module_utils.time import (
get_now_datetime,
)
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
# OpenSSL 1.1.0 or newer
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
else:
# OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
CRYPTOGRAPHY_IMP_ERR = None
try:
@@ -165,9 +145,13 @@ class CertificateInfoRetrieval(object):
def _get_ocsp_uri(self):
pass
def get_info(self, prefer_one_fingerprint=False):
@abc.abstractmethod
def _get_issuer_uri(self):
pass
def get_info(self, prefer_one_fingerprint=False, der_support_enabled=False):
result = dict()
self.cert = load_certificate(None, content=self.content, backend=self.backend)
self.cert = load_certificate(None, content=self.content, backend=self.backend, der_support_enabled=der_support_enabled)
result['signature_algorithm'] = self._get_signature_algorithm()
subject = self._get_subject_ordered()
@@ -191,9 +175,9 @@ class CertificateInfoRetrieval(object):
not_after = self.get_not_after()
result['not_before'] = not_before.strftime(TIMESTAMP_FORMAT)
result['not_after'] = not_after.strftime(TIMESTAMP_FORMAT)
result['expired'] = not_after < datetime.datetime.utcnow()
result['expired'] = not_after < get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
result['public_key'] = self._get_public_key_pem()
result['public_key'] = to_native(self._get_public_key_pem())
public_key_info = get_publickey_info(
self.module,
@@ -209,24 +193,24 @@ class CertificateInfoRetrieval(object):
result['fingerprints'] = get_fingerprint_of_bytes(
self._get_der_bytes(), prefer_one=prefer_one_fingerprint)
if self.backend != 'pyopenssl':
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
result['serial_number'] = self._get_serial_number()
result['extensions_by_oid'] = self._get_all_extensions()
result['ocsp_uri'] = self._get_ocsp_uri()
result['issuer_uri'] = self._get_issuer_uri()
return result
@@ -235,6 +219,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
"""Validate the supplied cert, using the cryptography backend"""
def __init__(self, module, content):
super(CertificateInfoRetrievalCryptography, self).__init__(module, 'cryptography', content)
self.name_encoding = module.params.get('name_encoding', 'ignore')
def _get_der_bytes(self):
return self.cert.public_bytes(serialization.Encoding.DER)
@@ -337,16 +322,16 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
def _get_subject_alt_name(self):
try:
san_ext = self.cert.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [cryptography_decode_name(san) for san in san_ext.value]
result = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in san_ext.value]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
def get_not_before(self):
return self.cert.not_valid_before
return get_not_valid_before(self.cert)
def get_not_after(self):
return self.cert.not_valid_after
return get_not_valid_after(self.cert)
def _get_public_key_pem(self):
return self.cert.public_key().public_bytes(
@@ -369,7 +354,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
issuer = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
except cryptography.x509.ExtensionNotFound:
return None, None, None
@@ -391,137 +376,21 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
pass
return None
class CertificateInfoRetrievalPyOpenSSL(CertificateInfoRetrieval):
"""validate the supplied certificate."""
def __init__(self, module, content):
super(CertificateInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content)
def _get_der_bytes(self):
return crypto.dump_certificate(crypto.FILETYPE_ASN1, self.cert)
def _get_signature_algorithm(self):
return to_text(self.cert.get_signature_algorithm())
def __get_name(self, name):
result = []
for sub in name.get_components():
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
return result
def _get_subject_ordered(self):
return self.__get_name(self.cert.get_subject())
def _get_issuer_ordered(self):
return self.__get_name(self.cert.get_issuer())
def _get_version(self):
# Version numbers in certs are off by one:
# v1: 0, v2: 1, v3: 2 ...
return self.cert.get_version() + 1
def _get_extension(self, short_name):
for extension_idx in range(0, self.cert.get_extension_count()):
extension = self.cert.get_extension(extension_idx)
if extension.get_short_name() == short_name:
result = [
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
]
return sorted(result), bool(extension.get_critical())
return None, False
def _get_key_usage(self):
return self._get_extension(b'keyUsage')
def _get_extended_key_usage(self):
return self._get_extension(b'extendedKeyUsage')
def _get_basic_constraints(self):
return self._get_extension(b'basicConstraints')
def _get_ocsp_must_staple(self):
extensions = [self.cert.get_extension(i) for i in range(0, self.cert.get_extension_count())]
oms_ext = [
ext for ext in extensions
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
]
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
# Older versions of libssl don't know about OCSP Must Staple
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
if oms_ext:
return True, bool(oms_ext[0].get_critical())
else:
return None, False
def _get_subject_alt_name(self):
for extension_idx in range(0, self.cert.get_extension_count()):
extension = self.cert.get_extension(extension_idx)
if extension.get_short_name() == b'subjectAltName':
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
to_text(extension, errors='surrogate_or_strict').split(', ')]
return result, bool(extension.get_critical())
return None, False
def get_not_before(self):
time_string = to_native(self.cert.get_notBefore())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def get_not_after(self):
time_string = to_native(self.cert.get_notAfter())
return datetime.datetime.strptime(time_string, "%Y%m%d%H%M%SZ")
def _get_public_key_pem(self):
def _get_issuer_uri(self):
try:
return crypto.dump_publickey(
crypto.FILETYPE_PEM,
self.cert.get_pubkey(),
)
except AttributeError:
try:
# pyOpenSSL < 16.0:
bio = crypto._new_mem_buf()
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.cert.get_pubkey()._pkey)
if rc != 1:
crypto._raise_current_error()
return crypto._bio_to_string(bio)
except AttributeError:
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_public_key_object(self):
return self.cert.get_pubkey()
def _get_subject_key_identifier(self):
# Won't be implemented
return None
def _get_authority_key_identifier(self):
# Won't be implemented
return None, None, None
def _get_serial_number(self):
return self.cert.get_serial_number()
def _get_all_extensions(self):
return pyopenssl_get_extensions_from_cert(self.cert)
def _get_ocsp_uri(self):
for i in range(self.cert.get_extension_count()):
ext = self.cert.get_extension(i)
if ext.get_short_name() == b'authorityInfoAccess':
v = str(ext)
m = re.search('^OCSP - URI:(.*)$', v, flags=re.MULTILINE)
if m:
return m.group(1)
ext = self.cert.extensions.get_extension_for_class(x509.AuthorityInformationAccess)
for desc in ext.value:
if desc.access_method == x509.oid.AuthorityInformationAccessOID.CA_ISSUERS:
if isinstance(desc.access_location, x509.UniformResourceIdentifier):
return desc.access_location.value
except x509.ExtensionNotFound as dummy:
pass
return None
def get_certificate_info(module, backend, content, prefer_one_fingerprint=False):
if backend == 'cryptography':
info = CertificateInfoRetrievalCryptography(module, content)
elif backend == 'pyopenssl':
info = CertificateInfoRetrievalPyOpenSSL(module, content)
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
@@ -529,34 +398,17 @@ def select_backend(module, backend, content):
if backend == 'auto':
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# First try cryptography, then pyOpenSSL
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Success?
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
module.fail_json(msg=("Cannot detect any of the required Python libraries "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
try:
getattr(crypto.X509Req, 'get_extensions')
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
return backend, CertificateInfoRetrievalPyOpenSSL(module, content)
elif backend == 'cryptography':
if backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -12,8 +13,6 @@ import os
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 (
@@ -23,15 +22,19 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_privatekey,
load_certificate,
get_relative_time_option,
select_message_digest,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_compare_public_keys,
cryptography_key_needs_digest_for_signing,
cryptography_serial_number_of_cert,
cryptography_verify_certificate_signature,
get_not_valid_after,
get_not_valid_before,
set_not_valid_after,
set_not_valid_before,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -41,10 +44,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
CertificateProvider,
)
try:
from OpenSSL import crypto
except (ImportError, AttributeError):
pass
from ansible_collections.community.crypto.plugins.module_utils.time import (
get_relative_time_option,
)
try:
import cryptography
@@ -61,8 +63,18 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
self.create_subject_key_identifier = module.params['ownca_create_subject_key_identifier']
self.create_authority_key_identifier = module.params['ownca_create_authority_key_identifier']
self.notBefore = get_relative_time_option(module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend)
self.notAfter = get_relative_time_option(module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend)
self.notBefore = get_relative_time_option(
module.params['ownca_not_before'],
'ownca_not_before',
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.notAfter = get_relative_time_option(
module.params['ownca_not_after'],
'ownca_not_after',
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.digest = select_message_digest(module.params['ownca_digest'])
self.version = module.params['ownca_version']
self.serial_number = x509.random_serial_number()
@@ -126,8 +138,8 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
cert_builder = cert_builder.subject_name(self.csr.subject)
cert_builder = cert_builder.issuer_name(self.ca_cert.subject)
cert_builder = cert_builder.serial_number(self.serial_number)
cert_builder = cert_builder.not_valid_before(self.notBefore)
cert_builder = cert_builder.not_valid_after(self.notAfter)
cert_builder = set_not_valid_before(cert_builder, self.notBefore)
cert_builder = set_not_valid_after(cert_builder, self.notAfter)
cert_builder = cert_builder.public_key(self.csr.public_key())
has_ski = False
for extension in self.csr.extensions:
@@ -175,7 +187,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
return self.cert.public_bytes(Encoding.PEM)
def needs_regeneration(self):
if super(OwnCACertificateBackendCryptography, self).needs_regeneration():
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
return True
self._ensure_existing_certificate_loaded()
@@ -226,8 +238,8 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
if self.cert is None:
self.cert = self.existing_certificate
result.update({
'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"),
'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"),
'notBefore': get_not_valid_before(self.cert).strftime("%Y%m%d%H%M%SZ"),
'notAfter': get_not_valid_after(self.cert).strftime("%Y%m%d%H%M%SZ"),
'serial_number': cryptography_serial_number_of_cert(self.cert),
})
@@ -242,112 +254,6 @@ def generate_serial_number():
return result
class OwnCACertificateBackendPyOpenSSL(CertificateBackend):
def __init__(self, module):
super(OwnCACertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
self.notBefore = get_relative_time_option(self.module.params['ownca_not_before'], 'ownca_not_before', backend=self.backend)
self.notAfter = get_relative_time_option(self.module.params['ownca_not_after'], 'ownca_not_after', backend=self.backend)
self.digest = self.module.params['ownca_digest']
self.version = self.module.params['ownca_version']
self.serial_number = generate_serial_number()
if self.module.params['ownca_create_subject_key_identifier'] != 'create_if_not_provided':
self.module.fail_json(msg='ownca_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
if self.module.params['ownca_create_authority_key_identifier']:
self.module.warn('ownca_create_authority_key_identifier is ignored by the pyOpenSSL backend!')
self.ca_cert_path = self.module.params['ownca_path']
self.ca_cert_content = self.module.params['ownca_content']
if self.ca_cert_content is not None:
self.ca_cert_content = self.ca_cert_content.encode('utf-8')
self.ca_privatekey_path = self.module.params['ownca_privatekey_path']
self.ca_privatekey_content = self.module.params['ownca_privatekey_content']
if self.ca_privatekey_content is not None:
self.ca_privatekey_content = self.ca_privatekey_content.encode('utf-8')
self.ca_privatekey_passphrase = self.module.params['ownca_privatekey_passphrase']
if self.csr_content is None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
)
if self.ca_cert_content is None and not os.path.exists(self.ca_cert_path):
raise CertificateError(
'The CA certificate file {0} does not exist'.format(self.ca_cert_path)
)
if self.ca_privatekey_content is None and not os.path.exists(self.ca_privatekey_path):
raise CertificateError(
'The CA private key file {0} does not exist'.format(self.ca_privatekey_path)
)
self._ensure_csr_loaded()
self.ca_cert = load_certificate(
path=self.ca_cert_path,
content=self.ca_cert_content,
)
try:
self.ca_privatekey = load_privatekey(
path=self.ca_privatekey_path,
content=self.ca_privatekey_content,
passphrase=self.ca_privatekey_passphrase
)
except OpenSSLBadPassphraseError as exc:
self.module.fail_json(msg=str(exc))
def generate_certificate(self):
"""(Re-)Generate certificate."""
cert = crypto.X509()
cert.set_serial_number(self.serial_number)
cert.set_notBefore(to_bytes(self.notBefore))
cert.set_notAfter(to_bytes(self.notAfter))
cert.set_subject(self.csr.get_subject())
cert.set_issuer(self.ca_cert.get_subject())
cert.set_version(self.version - 1)
cert.set_pubkey(self.csr.get_pubkey())
cert.add_extensions(self.csr.get_extensions())
cert.sign(self.ca_privatekey, self.digest)
self.cert = cert
def get_certificate_data(self):
"""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({
'ca_cert': self.ca_cert_path,
'ca_privatekey': self.ca_privatekey_path,
})
if self.module.check_mode:
result.update({
'notBefore': self.notBefore,
'notAfter': self.notAfter,
'serial_number': self.serial_number,
})
else:
if self.cert is None:
self.cert = self.existing_certificate
result.update({
'notBefore': self.cert.get_notBefore(),
'notAfter': self.cert.get_notAfter(),
'serial_number': self.cert.get_serial_number(),
})
return result
class OwnCACertificateProvider(CertificateProvider):
def validate_module_args(self, module):
if module.params['ownca_path'] is None and module.params['ownca_content'] is None:
@@ -361,8 +267,6 @@ class OwnCACertificateProvider(CertificateProvider):
def create_backend(self, module, backend):
if backend == 'cryptography':
return OwnCACertificateBackendCryptography(module)
if backend == 'pyopenssl':
return OwnCACertificateBackendPyOpenSSL(module)
def add_ownca_provider_to_argument_spec(argument_spec):

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -12,17 +13,19 @@ import os
from random import randrange
from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
get_relative_time_option,
select_message_digest,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_key_needs_digest_for_signing,
cryptography_serial_number_of_cert,
cryptography_verify_certificate_signature,
get_not_valid_after,
get_not_valid_before,
set_not_valid_after,
set_not_valid_before,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import (
@@ -31,10 +34,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
CertificateProvider,
)
try:
from OpenSSL import crypto
except (ImportError, AttributeError):
pass
from ansible_collections.community.crypto.plugins.module_utils.time import (
get_relative_time_option,
)
try:
import cryptography
@@ -50,8 +52,18 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
super(SelfSignedCertificateBackendCryptography, self).__init__(module, 'cryptography')
self.create_subject_key_identifier = module.params['selfsigned_create_subject_key_identifier']
self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend)
self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend)
self.notBefore = get_relative_time_option(
module.params['selfsigned_not_before'],
'selfsigned_not_before',
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.notAfter = get_relative_time_option(
module.params['selfsigned_not_after'],
'selfsigned_not_after',
backend=self.backend,
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.digest = select_message_digest(module.params['selfsigned_digest'])
self.version = module.params['selfsigned_version']
self.serial_number = x509.random_serial_number()
@@ -101,8 +113,8 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
cert_builder = cert_builder.subject_name(self.csr.subject)
cert_builder = cert_builder.issuer_name(self.csr.subject)
cert_builder = cert_builder.serial_number(self.serial_number)
cert_builder = cert_builder.not_valid_before(self.notBefore)
cert_builder = cert_builder.not_valid_after(self.notAfter)
cert_builder = set_not_valid_before(cert_builder, self.notBefore)
cert_builder = set_not_valid_after(cert_builder, self.notAfter)
cert_builder = cert_builder.public_key(self.privatekey.public_key())
has_ski = False
for extension in self.csr.extensions:
@@ -136,7 +148,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
return self.cert.public_bytes(Encoding.PEM)
def needs_regeneration(self):
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration():
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(not_before=self.notBefore, not_after=self.notAfter):
return True
self._ensure_existing_certificate_loaded()
@@ -160,8 +172,8 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
if self.cert is None:
self.cert = self.existing_certificate
result.update({
'notBefore': self.cert.not_valid_before.strftime("%Y%m%d%H%M%SZ"),
'notAfter': self.cert.not_valid_after.strftime("%Y%m%d%H%M%SZ"),
'notBefore': get_not_valid_before(self.cert).strftime("%Y%m%d%H%M%SZ"),
'notAfter': get_not_valid_after(self.cert).strftime("%Y%m%d%H%M%SZ"),
'serial_number': cryptography_serial_number_of_cert(self.cert),
})
@@ -176,76 +188,6 @@ def generate_serial_number():
return result
class SelfSignedCertificateBackendPyOpenSSL(CertificateBackend):
def __init__(self, module):
super(SelfSignedCertificateBackendPyOpenSSL, self).__init__(module, 'pyopenssl')
if module.params['selfsigned_create_subject_key_identifier'] != 'create_if_not_provided':
module.fail_json(msg='selfsigned_create_subject_key_identifier cannot be used with the pyOpenSSL backend!')
self.notBefore = get_relative_time_option(module.params['selfsigned_not_before'], 'selfsigned_not_before', backend=self.backend)
self.notAfter = get_relative_time_option(module.params['selfsigned_not_after'], 'selfsigned_not_after', backend=self.backend)
self.digest = module.params['selfsigned_digest']
self.version = module.params['selfsigned_version']
self.serial_number = generate_serial_number()
if self.csr_path is not None and not os.path.exists(self.csr_path):
raise CertificateError(
'The certificate signing request file {0} does not exist'.format(self.csr_path)
)
if self.privatekey_content is None and not os.path.exists(self.privatekey_path):
raise CertificateError(
'The private key file {0} does not exist'.format(self.privatekey_path)
)
self._ensure_private_key_loaded()
self._ensure_csr_loaded()
if self.csr is None:
# Create empty CSR on the fly
self.csr = crypto.X509Req()
self.csr.set_pubkey(self.privatekey)
self.csr.sign(self.privatekey, self.digest)
def generate_certificate(self):
"""(Re-)Generate certificate."""
cert = crypto.X509()
cert.set_serial_number(self.serial_number)
cert.set_notBefore(to_bytes(self.notBefore))
cert.set_notAfter(to_bytes(self.notAfter))
cert.set_subject(self.csr.get_subject())
cert.set_issuer(self.csr.get_subject())
cert.set_version(self.version - 1)
cert.set_pubkey(self.csr.get_pubkey())
cert.add_extensions(self.csr.get_extensions())
cert.sign(self.privatekey, self.digest)
self.cert = cert
def get_certificate_data(self):
"""Return bytes for self.cert."""
return crypto.dump_certificate(crypto.FILETYPE_PEM, self.cert)
def dump(self, include_certificate):
result = super(SelfSignedCertificateBackendPyOpenSSL, self).dump(include_certificate)
if self.module.check_mode:
result.update({
'notBefore': self.notBefore,
'notAfter': self.notAfter,
'serial_number': self.serial_number,
})
else:
if self.cert is None:
self.cert = self.existing_certificate
result.update({
'notBefore': self.cert.get_notBefore(),
'notAfter': self.cert.get_notAfter(),
'serial_number': self.cert.get_serial_number(),
})
return result
class SelfSignedCertificateProvider(CertificateProvider):
def validate_module_args(self, module):
if module.params['privatekey_path'] is None and module.params['privatekey_content'] is None:
@@ -257,8 +199,6 @@ class SelfSignedCertificateProvider(CertificateProvider):
def create_backend(self, module, backend):
if backend == 'cryptography':
return SelfSignedCertificateBackendCryptography(module)
if backend == 'pyopenssl':
return SelfSignedCertificateBackendPyOpenSSL(module)
def add_selfsigned_provider_to_argument_spec(argument_spec):

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -9,26 +10,19 @@ __metaclass__ = type
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec as _ArgumentSpec
class ArgumentSpec:
def __init__(self, argument_spec, mutually_exclusive=None, required_together=None, required_one_of=None, required_if=None, required_by=None):
self.argument_spec = argument_spec
self.mutually_exclusive = mutually_exclusive or []
self.required_together = required_together or []
self.required_one_of = required_one_of or []
self.required_if = required_if or []
self.required_by = required_by or {}
class ArgumentSpec(_ArgumentSpec):
def create_ansible_module_helper(self, clazz, args, **kwargs):
return clazz(
*args,
argument_spec=self.argument_spec,
mutually_exclusive=self.mutually_exclusive,
required_together=self.required_together,
required_one_of=self.required_one_of,
required_if=self.required_if,
required_by=self.required_by,
**kwargs)
result = super(ArgumentSpec, self).create_ansible_module_helper(clazz, args, **kwargs)
result.deprecate(
"The crypto.module_backends.common module utils is deprecated and will be removed from community.crypto 3.0.0."
" Use the argspec module utils from community.crypto instead.",
version='3.0.0',
collection_name='community.crypto',
)
return result
def create_ansible_module(self, **kwargs):
return self.create_ansible_module_helper(AnsibleModule, (), **kwargs)
__all__ = ('AnsibleModule', 'ArgumentSpec')

View File

@@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -51,6 +52,7 @@ class CRLInfoRetrieval(object):
self.module = module
self.content = content
self.list_revoked_certificates = list_revoked_certificates
self.name_encoding = module.params.get('name_encoding', 'ignore')
def get_info(self):
self.crl_pem = identify_pem_format(self.content)
@@ -86,7 +88,7 @@ class CRLInfoRetrieval(object):
result['revoked_certificates'] = []
for cert in self.crl:
entry = cryptography_decode_revoked_certificate(cert)
result['revoked_certificates'].append(cryptography_dump_revoked(entry))
result['revoked_certificates'].append(cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding))
return result

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -14,7 +15,9 @@ import traceback
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.module_utils.common.text.converters import to_native, to_text
from ansible_collections.community.crypto.plugins.module_utils.argspec import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
@@ -27,6 +30,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
load_privatekey,
load_certificate_request,
parse_name_field,
parse_ordered_name_field,
select_message_digest,
)
@@ -43,40 +47,13 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
REVOCATION_REASON_MAP,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_normalize_name_attribute,
pyopenssl_parse_name_constraints,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.csr_info import (
get_csr_info,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
MINIMAL_PYOPENSSL_VERSION = '0.15'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
# OpenSSL 1.1.0 or newer
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
else:
# OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
@@ -144,6 +121,7 @@ class CertificateSigningRequestBackend(object):
if self.create_subject_key_identifier and self.subject_key_identifier is not None:
module.fail_json(msg='subject_key_identifier cannot be specified if create_subject_key_identifier is true')
self.ordered_subject = False
self.subject = [
('C', module.params['country_name']),
('ST', module.params['state_or_province_name']),
@@ -153,11 +131,19 @@ class CertificateSigningRequestBackend(object):
('CN', module.params['common_name']),
('emailAddress', module.params['email_address']),
]
if module.params['subject']:
self.subject = self.subject + parse_name_field(module.params['subject'])
self.subject = [(entry[0], entry[1]) for entry in self.subject if entry[1]]
try:
if module.params['subject']:
self.subject = self.subject + parse_name_field(module.params['subject'], 'subject')
if module.params['subject_ordered']:
if self.subject:
raise CertificateSigningRequestError('subject_ordered cannot be combined with any other subject field')
self.subject = parse_ordered_name_field(module.params['subject_ordered'], 'subject_ordered')
self.ordered_subject = True
except ValueError as exc:
raise CertificateSigningRequestError(to_native(exc))
self.using_common_name_for_san = False
if not self.subjectAltName and module.params['use_common_name_for_san']:
for sub in self.subject:
@@ -273,174 +259,6 @@ class CertificateSigningRequestBackend(object):
return result
# Implementation with using pyOpenSSL
class CertificateSigningRequestPyOpenSSLBackend(CertificateSigningRequestBackend):
def __init__(self, module):
for o in ('create_subject_key_identifier', ):
if module.params[o]:
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
for o in ('subject_key_identifier', 'authority_key_identifier', 'authority_cert_issuer', 'authority_cert_serial_number', 'crl_distribution_points'):
if module.params[o] is not None:
module.fail_json(msg='You cannot use {0} with the pyOpenSSL backend!'.format(o))
super(CertificateSigningRequestPyOpenSSLBackend, self).__init__(module, 'pyopenssl')
def generate_csr(self):
"""(Re-)Generate CSR."""
self._ensure_private_key_loaded()
req = crypto.X509Req()
req.set_version(self.version - 1)
subject = req.get_subject()
for entry in self.subject:
if entry[1] is not None:
# Workaround for https://github.com/pyca/pyopenssl/issues/165
nid = OpenSSL._util.lib.OBJ_txt2nid(to_bytes(entry[0]))
if nid == 0:
raise CertificateSigningRequestError('Unknown subject field identifier "{0}"'.format(entry[0]))
res = OpenSSL._util.lib.X509_NAME_add_entry_by_NID(subject._name, nid, OpenSSL._util.lib.MBSTRING_UTF8, to_bytes(entry[1]), -1, -1, 0)
if res == 0:
raise CertificateSigningRequestError('Invalid value for subject field identifier "{0}": {1}'.format(entry[0], entry[1]))
extensions = []
if self.subjectAltName:
altnames = ', '.join(self.subjectAltName)
try:
extensions.append(crypto.X509Extension(b"subjectAltName", self.subjectAltName_critical, altnames.encode('ascii')))
except OpenSSL.crypto.Error as e:
raise CertificateSigningRequestError(
'Error while parsing Subject Alternative Names {0} (check for missing type prefix, such as "DNS:"!): {1}'.format(
', '.join(["{0}".format(san) for san in self.subjectAltName]), str(e)
)
)
if self.keyUsage:
usages = ', '.join(self.keyUsage)
extensions.append(crypto.X509Extension(b"keyUsage", self.keyUsage_critical, usages.encode('ascii')))
if self.extendedKeyUsage:
usages = ', '.join(self.extendedKeyUsage)
extensions.append(crypto.X509Extension(b"extendedKeyUsage", self.extendedKeyUsage_critical, usages.encode('ascii')))
if self.basicConstraints:
usages = ', '.join(self.basicConstraints)
extensions.append(crypto.X509Extension(b"basicConstraints", self.basicConstraints_critical, usages.encode('ascii')))
if self.name_constraints_permitted or self.name_constraints_excluded:
usages = ', '.join(
['permitted;{0}'.format(name) for name in self.name_constraints_permitted] +
['excluded;{0}'.format(name) for name in self.name_constraints_excluded]
)
extensions.append(crypto.X509Extension(b"nameConstraints", self.name_constraints_critical, usages.encode('ascii')))
if self.ocspMustStaple:
extensions.append(crypto.X509Extension(OPENSSL_MUST_STAPLE_NAME, self.ocspMustStaple_critical, OPENSSL_MUST_STAPLE_VALUE))
if extensions:
req.add_extensions(extensions)
req.set_pubkey(self.privatekey)
req.sign(self.privatekey, self.digest)
self.csr = req
def get_csr_data(self):
"""Return bytes for self.csr."""
return crypto.dump_certificate_request(crypto.FILETYPE_PEM, self.csr)
def _check_csr(self):
def _check_subject(csr):
subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in self.subject]
current_subject = [(OpenSSL._util.lib.OBJ_txt2nid(to_bytes(sub[0])), to_bytes(sub[1])) for sub in csr.get_subject().get_components()]
if not set(subject) == set(current_subject):
return False
return True
def _check_subjectAltName(extensions):
altnames_ext = next((ext for ext in extensions if ext.get_short_name() == b'subjectAltName'), '')
altnames = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
to_text(altnames_ext, errors='surrogate_or_strict').split(',') if altname.strip()]
if self.subjectAltName:
if (set(altnames) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.subjectAltName]) or
altnames_ext.get_critical() != self.subjectAltName_critical):
return False
else:
if altnames:
return False
return True
def _check_keyUsage_(extensions, extName, expected, critical):
usages_ext = [ext for ext in extensions if ext.get_short_name() == extName]
if (not usages_ext and expected) or (usages_ext and not expected):
return False
elif not usages_ext and not expected:
return True
else:
current = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage.strip())) for usage in str(usages_ext[0]).split(',')]
expected = [OpenSSL._util.lib.OBJ_txt2nid(to_bytes(usage)) for usage in expected]
return set(current) == set(expected) and usages_ext[0].get_critical() == critical
def _check_keyUsage(extensions):
usages_ext = [ext for ext in extensions if ext.get_short_name() == b'keyUsage']
if (not usages_ext and self.keyUsage) or (usages_ext and not self.keyUsage):
return False
elif not usages_ext and not self.keyUsage:
return True
else:
# OpenSSL._util.lib.OBJ_txt2nid() always returns 0 for all keyUsage values
# (since keyUsage has a fixed bitfield for these values and is not extensible).
# Therefore, we create an extension for the wanted values, and compare the
# data of the extensions (which is the serialized bitfield).
expected_ext = crypto.X509Extension(b"keyUsage", False, ', '.join(self.keyUsage).encode('ascii'))
return usages_ext[0].get_data() == expected_ext.get_data() and usages_ext[0].get_critical() == self.keyUsage_critical
def _check_extenededKeyUsage(extensions):
return _check_keyUsage_(extensions, b'extendedKeyUsage', self.extendedKeyUsage, self.extendedKeyUsage_critical)
def _check_basicConstraints(extensions):
return _check_keyUsage_(extensions, b'basicConstraints', self.basicConstraints, self.basicConstraints_critical)
def _check_nameConstraints(extensions):
nc_ext = next((ext for ext in extensions if ext.get_short_name() == b'nameConstraints'), '')
permitted, excluded = pyopenssl_parse_name_constraints(nc_ext)
if self.name_constraints_permitted or self.name_constraints_excluded:
if set(permitted) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_permitted]):
return False
if set(excluded) != set([pyopenssl_normalize_name_attribute(to_text(name)) for name in self.name_constraints_excluded]):
return False
if nc_ext.get_critical() != self.name_constraints_critical:
return False
else:
if permitted or excluded:
return False
return True
def _check_ocspMustStaple(extensions):
oms_ext = [ext for ext in extensions if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE]
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
# Older versions of libssl don't know about OCSP Must Staple
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
if self.ocspMustStaple:
return len(oms_ext) > 0 and oms_ext[0].get_critical() == self.ocspMustStaple_critical
else:
return len(oms_ext) == 0
def _check_extensions(csr):
extensions = csr.get_extensions()
return (_check_subjectAltName(extensions) and _check_keyUsage(extensions) and
_check_extenededKeyUsage(extensions) and _check_basicConstraints(extensions) and
_check_ocspMustStaple(extensions) and _check_nameConstraints(extensions))
def _check_signature(csr):
try:
return csr.verify(self.privatekey)
except crypto.Error:
return False
return _check_subject(self.existing_csr) and _check_extensions(self.existing_csr) and _check_signature(self.existing_csr)
def parse_crl_distribution_points(module, crl_distribution_points):
result = []
for index, parse_crl_distribution_point in enumerate(crl_distribution_points):
@@ -600,7 +418,10 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def _check_subject(csr):
subject = [(cryptography_name_to_oid(entry[0]), to_text(entry[1])) for entry in self.subject]
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
return set(subject) == set(current_subject)
if self.ordered_subject:
return subject == current_subject
else:
return set(subject) == set(current_subject)
def _find_extension(extensions, exttype):
return next(
@@ -760,42 +581,20 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
def select_backend(module, backend):
if module.params['version'] != 1:
module.deprecate('The version option will only support allowed values from community.crypto 2.0.0 on. '
'Currently, only the value 1 is allowed by RFC 2986',
version='2.0.0', collection_name='community.crypto')
if backend == 'auto':
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# First try cryptography, then pyOpenSSL
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Success?
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
module.fail_json(msg=("Cannot detect any of the required Python libraries "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
try:
getattr(crypto.X509Req, 'get_extensions')
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
return backend, CertificateSigningRequestPyOpenSSLBackend(module)
elif backend == 'cryptography':
if backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
@@ -811,8 +610,9 @@ def get_csr_argument_spec():
privatekey_path=dict(type='path'),
privatekey_content=dict(type='str', no_log=True),
privatekey_passphrase=dict(type='str', no_log=True),
version=dict(type='int', default=1),
version=dict(type='int', default=1, choices=[1]),
subject=dict(type='dict'),
subject_ordered=dict(type='list', elements='dict'),
country_name=dict(type='str', aliases=['C', 'countryName']),
state_or_province_name=dict(type='str', aliases=['ST', 'stateOrProvinceName']),
locality_name=dict(type='str', aliases=['L', 'localityName']),
@@ -860,13 +660,14 @@ def get_csr_argument_spec():
mutually_exclusive=[('full_name', 'relative_name')],
required_one_of=[('full_name', 'relative_name', 'crl_issuer')],
),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'pyopenssl']),
select_crypto_backend=dict(type='str', default='auto', choices=['auto', 'cryptography']),
),
required_together=[
['authority_cert_issuer', 'authority_cert_serial_number'],
],
mutually_exclusive=[
['privatekey_path', 'privatekey_content'],
['subject', 'subject_ordered'],
],
required_one_of=[
['privatekey_path', 'privatekey_content'],

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016-2017, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2017, Markus Teufelberger <mteufelberger+ansible@mgit.at>
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -15,13 +16,12 @@ import traceback
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.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.support import (
load_certificate_request,
get_fingerprint_of_bytes,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
@@ -30,38 +30,11 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
cryptography_oid_to_name,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pyopenssl_support import (
pyopenssl_get_extensions_from_csr,
pyopenssl_normalize_name,
pyopenssl_normalize_name_attribute,
pyopenssl_parse_name_constraints,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.publickey_info import (
get_publickey_info,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
MINIMAL_PYOPENSSL_VERSION = '0.15'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER >= 0x10100000:
# OpenSSL 1.1.0 or newer
OPENSSL_MUST_STAPLE_NAME = b"tlsfeature"
OPENSSL_MUST_STAPLE_VALUE = b"status_request"
else:
# OpenSSL 1.0.x or older
OPENSSL_MUST_STAPLE_NAME = b"1.3.6.1.5.5.7.1.24"
OPENSSL_MUST_STAPLE_VALUE = b"DER:30:03:02:01:05"
except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
@@ -160,7 +133,7 @@ class CSRInfoRetrieval(object):
result['name_constraints_critical'],
) = self._get_name_constraints()
result['public_key'] = self._get_public_key_pem()
result['public_key'] = to_native(self._get_public_key_pem())
public_key_info = get_publickey_info(
self.module,
@@ -173,20 +146,19 @@ class CSRInfoRetrieval(object):
'public_key_fingerprints': public_key_info['fingerprints'],
})
if self.backend != 'pyopenssl':
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
ski = self._get_subject_key_identifier()
if ski is not None:
ski = to_native(binascii.hexlify(ski))
ski = ':'.join([ski[i:i + 2] for i in range(0, len(ski), 2)])
result['subject_key_identifier'] = ski
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
aki, aci, acsn = self._get_authority_key_identifier()
if aki is not None:
aki = to_native(binascii.hexlify(aki))
aki = ':'.join([aki[i:i + 2] for i in range(0, len(aki), 2)])
result['authority_key_identifier'] = aki
result['authority_cert_issuer'] = aci
result['authority_cert_serial_number'] = acsn
result['extensions_by_oid'] = self._get_all_extensions()
@@ -203,6 +175,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
"""Validate the supplied CSR, using the cryptography backend"""
def __init__(self, module, content, validate_signature):
super(CSRInfoRetrievalCryptography, self).__init__(module, 'cryptography', content, validate_signature)
self.name_encoding = module.params.get('name_encoding', 'ignore')
def _get_subject_ordered(self):
result = []
@@ -285,7 +258,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
def _get_subject_alt_name(self):
try:
san_ext = self.csr.extensions.get_extension_for_class(x509.SubjectAlternativeName)
result = [cryptography_decode_name(san) for san in san_ext.value]
result = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in san_ext.value]
return result, san_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, False
@@ -293,8 +266,8 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
def _get_name_constraints(self):
try:
nc_ext = self.csr.extensions.get_extension_for_class(x509.NameConstraints)
permitted = [cryptography_decode_name(san) for san in nc_ext.value.permitted_subtrees or []]
excluded = [cryptography_decode_name(san) for san in nc_ext.value.excluded_subtrees or []]
permitted = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in nc_ext.value.permitted_subtrees or []]
excluded = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in nc_ext.value.excluded_subtrees or []]
return permitted, excluded, nc_ext.critical
except cryptography.x509.ExtensionNotFound:
return None, None, False
@@ -320,7 +293,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
ext = self.csr.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier)
issuer = None
if ext.value.authority_cert_issuer is not None:
issuer = [cryptography_decode_name(san) for san in ext.value.authority_cert_issuer]
issuer = [cryptography_decode_name(san, idn_rewrite=self.name_encoding) for san in ext.value.authority_cert_issuer]
return ext.value.key_identifier, issuer, ext.value.authority_cert_serial_number
except cryptography.x509.ExtensionNotFound:
return None, None, None
@@ -332,112 +305,9 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
return self.csr.is_signature_valid
class CSRInfoRetrievalPyOpenSSL(CSRInfoRetrieval):
"""validate the supplied CSR."""
def __init__(self, module, content, validate_signature):
super(CSRInfoRetrievalPyOpenSSL, self).__init__(module, 'pyopenssl', content, validate_signature)
def __get_name(self, name):
result = []
for sub in name.get_components():
result.append([pyopenssl_normalize_name(sub[0]), to_text(sub[1])])
return result
def _get_subject_ordered(self):
return self.__get_name(self.csr.get_subject())
def _get_extension(self, short_name):
for extension in self.csr.get_extensions():
if extension.get_short_name() == short_name:
result = [
pyopenssl_normalize_name(usage.strip()) for usage in to_text(extension, errors='surrogate_or_strict').split(',')
]
return sorted(result), bool(extension.get_critical())
return None, False
def _get_key_usage(self):
return self._get_extension(b'keyUsage')
def _get_extended_key_usage(self):
return self._get_extension(b'extendedKeyUsage')
def _get_basic_constraints(self):
return self._get_extension(b'basicConstraints')
def _get_ocsp_must_staple(self):
extensions = self.csr.get_extensions()
oms_ext = [
ext for ext in extensions
if to_bytes(ext.get_short_name()) == OPENSSL_MUST_STAPLE_NAME and to_bytes(ext) == OPENSSL_MUST_STAPLE_VALUE
]
if OpenSSL.SSL.OPENSSL_VERSION_NUMBER < 0x10100000:
# Older versions of libssl don't know about OCSP Must Staple
oms_ext.extend([ext for ext in extensions if ext.get_short_name() == b'UNDEF' and ext.get_data() == b'\x30\x03\x02\x01\x05'])
if oms_ext:
return True, bool(oms_ext[0].get_critical())
else:
return None, False
def _get_subject_alt_name(self):
for extension in self.csr.get_extensions():
if extension.get_short_name() == b'subjectAltName':
result = [pyopenssl_normalize_name_attribute(altname.strip()) for altname in
to_text(extension, errors='surrogate_or_strict').split(', ')]
return result, bool(extension.get_critical())
return None, False
def _get_name_constraints(self):
for extension in self.csr.get_extensions():
if extension.get_short_name() == b'nameConstraints':
permitted, excluded = pyopenssl_parse_name_constraints(extension)
return permitted, excluded, bool(extension.get_critical())
return None, None, False
def _get_public_key_pem(self):
try:
return crypto.dump_publickey(
crypto.FILETYPE_PEM,
self.csr.get_pubkey(),
)
except AttributeError:
try:
bio = crypto._new_mem_buf()
rc = crypto._lib.PEM_write_bio_PUBKEY(bio, self.csr.get_pubkey()._pkey)
if rc != 1:
crypto._raise_current_error()
return crypto._bio_to_string(bio)
except AttributeError:
self.module.warn('Your pyOpenSSL version does not support dumping public keys. '
'Please upgrade to version 16.0 or newer, or use the cryptography backend.')
def _get_public_key_object(self):
return self.csr.get_pubkey()
def _get_subject_key_identifier(self):
# Won't be implemented
return None
def _get_authority_key_identifier(self):
# Won't be implemented
return None, None, None
def _get_all_extensions(self):
return pyopenssl_get_extensions_from_csr(self.csr)
def _is_signature_valid(self):
try:
return bool(self.csr.verify(self.csr.get_pubkey()))
except crypto.Error:
# OpenSSL error means that key is not consistent
return False
def get_csr_info(module, backend, content, validate_signature=True, prefer_one_fingerprint=False):
if backend == 'cryptography':
info = CSRInfoRetrievalCryptography(module, content, validate_signature=validate_signature)
elif backend == 'pyopenssl':
info = CSRInfoRetrievalPyOpenSSL(module, content, validate_signature=validate_signature)
return info.get_info(prefer_one_fingerprint=prefer_one_fingerprint)
@@ -445,34 +315,17 @@ def select_backend(module, backend, content, validate_signature=True):
if backend == 'auto':
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# First try cryptography, then pyOpenSSL
# Try cryptography
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
# Success?
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
try:
getattr(crypto.X509Req, 'get_extensions')
except AttributeError:
module.fail_json(msg='You need to have PyOpenSSL>=0.15 to generate CSRs')
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
return backend, CSRInfoRetrievalPyOpenSSL(module, content, validate_signature=validate_signature)
elif backend == 'cryptography':
if backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)

View File

@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
#
# Copyright: (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright: (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
# Copyright (c) 2016, Yanis Guenane <yanis+ansible@guenane.org>
# Copyright (c) 2020, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -16,6 +17,8 @@ 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.argspec import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -25,11 +28,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
OpenSSLObjectError,
OpenSSLBadPassphraseError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_privatekey,
get_fingerprint_of_privatekey,
)
@@ -43,23 +44,9 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
get_privatekey_info,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
MINIMAL_PYOPENSSL_VERSION = '0.6'
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
@@ -188,7 +175,7 @@ class PrivateKeyBackend:
return True
self.module.fail_json(msg='Unable to read the key. The key is protected with a another passphrase / no passphrase or broken.'
' Will not proceed. To force regeneration, call the module with `generate`'
' set to `full_idempotence` or `always`, or with `force=yes`.')
' set to `full_idempotence` or `always`, or with `force=true`.')
self._ensure_existing_private_key_loaded()
if self.regenerate != 'never':
if not self._check_size_and_type():
@@ -196,7 +183,7 @@ class PrivateKeyBackend:
return True
self.module.fail_json(msg='Key has wrong type and/or size.'
' Will not proceed. To force regeneration, call the module with `generate`'
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.')
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`.')
# During generation step, regenerate if format does not match and format_mismatch == 'regenerate'
if self.format_mismatch == 'regenerate' and self.regenerate != 'never':
if not self._check_format():
@@ -204,7 +191,7 @@ class PrivateKeyBackend:
return True
self.module.fail_json(msg='Key has wrong format.'
' Will not proceed. To force regeneration, call the module with `generate`'
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.'
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=true`.'
' To convert the key, set `format_mismatch` to `convert`.')
return False
@@ -263,61 +250,6 @@ class PrivateKeyBackend:
return result
# Implementation with using pyOpenSSL
class PrivateKeyPyOpenSSLBackend(PrivateKeyBackend):
def __init__(self, module):
super(PrivateKeyPyOpenSSLBackend, self).__init__(module=module, backend='pyopenssl')
if self.type == 'RSA':
self.openssl_type = crypto.TYPE_RSA
elif self.type == 'DSA':
self.openssl_type = crypto.TYPE_DSA
else:
self.module.fail_json(msg="PyOpenSSL backend only supports RSA and DSA keys.")
if self.format != 'auto_ignore':
self.module.fail_json(msg="PyOpenSSL backend only supports auto_ignore format.")
def generate_private_key(self):
"""(Re-)Generate private key."""
self.private_key = crypto.PKey()
try:
self.private_key.generate_key(self.openssl_type, self.size)
except (TypeError, ValueError) as exc:
raise PrivateKeyError(exc)
def _ensure_existing_private_key_loaded(self):
if self.existing_private_key is None and self.has_existing():
try:
self.existing_private_key = load_privatekey(
None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend)
except OpenSSLBadPassphraseError as exc:
raise PrivateKeyError(exc)
def get_private_key_data(self):
"""Return bytes for self.private_key"""
if self.cipher and self.passphrase:
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key,
self.cipher, to_bytes(self.passphrase))
else:
return crypto.dump_privatekey(crypto.FILETYPE_PEM, self.private_key)
def _check_passphrase(self):
try:
load_privatekey(None, self.passphrase, content=self.existing_private_key_bytes, backend=self.backend)
return True
except Exception as dummy:
return False
def _check_size_and_type(self):
return self.size == self.existing_private_key.bits() and self.openssl_type == self.existing_private_key.type()
def _check_format(self):
# Not supported by this backend
return True
# Implementation with using cryptography
class PrivateKeyCryptographyBackend(PrivateKeyBackend):
@@ -550,36 +482,16 @@ def select_backend(module, backend):
if backend == 'auto':
# Detection what is possible
can_use_cryptography = CRYPTOGRAPHY_FOUND and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
can_use_pyopenssl = PYOPENSSL_FOUND and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
# Decision
if module.params['cipher'] and module.params['passphrase'] and module.params['cipher'] != 'auto':
# First try pyOpenSSL, then cryptography
if can_use_pyopenssl:
backend = 'pyopenssl'
elif can_use_cryptography:
backend = 'cryptography'
else:
# First try cryptography, then pyOpenSSL
if can_use_cryptography:
backend = 'cryptography'
elif can_use_pyopenssl:
backend = 'pyopenssl'
if can_use_cryptography:
backend = 'cryptography'
# Success?
if backend == 'auto':
module.fail_json(msg=("Can't detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1})").format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION))
if backend == 'pyopenssl':
if not PYOPENSSL_FOUND:
module.fail_json(msg=missing_required_lib('pyOpenSSL >= {0}'.format(MINIMAL_PYOPENSSL_VERSION)),
exception=PYOPENSSL_IMP_ERR)
module.deprecate('The module is using the PyOpenSSL backend. This backend has been deprecated',
version='2.0.0', collection_name='community.crypto')
return backend, PrivateKeyPyOpenSSLBackend(module)
elif backend == 'cryptography':
module.fail_json(msg=("Cannot detect the required Python library "
"cryptography (>= {0})").format(MINIMAL_CRYPTOGRAPHY_VERSION))
if backend == 'cryptography':
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
@@ -605,7 +517,7 @@ def get_privatekey_argument_spec():
cipher=dict(type='str'),
format=dict(type='str', default='auto_ignore', choices=['pkcs1', 'pkcs8', 'raw', 'auto', 'auto_ignore']),
format_mismatch=dict(type='str', default='regenerate', choices=['regenerate', 'convert']),
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
select_crypto_backend=dict(type='str', choices=['auto', 'cryptography'], default='auto'),
regenerate=dict(
type='str',
default='full_idempotence',

View File

@@ -0,0 +1,236 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2022, Felix Fontein <felix@fontein.de>
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import abc
import traceback
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.argspec import ArgumentSpec
from ansible_collections.community.crypto.plugins.module_utils.io import (
load_file,
)
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,
CRYPTOGRAPHY_HAS_ED25519,
CRYPTOGRAPHY_HAS_ED448,
OpenSSLObjectError,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
cryptography_compare_private_keys,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
identify_private_key_format,
)
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
import cryptography.exceptions
import cryptography.hazmat.backends
import cryptography.hazmat.primitives.serialization
import cryptography.hazmat.primitives.asymmetric.rsa
import cryptography.hazmat.primitives.asymmetric.dsa
import cryptography.hazmat.primitives.asymmetric.ec
import cryptography.hazmat.primitives.asymmetric.utils
CRYPTOGRAPHY_VERSION = LooseVersion(cryptography.__version__)
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True
class PrivateKeyError(OpenSSLObjectError):
pass
# From the object called `module`, only the following properties are used:
#
# - module.params[]
# - module.warn(msg: str)
# - module.fail_json(msg: str, **kwargs)
@six.add_metaclass(abc.ABCMeta)
class PrivateKeyConvertBackend:
def __init__(self, module, backend):
self.module = module
self.src_path = module.params['src_path']
self.src_content = module.params['src_content']
self.src_passphrase = module.params['src_passphrase']
self.format = module.params['format']
self.dest_passphrase = module.params['dest_passphrase']
self.backend = backend
self.src_private_key = None
if self.src_path is not None:
self.src_private_key_bytes = load_file(self.src_path, module)
else:
self.src_private_key_bytes = self.src_content.encode('utf-8')
self.dest_private_key = None
self.dest_private_key_bytes = None
@abc.abstractmethod
def get_private_key_data(self):
"""Return bytes for self.src_private_key in output format."""
pass
def set_existing_destination(self, privatekey_bytes):
"""Set existing private key bytes. None indicates that the key does not exist."""
self.dest_private_key_bytes = privatekey_bytes
def has_existing_destination(self):
"""Query whether an existing private key is/has been there."""
return self.dest_private_key_bytes is not None
@abc.abstractmethod
def _load_private_key(self, data, passphrase, current_hint=None):
"""Check whether data can be loaded as a private key with the provided passphrase. Return tuple (type, private_key)."""
pass
def needs_conversion(self):
"""Check whether a conversion is necessary. Must only be called if needs_regeneration() returned False."""
dummy, self.src_private_key = self._load_private_key(self.src_private_key_bytes, self.src_passphrase)
if not self.has_existing_destination():
return True
try:
format, self.dest_private_key = self._load_private_key(self.dest_private_key_bytes, self.dest_passphrase, current_hint=self.src_private_key)
except Exception:
return True
return format != self.format or not cryptography_compare_private_keys(self.dest_private_key, self.src_private_key)
def dump(self):
"""Serialize the object into a dictionary."""
return {}
# Implementation with using cryptography
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
def __init__(self, module):
super(PrivateKeyConvertCryptographyBackend, self).__init__(module=module, backend='cryptography')
self.cryptography_backend = cryptography.hazmat.backends.default_backend()
def get_private_key_data(self):
"""Return bytes for self.src_private_key in output format"""
# Select export format and encoding
try:
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.PEM
if self.format == 'pkcs1':
# "TraditionalOpenSSL" format is PKCS1
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL
elif self.format == 'pkcs8':
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8
elif self.format == 'raw':
export_format = cryptography.hazmat.primitives.serialization.PrivateFormat.Raw
export_encoding = cryptography.hazmat.primitives.serialization.Encoding.Raw
except AttributeError:
self.module.fail_json(msg='Cryptography backend does not support the selected output format "{0}"'.format(self.format))
# Select key encryption
encryption_algorithm = cryptography.hazmat.primitives.serialization.NoEncryption()
if self.dest_passphrase:
encryption_algorithm = cryptography.hazmat.primitives.serialization.BestAvailableEncryption(to_bytes(self.dest_passphrase))
# Serialize key
try:
return self.src_private_key.private_bytes(
encoding=export_encoding,
format=export_format,
encryption_algorithm=encryption_algorithm
)
except ValueError as dummy:
self.module.fail_json(
msg='Cryptography backend cannot serialize the private key in the required format "{0}"'.format(self.format)
)
except Exception as dummy:
self.module.fail_json(
msg='Error while serializing the private key in the required format "{0}"'.format(self.format),
exception=traceback.format_exc()
)
def _load_private_key(self, data, passphrase, current_hint=None):
try:
# Interpret bytes depending on format.
format = identify_private_key_format(data)
if format == 'raw':
if passphrase is not None:
raise PrivateKeyError('Cannot load raw key with passphrase')
if len(data) == 56 and CRYPTOGRAPHY_HAS_X448:
return format, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(data)
if len(data) == 57 and CRYPTOGRAPHY_HAS_ED448:
return format, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(data)
if len(data) == 32:
if CRYPTOGRAPHY_HAS_X25519 and not CRYPTOGRAPHY_HAS_ED25519:
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
if CRYPTOGRAPHY_HAS_ED25519 and not CRYPTOGRAPHY_HAS_X25519:
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
if CRYPTOGRAPHY_HAS_X25519 and CRYPTOGRAPHY_HAS_ED25519:
if isinstance(current_hint, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey):
try:
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
except Exception:
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
else:
try:
return format, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(data)
except Exception:
return format, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(data)
raise PrivateKeyError('Cannot load raw key')
else:
return format, cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if passphrase is None else to_bytes(passphrase),
backend=self.cryptography_backend
)
except Exception as e:
raise PrivateKeyError(e)
def select_backend(module):
if not CRYPTOGRAPHY_FOUND:
module.fail_json(msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR)
return PrivateKeyConvertCryptographyBackend(module)
def get_privatekey_argument_spec():
return ArgumentSpec(
argument_spec=dict(
src_path=dict(type='path'),
src_content=dict(type='str'),
src_passphrase=dict(type='str', no_log=True),
dest_passphrase=dict(type='str', no_log=True),
format=dict(type='str', required=True, choices=['pkcs1', 'pkcs8', 'raw']),
),
mutually_exclusive=[
['src_path', 'src_content'],
],
required_one_of=[
['src_path', 'src_content'],
],
)

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