summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/crypto
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/community/crypto')
-rw-r--r--ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml2
-rw-r--r--ansible_collections/community/crypto/.github/workflows/ansible-test.yml3
-rw-r--r--ansible_collections/community/crypto/CHANGELOG.md617
-rw-r--r--ansible_collections/community/crypto/CHANGELOG.rst94
-rw-r--r--ansible_collections/community/crypto/FILES.json332
-rw-r--r--ansible_collections/community/crypto/MANIFEST.json4
-rw-r--r--ansible_collections/community/crypto/README.md2
-rw-r--r--ansible_collections/community/crypto/changelogs/changelog.yaml67
-rw-r--r--ansible_collections/community/crypto/changelogs/config.yaml3
-rw-r--r--ansible_collections/community/crypto/meta/runtime.yml1
-rw-r--r--ansible_collections/community/crypto/plugins/doc_fragments/acme.py178
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/acme.py91
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/backend_cryptography.py97
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/backend_openssl_cli.py95
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/backends.py102
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/challenges.py12
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/orders.py6
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/acme/utils.py58
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/argspec.py75
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/math.py31
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py4
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py14
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_info.py5
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py20
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py20
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py33
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py4
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py4
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey_convert.py6
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/crypto/support.py92
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/openssh/certificate.py18
-rw-r--r--ansible_collections/community/crypto/plugins/module_utils/time.py171
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_account.py22
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_account_info.py22
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_ari_info.py142
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate.py192
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py119
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py245
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py18
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py8
-rw-r--r--ansible_collections/community/crypto/plugins/modules/acme_inspect.py19
-rw-r--r--ansible_collections/community/crypto/plugins/modules/ecs_certificate.py4
-rw-r--r--ansible_collections/community/crypto/plugins/modules/get_certificate.py8
-rw-r--r--ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py8
-rw-r--r--ansible_collections/community/crypto/plugins/modules/x509_crl.py5
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases10
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml8
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml154
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml40
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml17
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases10
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml8
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml145
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml40
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml159
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml47
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml10
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml16
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml12
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml5
-rw-r--r--ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml5
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py89
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt38
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license3
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt57
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license3
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem19
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license3
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt56
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license3
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py57
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py59
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py97
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py117
-rw-r--r--ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py323
75 files changed, 4023 insertions, 660 deletions
diff --git a/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml b/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml
index 19a36fb46..3505f5d99 100644
--- a/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml
+++ b/ansible_collections/community/crypto/.azure-pipelines/azure-pipelines.yml
@@ -176,7 +176,7 @@ stages:
- name: Debian Bookworm
test: debian-bookworm/3.11
- name: ArchLinux
- test: archlinux/3.11
+ test: archlinux/3.12
groups:
- 1
- 2
diff --git a/ansible_collections/community/crypto/.github/workflows/ansible-test.yml b/ansible_collections/community/crypto/.github/workflows/ansible-test.yml
index d31e861c4..dac31b04c 100644
--- a/ansible_collections/community/crypto/.github/workflows/ansible-test.yml
+++ b/ansible_collections/community/crypto/.github/workflows/ansible-test.yml
@@ -50,6 +50,7 @@ jobs:
with:
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
ansible-core-version: stable-${{ matrix.ansible }}
+ codecov-token: ${{ secrets.CODECOV_TOKEN }}
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
pull-request-change-detection: 'true'
testing-type: sanity
@@ -85,6 +86,7 @@ jobs:
with:
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
ansible-core-version: stable-${{ matrix.ansible }}
+ codecov-token: ${{ secrets.CODECOV_TOKEN }}
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
pull-request-change-detection: 'true'
testing-type: units
@@ -282,6 +284,7 @@ jobs:
with:
ansible-core-github-repository-slug: ${{ contains(fromJson('["2.10", "2.11"]'), matrix.ansible) && 'felixfontein/ansible' || 'ansible/ansible' }}
ansible-core-version: stable-${{ matrix.ansible }}
+ codecov-token: ${{ secrets.CODECOV_TOKEN }}
coverage: ${{ github.event_name == 'schedule' && 'always' || 'never' }}
docker-image: ${{ matrix.docker }}
integration-continue-on-error: 'false'
diff --git a/ansible_collections/community/crypto/CHANGELOG.md b/ansible_collections/community/crypto/CHANGELOG.md
index 1652a3ad2..abb97442d 100644
--- a/ansible_collections/community/crypto/CHANGELOG.md
+++ b/ansible_collections/community/crypto/CHANGELOG.md
@@ -2,269 +2,328 @@
**Topics**
-- <a href="#v2-19-0">v2\.19\.0</a>
+- <a href="#v2-20-0">v2\.20\.0</a>
- <a href="#release-summary">Release Summary</a>
- <a href="#minor-changes">Minor Changes</a>
- <a href="#deprecated-features">Deprecated Features</a>
- <a href="#bugfixes">Bugfixes</a>
- <a href="#new-modules">New Modules</a>
-- <a href="#v2-18-0">v2\.18\.0</a>
+- <a href="#v2-19-1">v2\.19\.1</a>
- <a href="#release-summary-1">Release Summary</a>
- - <a href="#minor-changes-1">Minor Changes</a>
- - <a href="#deprecated-features-1">Deprecated Features</a>
- <a href="#bugfixes-1">Bugfixes</a>
- - <a href="#new-plugins">New Plugins</a>
- - <a href="#filter">Filter</a>
-- <a href="#v2-17-1">v2\.17\.1</a>
+- <a href="#v2-19-0">v2\.19\.0</a>
- <a href="#release-summary-2">Release Summary</a>
+ - <a href="#minor-changes-1">Minor Changes</a>
+ - <a href="#deprecated-features-1">Deprecated Features</a>
- <a href="#bugfixes-2">Bugfixes</a>
-- <a href="#v2-17-0">v2\.17\.0</a>
+ - <a href="#new-modules-1">New Modules</a>
+- <a href="#v2-18-0">v2\.18\.0</a>
- <a href="#release-summary-3">Release Summary</a>
- <a href="#minor-changes-2">Minor Changes</a>
-- <a href="#v2-16-2">v2\.16\.2</a>
- - <a href="#release-summary-4">Release Summary</a>
+ - <a href="#deprecated-features-2">Deprecated Features</a>
- <a href="#bugfixes-3">Bugfixes</a>
-- <a href="#v2-16-1">v2\.16\.1</a>
- - <a href="#release-summary-5">Release Summary</a>
+ - <a href="#new-plugins">New Plugins</a>
+ - <a href="#filter">Filter</a>
+- <a href="#v2-17-1">v2\.17\.1</a>
+ - <a href="#release-summary-4">Release Summary</a>
- <a href="#bugfixes-4">Bugfixes</a>
-- <a href="#v2-16-0">v2\.16\.0</a>
- - <a href="#release-summary-6">Release Summary</a>
+- <a href="#v2-17-0">v2\.17\.0</a>
+ - <a href="#release-summary-5">Release Summary</a>
- <a href="#minor-changes-3">Minor Changes</a>
+- <a href="#v2-16-2">v2\.16\.2</a>
+ - <a href="#release-summary-6">Release Summary</a>
- <a href="#bugfixes-5">Bugfixes</a>
-- <a href="#v2-15-1">v2\.15\.1</a>
+- <a href="#v2-16-1">v2\.16\.1</a>
- <a href="#release-summary-7">Release Summary</a>
- <a href="#bugfixes-6">Bugfixes</a>
-- <a href="#v2-15-0">v2\.15\.0</a>
+- <a href="#v2-16-0">v2\.16\.0</a>
- <a href="#release-summary-8">Release Summary</a>
- <a href="#minor-changes-4">Minor Changes</a>
- - <a href="#deprecated-features-2">Deprecated Features</a>
- <a href="#bugfixes-7">Bugfixes</a>
+- <a href="#v2-15-1">v2\.15\.1</a>
+ - <a href="#release-summary-9">Release Summary</a>
+ - <a href="#bugfixes-8">Bugfixes</a>
+- <a href="#v2-15-0">v2\.15\.0</a>
+ - <a href="#release-summary-10">Release Summary</a>
+ - <a href="#minor-changes-5">Minor Changes</a>
+ - <a href="#deprecated-features-3">Deprecated Features</a>
+ - <a href="#bugfixes-9">Bugfixes</a>
- <a href="#new-plugins-1">New Plugins</a>
- <a href="#filter-1">Filter</a>
- <a href="#lookup">Lookup</a>
- <a href="#v2-14-1">v2\.14\.1</a>
- - <a href="#release-summary-9">Release Summary</a>
- - <a href="#bugfixes-8">Bugfixes</a>
+ - <a href="#release-summary-11">Release Summary</a>
+ - <a href="#bugfixes-10">Bugfixes</a>
- <a href="#known-issues">Known Issues</a>
- <a href="#v2-14-0">v2\.14\.0</a>
- - <a href="#release-summary-10">Release Summary</a>
- - <a href="#minor-changes-5">Minor Changes</a>
-- <a href="#v2-13-1">v2\.13\.1</a>
- - <a href="#release-summary-11">Release Summary</a>
- - <a href="#bugfixes-9">Bugfixes</a>
-- <a href="#v2-13-0">v2\.13\.0</a>
- <a href="#release-summary-12">Release Summary</a>
- <a href="#minor-changes-6">Minor Changes</a>
- - <a href="#deprecated-features-3">Deprecated Features</a>
- - <a href="#bugfixes-10">Bugfixes</a>
-- <a href="#v2-12-0">v2\.12\.0</a>
+- <a href="#v2-13-1">v2\.13\.1</a>
- <a href="#release-summary-13">Release Summary</a>
- - <a href="#minor-changes-7">Minor Changes</a>
-- <a href="#v2-11-1">v2\.11\.1</a>
+ - <a href="#bugfixes-11">Bugfixes</a>
+- <a href="#v2-13-0">v2\.13\.0</a>
- <a href="#release-summary-14">Release Summary</a>
-- <a href="#v2-11-0">v2\.11\.0</a>
+ - <a href="#minor-changes-7">Minor Changes</a>
+ - <a href="#deprecated-features-4">Deprecated Features</a>
+ - <a href="#bugfixes-12">Bugfixes</a>
+- <a href="#v2-12-0">v2\.12\.0</a>
- <a href="#release-summary-15">Release Summary</a>
- <a href="#minor-changes-8">Minor Changes</a>
- - <a href="#bugfixes-11">Bugfixes</a>
-- <a href="#v2-10-0">v2\.10\.0</a>
+- <a href="#v2-11-1">v2\.11\.1</a>
- <a href="#release-summary-16">Release Summary</a>
- - <a href="#bugfixes-12">Bugfixes</a>
- - <a href="#new-plugins-2">New Plugins</a>
- - <a href="#filter-2">Filter</a>
-- <a href="#v2-9-0">v2\.9\.0</a>
+- <a href="#v2-11-0">v2\.11\.0</a>
- <a href="#release-summary-17">Release Summary</a>
- <a href="#minor-changes-9">Minor Changes</a>
-- <a href="#v2-8-1">v2\.8\.1</a>
+ - <a href="#bugfixes-13">Bugfixes</a>
+- <a href="#v2-10-0">v2\.10\.0</a>
- <a href="#release-summary-18">Release Summary</a>
-- <a href="#v2-8-0">v2\.8\.0</a>
+ - <a href="#bugfixes-14">Bugfixes</a>
+ - <a href="#new-plugins-2">New Plugins</a>
+ - <a href="#filter-2">Filter</a>
+- <a href="#v2-9-0">v2\.9\.0</a>
- <a href="#release-summary-19">Release Summary</a>
- <a href="#minor-changes-10">Minor Changes</a>
-- <a href="#v2-7-1">v2\.7\.1</a>
+- <a href="#v2-8-1">v2\.8\.1</a>
- <a href="#release-summary-20">Release Summary</a>
- - <a href="#bugfixes-13">Bugfixes</a>
-- <a href="#v2-7-0">v2\.7\.0</a>
+- <a href="#v2-8-0">v2\.8\.0</a>
- <a href="#release-summary-21">Release Summary</a>
- <a href="#minor-changes-11">Minor Changes</a>
- - <a href="#bugfixes-14">Bugfixes</a>
-- <a href="#v2-6-0">v2\.6\.0</a>
+- <a href="#v2-7-1">v2\.7\.1</a>
- <a href="#release-summary-22">Release Summary</a>
- - <a href="#minor-changes-12">Minor Changes</a>
-- <a href="#v2-5-0">v2\.5\.0</a>
+ - <a href="#bugfixes-15">Bugfixes</a>
+- <a href="#v2-7-0">v2\.7\.0</a>
- <a href="#release-summary-23">Release Summary</a>
- - <a href="#minor-changes-13">Minor Changes</a>
-- <a href="#v2-4-0">v2\.4\.0</a>
+ - <a href="#minor-changes-12">Minor Changes</a>
+ - <a href="#bugfixes-16">Bugfixes</a>
+- <a href="#v2-6-0">v2\.6\.0</a>
- <a href="#release-summary-24">Release Summary</a>
- - <a href="#deprecated-features-4">Deprecated Features</a>
- - <a href="#bugfixes-15">Bugfixes</a>
-- <a href="#v2-3-4">v2\.3\.4</a>
+ - <a href="#minor-changes-13">Minor Changes</a>
+- <a href="#v2-5-0">v2\.5\.0</a>
- <a href="#release-summary-25">Release Summary</a>
-- <a href="#v2-3-3">v2\.3\.3</a>
+ - <a href="#minor-changes-14">Minor Changes</a>
+- <a href="#v2-4-0">v2\.4\.0</a>
- <a href="#release-summary-26">Release Summary</a>
- - <a href="#bugfixes-16">Bugfixes</a>
-- <a href="#v2-3-2">v2\.3\.2</a>
- - <a href="#release-summary-27">Release Summary</a>
+ - <a href="#deprecated-features-5">Deprecated Features</a>
- <a href="#bugfixes-17">Bugfixes</a>
-- <a href="#v2-3-1">v2\.3\.1</a>
+- <a href="#v2-3-4">v2\.3\.4</a>
+ - <a href="#release-summary-27">Release Summary</a>
+- <a href="#v2-3-3">v2\.3\.3</a>
- <a href="#release-summary-28">Release Summary</a>
- <a href="#bugfixes-18">Bugfixes</a>
-- <a href="#v2-3-0">v2\.3\.0</a>
+- <a href="#v2-3-2">v2\.3\.2</a>
- <a href="#release-summary-29">Release Summary</a>
- - <a href="#minor-changes-14">Minor Changes</a>
- <a href="#bugfixes-19">Bugfixes</a>
-- <a href="#v2-2-4">v2\.2\.4</a>
+- <a href="#v2-3-1">v2\.3\.1</a>
- <a href="#release-summary-30">Release Summary</a>
- <a href="#bugfixes-20">Bugfixes</a>
-- <a href="#v2-2-3">v2\.2\.3</a>
+- <a href="#v2-3-0">v2\.3\.0</a>
- <a href="#release-summary-31">Release Summary</a>
+ - <a href="#minor-changes-15">Minor Changes</a>
- <a href="#bugfixes-21">Bugfixes</a>
-- <a href="#v2-2-2">v2\.2\.2</a>
+- <a href="#v2-2-4">v2\.2\.4</a>
- <a href="#release-summary-32">Release Summary</a>
- <a href="#bugfixes-22">Bugfixes</a>
-- <a href="#v2-2-1">v2\.2\.1</a>
+- <a href="#v2-2-3">v2\.2\.3</a>
- <a href="#release-summary-33">Release Summary</a>
- <a href="#bugfixes-23">Bugfixes</a>
-- <a href="#v2-2-0">v2\.2\.0</a>
+- <a href="#v2-2-2">v2\.2\.2</a>
- <a href="#release-summary-34">Release Summary</a>
- - <a href="#minor-changes-15">Minor Changes</a>
- <a href="#bugfixes-24">Bugfixes</a>
-- <a href="#v2-1-0">v2\.1\.0</a>
+- <a href="#v2-2-1">v2\.2\.1</a>
- <a href="#release-summary-35">Release Summary</a>
- - <a href="#minor-changes-16">Minor Changes</a>
- <a href="#bugfixes-25">Bugfixes</a>
- - <a href="#new-modules-1">New Modules</a>
-- <a href="#v2-0-2">v2\.0\.2</a>
+- <a href="#v2-2-0">v2\.2\.0</a>
- <a href="#release-summary-36">Release Summary</a>
-- <a href="#v2-0-1">v2\.0\.1</a>
+ - <a href="#minor-changes-16">Minor Changes</a>
+ - <a href="#bugfixes-26">Bugfixes</a>
+- <a href="#v2-1-0">v2\.1\.0</a>
- <a href="#release-summary-37">Release Summary</a>
- <a href="#minor-changes-17">Minor Changes</a>
- - <a href="#bugfixes-26">Bugfixes</a>
-- <a href="#v2-0-0">v2\.0\.0</a>
- - <a href="#release-summary-38">Release Summary</a>
- - <a href="#minor-changes-18">Minor Changes</a>
- - <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
- - <a href="#deprecated-features-5">Deprecated Features</a>
- - <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
- <a href="#bugfixes-27">Bugfixes</a>
-- <a href="#v1-9-4">v1\.9\.4</a>
+ - <a href="#new-modules-2">New Modules</a>
+- <a href="#v2-0-2">v2\.0\.2</a>
+ - <a href="#release-summary-38">Release Summary</a>
+- <a href="#v2-0-1">v2\.0\.1</a>
- <a href="#release-summary-39">Release Summary</a>
+ - <a href="#minor-changes-18">Minor Changes</a>
- <a href="#bugfixes-28">Bugfixes</a>
-- <a href="#v1-9-3">v1\.9\.3</a>
+- <a href="#v2-0-0">v2\.0\.0</a>
- <a href="#release-summary-40">Release Summary</a>
+ - <a href="#minor-changes-19">Minor Changes</a>
+ - <a href="#breaking-changes--porting-guide">Breaking Changes / Porting Guide</a>
+ - <a href="#deprecated-features-6">Deprecated Features</a>
+ - <a href="#removed-features-previously-deprecated">Removed Features \(previously deprecated\)</a>
- <a href="#bugfixes-29">Bugfixes</a>
-- <a href="#v1-9-2">v1\.9\.2</a>
+- <a href="#v1-9-4">v1\.9\.4</a>
- <a href="#release-summary-41">Release Summary</a>
-- <a href="#v1-9-1">v1\.9\.1</a>
+ - <a href="#bugfixes-30">Bugfixes</a>
+- <a href="#v1-9-3">v1\.9\.3</a>
- <a href="#release-summary-42">Release Summary</a>
-- <a href="#v1-9-0">v1\.9\.0</a>
+ - <a href="#bugfixes-31">Bugfixes</a>
+- <a href="#v1-9-2">v1\.9\.2</a>
- <a href="#release-summary-43">Release Summary</a>
- - <a href="#minor-changes-19">Minor Changes</a>
- - <a href="#bugfixes-30">Bugfixes</a>
-- <a href="#v1-8-0">v1\.8\.0</a>
+- <a href="#v1-9-1">v1\.9\.1</a>
- <a href="#release-summary-44">Release Summary</a>
- - <a href="#minor-changes-20">Minor Changes</a>
- - <a href="#bugfixes-31">Bugfixes</a>
-- <a href="#v1-7-1">v1\.7\.1</a>
+- <a href="#v1-9-0">v1\.9\.0</a>
- <a href="#release-summary-45">Release Summary</a>
+ - <a href="#minor-changes-20">Minor Changes</a>
- <a href="#bugfixes-32">Bugfixes</a>
-- <a href="#v1-7-0">v1\.7\.0</a>
+- <a href="#v1-8-0">v1\.8\.0</a>
- <a href="#release-summary-46">Release Summary</a>
- <a href="#minor-changes-21">Minor Changes</a>
- <a href="#bugfixes-33">Bugfixes</a>
- - <a href="#new-modules-2">New Modules</a>
-- <a href="#v1-6-2">v1\.6\.2</a>
+- <a href="#v1-7-1">v1\.7\.1</a>
- <a href="#release-summary-47">Release Summary</a>
- <a href="#bugfixes-34">Bugfixes</a>
-- <a href="#v1-6-1">v1\.6\.1</a>
+- <a href="#v1-7-0">v1\.7\.0</a>
- <a href="#release-summary-48">Release Summary</a>
+ - <a href="#minor-changes-22">Minor Changes</a>
- <a href="#bugfixes-35">Bugfixes</a>
-- <a href="#v1-6-0">v1\.6\.0</a>
+ - <a href="#new-modules-3">New Modules</a>
+- <a href="#v1-6-2">v1\.6\.2</a>
- <a href="#release-summary-49">Release Summary</a>
- - <a href="#minor-changes-22">Minor Changes</a>
- - <a href="#deprecated-features-6">Deprecated Features</a>
- <a href="#bugfixes-36">Bugfixes</a>
-- <a href="#v1-5-0">v1\.5\.0</a>
+- <a href="#v1-6-1">v1\.6\.1</a>
- <a href="#release-summary-50">Release Summary</a>
- - <a href="#minor-changes-23">Minor Changes</a>
- - <a href="#deprecated-features-7">Deprecated Features</a>
- <a href="#bugfixes-37">Bugfixes</a>
-- <a href="#v1-4-0">v1\.4\.0</a>
+- <a href="#v1-6-0">v1\.6\.0</a>
- <a href="#release-summary-51">Release Summary</a>
- - <a href="#minor-changes-24">Minor Changes</a>
+ - <a href="#minor-changes-23">Minor Changes</a>
+ - <a href="#deprecated-features-7">Deprecated Features</a>
- <a href="#bugfixes-38">Bugfixes</a>
-- <a href="#v1-3-0">v1\.3\.0</a>
+- <a href="#v1-5-0">v1\.5\.0</a>
- <a href="#release-summary-52">Release Summary</a>
- - <a href="#minor-changes-25">Minor Changes</a>
+ - <a href="#minor-changes-24">Minor Changes</a>
+ - <a href="#deprecated-features-8">Deprecated Features</a>
- <a href="#bugfixes-39">Bugfixes</a>
- - <a href="#new-modules-3">New Modules</a>
-- <a href="#v1-2-0">v1\.2\.0</a>
+- <a href="#v1-4-0">v1\.4\.0</a>
- <a href="#release-summary-53">Release Summary</a>
- - <a href="#minor-changes-26">Minor Changes</a>
- - <a href="#security-fixes">Security Fixes</a>
+ - <a href="#minor-changes-25">Minor Changes</a>
- <a href="#bugfixes-40">Bugfixes</a>
-- <a href="#v1-1-1">v1\.1\.1</a>
+- <a href="#v1-3-0">v1\.3\.0</a>
- <a href="#release-summary-54">Release Summary</a>
+ - <a href="#minor-changes-26">Minor Changes</a>
- <a href="#bugfixes-41">Bugfixes</a>
-- <a href="#v1-1-0">v1\.1\.0</a>
+ - <a href="#new-modules-4">New Modules</a>
+- <a href="#v1-2-0">v1\.2\.0</a>
- <a href="#release-summary-55">Release Summary</a>
- <a href="#minor-changes-27">Minor Changes</a>
+ - <a href="#security-fixes">Security Fixes</a>
- <a href="#bugfixes-42">Bugfixes</a>
- - <a href="#new-modules-4">New Modules</a>
-- <a href="#v1-0-0">v1\.0\.0</a>
+- <a href="#v1-1-1">v1\.1\.1</a>
- <a href="#release-summary-56">Release Summary</a>
- - <a href="#minor-changes-28">Minor Changes</a>
- - <a href="#deprecated-features-8">Deprecated Features</a>
- - <a href="#removed-features-previously-deprecated-1">Removed Features \(previously deprecated\)</a>
- <a href="#bugfixes-43">Bugfixes</a>
+- <a href="#v1-1-0">v1\.1\.0</a>
+ - <a href="#release-summary-57">Release Summary</a>
+ - <a href="#minor-changes-28">Minor Changes</a>
+ - <a href="#bugfixes-44">Bugfixes</a>
- <a href="#new-modules-5">New Modules</a>
+- <a href="#v1-0-0">v1\.0\.0</a>
+ - <a href="#release-summary-58">Release Summary</a>
+ - <a href="#minor-changes-29">Minor Changes</a>
+ - <a href="#deprecated-features-9">Deprecated Features</a>
+ - <a href="#removed-features-previously-deprecated-1">Removed Features \(previously deprecated\)</a>
+ - <a href="#bugfixes-45">Bugfixes</a>
+ - <a href="#new-modules-6">New Modules</a>
+
+<a id="v2-20-0"></a>
+## v2\.20\.0
+
+<a id="release-summary"></a>
+### 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\.
+
+<a id="minor-changes"></a>
+### Minor Changes
+
+* acme\_certificate \- add <code>include\_renewal\_cert\_id</code> 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](https\://github\.com/ansible\-collections/community\.crypto/pull/739)\)\.
+
+<a id="deprecated-features"></a>
+### Deprecated Features
+
+* acme documentation fragment \- the default <code>community\.crypto\.acme\[\.documentation\]</code> docs fragment is deprecated and will be removed from community\.crypto 3\.0\.0\. Replace it with both the new <code>community\.crypto\.acme\.basic</code> and <code>community\.crypto\.acme\.account</code> fragments \([https\://github\.com/ansible\-collections/community\.crypto/pull/735](https\://github\.com/ansible\-collections/community\.crypto/pull/735)\)\.
+* acme\.backends module utils \- the <code>get\_cert\_information\(\)</code> 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](https\://github\.com/ansible\-collections/community\.crypto/pull/736)\)\.
+* crypto\.module\_backends\.common module utils \- the <code>crypto\.module\_backends\.common</code> module utils is deprecated and will be removed from community\.crypto 3\.0\.0\. Use the improved <code>argspec</code> module util instead \([https\://github\.com/ansible\-collections/community\.crypto/pull/749](https\://github\.com/ansible\-collections/community\.crypto/pull/749)\)\.
+
+<a id="bugfixes"></a>
+### 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](https\://github\.com/ansible\-collections/community\.crypto/pull/745)\)\.
+
+<a id="new-modules"></a>
+### 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\.
+
+<a id="v2-19-1"></a>
+## v2\.19\.1
+
+<a id="release-summary-1"></a>
+### Release Summary
+
+Bugfix release\.
+
+<a id="bugfixes-1"></a>
+### Bugfixes
+
+* crypto\.math module utils \- change return values for <code>quick\_is\_not\_prime\(\)</code> and <code>convert\_int\_to\_bytes\(0\, 0\)</code> for special cases that do not appear when using the collection \([https\://github\.com/ansible\-collections/community\.crypto/pull/733](https\://github\.com/ansible\-collections/community\.crypto/pull/733)\)\.
+* ecs\_certificate \- fixed <code>csr</code> 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](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 <code>not\_before</code> and <code>not\_after</code> times\. This is now fixed \([https\://github\.com/ansible\-collections/community\.crypto/issues/753](https\://github\.com/ansible\-collections/community\.crypto/issues/753)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/754](https\://github\.com/ansible\-collections/community\.crypto/pull/754)\)\.
<a id="v2-19-0"></a>
## v2\.19\.0
-<a id="release-summary"></a>
+<a id="release-summary-2"></a>
### Release Summary
Bugfix and feature release\.
-<a id="minor-changes"></a>
+<a id="minor-changes-1"></a>
### Minor Changes
* When using cryptography \>\= 42\.0\.0\, use offset\-aware <code>datetime\.datetime</code> 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/issues/726)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/727](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](https\://github\.com/ansible\-collections/community\.crypto/pull/727)\)\.
-<a id="deprecated-features"></a>
+<a id="deprecated-features-1"></a>
### Deprecated Features
* acme\.backends module utils \- from community\.crypto on\, all implementations of <code>CryptoBackend</code> must override <code>get\_ordered\_csr\_identifiers\(\)</code>\. The current default implementation\, which simply sorts the result of <code>get\_csr\_identifiers\(\)</code>\, will then be removed \([https\://github\.com/ansible\-collections/community\.crypto/pull/725](https\://github\.com/ansible\-collections/community\.crypto/pull/725)\)\.
-<a id="bugfixes"></a>
+<a id="bugfixes-2"></a>
### 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/issues/723)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/725](https\://github\.com/ansible\-collections/community\.crypto/pull/725)\)\.
-<a id="new-modules"></a>
+<a id="new-modules-1"></a>
### New Modules
-* x509\_certificate\_convert \- Convert X\.509 certificates
+* community\.crypto\.x509\_certificate\_convert \- Convert X\.509 certificates
<a id="v2-18-0"></a>
## v2\.18\.0
-<a id="release-summary-1"></a>
+<a id="release-summary-3"></a>
### Release Summary
Bugfix and feature release\.
-<a id="minor-changes-1"></a>
+<a id="minor-changes-2"></a>
### Minor Changes
* x509\_crl \- the new option <code>serial\_numbers</code> allow to configure in which format serial numbers can be provided to <code>revoked\_certificates\[\]\.serial\_number</code>\. The default is as integers \(<code>serial\_numbers\=integer</code>\) for backwards compatibility\; setting <code>serial\_numbers\=hex\-octets</code> allows to specify colon\-separated hex octet strings like <code>00\:11\:22\:FF</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/687](https\://github\.com/ansible\-collections/community\.crypto/issues/687)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/715](https\://github\.com/ansible\-collections/community\.crypto/pull/715)\)\.
-<a id="deprecated-features-1"></a>
+<a id="deprecated-features-2"></a>
### 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 <code>\_pipe</code>\: if the object needs to be \(re\-\)generated\, only the <code>changed</code> 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 <code>check\_mode\: false</code> 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/issues/712)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/714](https\://github\.com/ansible\-collections/community\.crypto/pull/714)\)\.
-<a id="bugfixes-1"></a>
+<a id="bugfixes-3"></a>
### Bugfixes
* luks\_device \- fixed module a bug that prevented using <code>remove\_keyslot</code> with the value <code>0</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/710](https\://github\.com/ansible\-collections/community\.crypto/pull/710)\)\.
@@ -277,18 +336,18 @@ Bugfix and feature release\.
<a id="filter"></a>
#### Filter
-* parse\_serial \- Convert a serial number as a colon\-separated list of hex numbers to an integer
-* to\_serial \- Convert an integer to a colon\-separated list of hex numbers
+* 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
<a id="v2-17-1"></a>
## v2\.17\.1
-<a id="release-summary-2"></a>
+<a id="release-summary-4"></a>
### Release Summary
Bugfix release for compatibility with cryptography 42\.0\.0\.
-<a id="bugfixes-2"></a>
+<a id="bugfixes-4"></a>
### Bugfixes
* openssl\_dhparam \- was using an internal function instead of the public API to load DH param files when using the <code>cryptography</code> 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](https\://github\.com/ansible\-collections/community\.crypto/pull/698)\)\.
@@ -298,12 +357,12 @@ Bugfix release for compatibility with cryptography 42\.0\.0\.
<a id="v2-17-0"></a>
## v2\.17\.0
-<a id="release-summary-3"></a>
+<a id="release-summary-5"></a>
### Release Summary
Feature release\.
-<a id="minor-changes-2"></a>
+<a id="minor-changes-3"></a>
### Minor Changes
* luks\_device \- add allow discards option \([https\://github\.com/ansible\-collections/community\.crypto/pull/693](https\://github\.com/ansible\-collections/community\.crypto/pull/693)\)\.
@@ -311,12 +370,12 @@ Feature release\.
<a id="v2-16-2"></a>
## v2\.16\.2
-<a id="release-summary-4"></a>
+<a id="release-summary-6"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-3"></a>
+<a id="bugfixes-5"></a>
### Bugfixes
* acme\_\* modules \- directly react on bad return data for account creation/retrieval/updating requests \([https\://github\.com/ansible\-collections/community\.crypto/pull/682](https\://github\.com/ansible\-collections/community\.crypto/pull/682)\)\.
@@ -327,12 +386,12 @@ Bugfix release\.
<a id="v2-16-1"></a>
## v2\.16\.1
-<a id="release-summary-5"></a>
+<a id="release-summary-7"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-4"></a>
+<a id="bugfixes-6"></a>
### 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](https\://github\.com/ansible\-collections/community\.crypto/issues/680)\)\.
@@ -340,17 +399,17 @@ Bugfix release\.
<a id="v2-16-0"></a>
## v2\.16\.0
-<a id="release-summary-6"></a>
+<a id="release-summary-8"></a>
### Release Summary
Bugfix release\.
-<a id="minor-changes-3"></a>
+<a id="minor-changes-4"></a>
### Minor Changes
* luks\_devices \- add new options <code>keyslot</code>\, <code>new\_keyslot</code>\, and <code>remove\_keyslot</code> to allow adding/removing keys to/from specific keyslots \([https\://github\.com/ansible\-collections/community\.crypto/pull/664](https\://github\.com/ansible\-collections/community\.crypto/pull/664)\)\.
-<a id="bugfixes-5"></a>
+<a id="bugfixes-7"></a>
### 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](https\://github\.com/ansible\-collections/community\.crypto/pull/666)\)\.
@@ -358,12 +417,12 @@ Bugfix release\.
<a id="v2-15-1"></a>
## v2\.15\.1
-<a id="release-summary-7"></a>
+<a id="release-summary-9"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-6"></a>
+<a id="bugfixes-8"></a>
### Bugfixes
* acme\_\* modules \- correctly handle error documents without <code>type</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/651](https\://github\.com/ansible\-collections/community\.crypto/issues/651)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/652](https\://github\.com/ansible\-collections/community\.crypto/pull/652)\)\.
@@ -371,22 +430,22 @@ Bugfix release\.
<a id="v2-15-0"></a>
## v2\.15\.0
-<a id="release-summary-8"></a>
+<a id="release-summary-10"></a>
### Release Summary
Bugfix and feature release\.
-<a id="minor-changes-4"></a>
+<a id="minor-changes-5"></a>
### Minor Changes
* openssh\_keypair \- fail when comment cannot be updated \([https\://github\.com/ansible\-collections/community\.crypto/pull/646](https\://github\.com/ansible\-collections/community\.crypto/pull/646)\)\.
-<a id="deprecated-features-2"></a>
+<a id="deprecated-features-3"></a>
### Deprecated Features
* get\_certificate \- the default <code>false</code> of the <code>asn1\_base64</code> option is deprecated and will change to <code>true</code> in community\.crypto 3\.0\.0 \([https\://github\.com/ansible\-collections/community\.crypto/pull/600](https\://github\.com/ansible\-collections/community\.crypto/pull/600)\)\.
-<a id="bugfixes-7"></a>
+<a id="bugfixes-9"></a>
### Bugfixes
* openssh\_cert\, openssh\_keypair \- the modules ignored return codes of <code>ssh</code> and <code>ssh\-keygen</code> in some cases \([https\://github\.com/ansible\-collections/community\.crypto/issues/645](https\://github\.com/ansible\-collections/community\.crypto/issues/645)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/646](https\://github\.com/ansible\-collections/community\.crypto/pull/646)\)\.
@@ -398,17 +457,17 @@ Bugfix and feature release\.
<a id="filter-1"></a>
#### Filter
-* gpg\_fingerprint \- Retrieve a GPG fingerprint from a GPG public or private key
+* community\.crypto\.gpg\_fingerprint \- Retrieve a GPG fingerprint from a GPG public or private key
<a id="lookup"></a>
#### Lookup
-* gpg\_fingerprint \- Retrieve a GPG fingerprint from a GPG public or private key file
+* community\.crypto\.gpg\_fingerprint \- Retrieve a GPG fingerprint from a GPG public or private key file
<a id="v2-14-1"></a>
## v2\.14\.1
-<a id="release-summary-9"></a>
+<a id="release-summary-11"></a>
### Release Summary
Bugfix and maintenance release with updated documentation\.
@@ -421,7 +480,7 @@ 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\.
-<a id="bugfixes-8"></a>
+<a id="bugfixes-10"></a>
### Bugfixes
* Fix PEM detection/identification to also accept random other lines before the line starting with <code>\-\-\-\-\-BEGIN</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/627](https\://github\.com/ansible\-collections/community\.crypto/issues/627)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/628](https\://github\.com/ansible\-collections/community\.crypto/pull/628)\)\.
@@ -434,12 +493,12 @@ for the rendered HTML version of the documentation of the latest release\.
<a id="v2-14-0"></a>
## v2\.14\.0
-<a id="release-summary-10"></a>
+<a id="release-summary-12"></a>
### Release Summary
Feature release\.
-<a id="minor-changes-5"></a>
+<a id="minor-changes-6"></a>
### Minor Changes
* acme\_certificate \- allow to use no challenge by providing <code>no challenge</code> for the <code>challenge</code> 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/issues/613)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/615](https\://github\.com/ansible\-collections/community\.crypto/pull/615)\)\.
@@ -449,12 +508,12 @@ Feature release\.
<a id="v2-13-1"></a>
## v2\.13\.1
-<a id="release-summary-11"></a>
+<a id="release-summary-13"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-9"></a>
+<a id="bugfixes-11"></a>
### Bugfixes
* execution environment definition \- fix installation of <code>python3\-pyOpenSSL</code> package on CentOS and RHEL \([https\://github\.com/ansible\-collections/community\.crypto/pull/606](https\://github\.com/ansible\-collections/community\.crypto/pull/606)\)\.
@@ -463,22 +522,22 @@ Bugfix release\.
<a id="v2-13-0"></a>
## v2\.13\.0
-<a id="release-summary-12"></a>
+<a id="release-summary-14"></a>
### Release Summary
Bugfix and maintenance release\.
-<a id="minor-changes-6"></a>
+<a id="minor-changes-7"></a>
### Minor Changes
* x509\_crl \- the <code>crl\_mode</code> option has been added to replace the existing <code>mode</code> option \([https\://github\.com/ansible\-collections/community\.crypto/issues/596](https\://github\.com/ansible\-collections/community\.crypto/issues/596)\)\.
-<a id="deprecated-features-3"></a>
+<a id="deprecated-features-4"></a>
### Deprecated Features
* x509\_crl \- the <code>mode</code> option is deprecated\; use <code>crl\_mode</code> instead\. The <code>mode</code> 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](https\://github\.com/ansible\-collections/community\.crypto/issues/596)\)\.
-<a id="bugfixes-10"></a>
+<a id="bugfixes-12"></a>
### Bugfixes
* openssh\_keypair \- always generate a new key pair if the private key does not exist\. Previously\, the module would fail when <code>regenerate\=fail</code> without an existing key\, contradicting the documentation \([https\://github\.com/ansible\-collections/community\.crypto/pull/598](https\://github\.com/ansible\-collections/community\.crypto/pull/598)\)\.
@@ -487,12 +546,12 @@ Bugfix and maintenance release\.
<a id="v2-12-0"></a>
## v2\.12\.0
-<a id="release-summary-13"></a>
+<a id="release-summary-15"></a>
### Release Summary
Feature release\.
-<a id="minor-changes-7"></a>
+<a id="minor-changes-8"></a>
### Minor Changes
* get\_certificate \- add <code>asn1\_base64</code> option to control whether the ASN\.1 included in the <code>extensions</code> return value is binary data or Base64 encoded \([https\://github\.com/ansible\-collections/community\.crypto/pull/592](https\://github\.com/ansible\-collections/community\.crypto/pull/592)\)\.
@@ -500,7 +559,7 @@ Feature release\.
<a id="v2-11-1"></a>
## v2\.11\.1
-<a id="release-summary-14"></a>
+<a id="release-summary-16"></a>
### Release Summary
Maintenance release with improved documentation\.
@@ -508,17 +567,17 @@ Maintenance release with improved documentation\.
<a id="v2-11-0"></a>
## v2\.11\.0
-<a id="release-summary-15"></a>
+<a id="release-summary-17"></a>
### Release Summary
Feature and bugfix release\.
-<a id="minor-changes-8"></a>
+<a id="minor-changes-9"></a>
### Minor Changes
* get\_certificate \- adds <code>ciphers</code> option for custom cipher selection \([https\://github\.com/ansible\-collections/community\.crypto/pull/571](https\://github\.com/ansible\-collections/community\.crypto/pull/571)\)\.
-<a id="bugfixes-11"></a>
+<a id="bugfixes-13"></a>
### Bugfixes
* action plugin helper \- fix handling of deprecations for ansible\-core 2\.14\.2 \([https\://github\.com/ansible\-collections/community\.crypto/pull/572](https\://github\.com/ansible\-collections/community\.crypto/pull/572)\)\.
@@ -528,12 +587,12 @@ Feature and bugfix release\.
<a id="v2-10-0"></a>
## v2\.10\.0
-<a id="release-summary-16"></a>
+<a id="release-summary-18"></a>
### Release Summary
Bugfix and feature release\.
-<a id="bugfixes-12"></a>
+<a id="bugfixes-14"></a>
### Bugfixes
* openssl\_csr\, openssl\_csr\_pipe \- prevent invalid values for <code>crl\_distribution\_points</code> that do not have one of <code>full\_name</code>\, <code>relative\_name</code>\, and <code>crl\_issuer</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/560](https\://github\.com/ansible\-collections/community\.crypto/pull/560)\)\.
@@ -545,22 +604,22 @@ Bugfix and feature release\.
<a id="filter-2"></a>
#### Filter
-* openssl\_csr\_info \- Retrieve information from OpenSSL Certificate Signing Requests \(CSR\)
-* openssl\_privatekey\_info \- Retrieve information from OpenSSL private keys
-* openssl\_publickey\_info \- Retrieve information from OpenSSL public keys in PEM format
-* split\_pem \- Split PEM file contents into multiple objects
-* x509\_certificate\_info \- Retrieve information from X\.509 certificates in PEM format
-* x509\_crl\_info \- Retrieve information from X\.509 CRLs in PEM format
+* 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
<a id="v2-9-0"></a>
## v2\.9\.0
-<a id="release-summary-17"></a>
+<a id="release-summary-19"></a>
### Release Summary
Regular feature release\.
-<a id="minor-changes-9"></a>
+<a id="minor-changes-10"></a>
### Minor Changes
* x509\_certificate\_info \- adds <code>issuer\_uri</code> field in return value based on Authority Information Access data \([https\://github\.com/ansible\-collections/community\.crypto/pull/530](https\://github\.com/ansible\-collections/community\.crypto/pull/530)\)\.
@@ -568,7 +627,7 @@ Regular feature release\.
<a id="v2-8-1"></a>
## v2\.8\.1
-<a id="release-summary-18"></a>
+<a id="release-summary-20"></a>
### Release Summary
Maintenance release with improved documentation\.
@@ -576,12 +635,12 @@ Maintenance release with improved documentation\.
<a id="v2-8-0"></a>
## v2\.8\.0
-<a id="release-summary-19"></a>
+<a id="release-summary-21"></a>
### Release Summary
Feature release\.
-<a id="minor-changes-10"></a>
+<a id="minor-changes-11"></a>
### 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](https\://github\.com/ansible\-collections/community\.crypto/pull/525)\)\.
@@ -591,12 +650,12 @@ Feature release\.
<a id="v2-7-1"></a>
## v2\.7\.1
-<a id="release-summary-20"></a>
+<a id="release-summary-22"></a>
### Release Summary
Maintenance release\.
-<a id="bugfixes-13"></a>
+<a id="bugfixes-15"></a>
### Bugfixes
* acme\_\* modules \- improve feedback when importing <code>cryptography</code> does not work \([https\://github\.com/ansible\-collections/community\.crypto/issues/518](https\://github\.com/ansible\-collections/community\.crypto/issues/518)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/519](https\://github\.com/ansible\-collections/community\.crypto/pull/519)\)\.
@@ -604,17 +663,17 @@ Maintenance release\.
<a id="v2-7-0"></a>
## v2\.7\.0
-<a id="release-summary-21"></a>
+<a id="release-summary-23"></a>
### Release Summary
Feature release\.
-<a id="minor-changes-11"></a>
+<a id="minor-changes-12"></a>
### 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](https\://github\.com/ansible\-collections/community\.crypto/pull/513)\)\.
-<a id="bugfixes-14"></a>
+<a id="bugfixes-16"></a>
### Bugfixes
* openssl\_privatekey\_pipe \- ensure compatibility with newer versions of ansible\-core \([https\://github\.com/ansible\-collections/community\.crypto/pull/515](https\://github\.com/ansible\-collections/community\.crypto/pull/515)\)\.
@@ -622,12 +681,12 @@ Feature release\.
<a id="v2-6-0"></a>
## v2\.6\.0
-<a id="release-summary-22"></a>
+<a id="release-summary-24"></a>
### Release Summary
Feature release\.
-<a id="minor-changes-12"></a>
+<a id="minor-changes-13"></a>
### Minor Changes
* acme\* modules \- support the HTTP 429 Too Many Requests response status \([https\://github\.com/ansible\-collections/community\.crypto/pull/508](https\://github\.com/ansible\-collections/community\.crypto/pull/508)\)\.
@@ -636,12 +695,12 @@ Feature release\.
<a id="v2-5-0"></a>
## v2\.5\.0
-<a id="release-summary-23"></a>
+<a id="release-summary-25"></a>
### Release Summary
Maintenance release with improved licensing declaration and documentation fixes\.
-<a id="minor-changes-13"></a>
+<a id="minor-changes-14"></a>
### Minor Changes
* All software licenses are now in the <code>LICENSES/</code> directory of the collection root\. Moreover\, <code>SPDX\-License\-Identifier\:</code> is used to declare the applicable license for every file that is not automatically generated \([https\://github\.com/ansible\-collections/community\.crypto/pull/491](https\://github\.com/ansible\-collections/community\.crypto/pull/491)\)\.
@@ -649,17 +708,17 @@ Maintenance release with improved licensing declaration and documentation fixes\
<a id="v2-4-0"></a>
## v2\.4\.0
-<a id="release-summary-24"></a>
+<a id="release-summary-26"></a>
### Release Summary
Deprecation and bugfix release\. No new features this time\.
-<a id="deprecated-features-4"></a>
+<a id="deprecated-features-5"></a>
### 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](https\://github\.com/ansible\-collections/community\.crypto/pull/460)\)\.
-<a id="bugfixes-15"></a>
+<a id="bugfixes-17"></a>
### 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/issues/486)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/487](https\://github\.com/ansible\-collections/community\.crypto/pull/487)\)\.
@@ -667,7 +726,7 @@ Deprecation and bugfix release\. No new features this time\.
<a id="v2-3-4"></a>
## v2\.3\.4
-<a id="release-summary-25"></a>
+<a id="release-summary-27"></a>
### Release Summary
Re\-release of what was intended to be 2\.3\.3\.
@@ -683,12 +742,12 @@ has been added\.
<a id="v2-3-3"></a>
## v2\.3\.3
-<a id="release-summary-26"></a>
+<a id="release-summary-28"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-16"></a>
+<a id="bugfixes-18"></a>
### Bugfixes
* Include <code>Apache\-2\.0\.txt</code> file for <code>plugins/module\_utils/crypto/\_obj2txt\.py</code> and <code>plugins/module\_utils/crypto/\_objects\_data\.py</code>\.
@@ -698,12 +757,12 @@ Bugfix release\.
<a id="v2-3-2"></a>
## v2\.3\.2
-<a id="release-summary-27"></a>
+<a id="release-summary-29"></a>
### Release Summary
Maintenance and bugfix release\.
-<a id="bugfixes-17"></a>
+<a id="bugfixes-19"></a>
### Bugfixes
* Include <code>simplified\_bsd\.txt</code> license file for the ECS module utils\.
@@ -712,12 +771,12 @@ Maintenance and bugfix release\.
<a id="v2-3-1"></a>
## v2\.3\.1
-<a id="release-summary-28"></a>
+<a id="release-summary-30"></a>
### Release Summary
Maintenance release\.
-<a id="bugfixes-18"></a>
+<a id="bugfixes-20"></a>
### Bugfixes
* Include <code>PSF\-license\.txt</code> file for <code>plugins/module\_utils/\_version\.py</code>\.
@@ -725,12 +784,12 @@ Maintenance release\.
<a id="v2-3-0"></a>
## v2\.3\.0
-<a id="release-summary-29"></a>
+<a id="release-summary-31"></a>
### Release Summary
Feature and bugfix release\.
-<a id="minor-changes-14"></a>
+<a id="minor-changes-15"></a>
### 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 <code>cryptography \>\= 37\.0\.0</code> in your Execution Environment\'s Python dependencies file \([https\://github\.com/ansible\-collections/community\.crypto/pull/440](https\://github\.com/ansible\-collections/community\.crypto/pull/440)\)\.
@@ -744,7 +803,7 @@ Feature and bugfix release\.
* x509\_crl \- add <code>name\_encoding</code> option to control the encoding \(IDNA\, Unicode\) used to return domain names in general names \([https\://github\.com/ansible\-collections/community\.crypto/pull/436](https\://github\.com/ansible\-collections/community\.crypto/pull/436)\)\.
* x509\_crl\_info \- add <code>name\_encoding</code> option to control the encoding \(IDNA\, Unicode\) used to return domain names in general names \([https\://github\.com/ansible\-collections/community\.crypto/pull/436](https\://github\.com/ansible\-collections/community\.crypto/pull/436)\)\.
-<a id="bugfixes-19"></a>
+<a id="bugfixes-21"></a>
### Bugfixes
* Make collection more robust when PyOpenSSL is used with an incompatible cryptography version \([https\://github\.com/ansible\-collections/community\.crypto/pull/445](https\://github\.com/ansible\-collections/community\.crypto/pull/445)\)\.
@@ -753,12 +812,12 @@ Feature and bugfix release\.
<a id="v2-2-4"></a>
## v2\.2\.4
-<a id="release-summary-30"></a>
+<a id="release-summary-32"></a>
### Release Summary
Regular maintenance release\.
-<a id="bugfixes-20"></a>
+<a id="bugfixes-22"></a>
### Bugfixes
* openssh\_\* modules \- fix exception handling to report traceback to users for enhanced traceability \([https\://github\.com/ansible\-collections/community\.crypto/pull/417](https\://github\.com/ansible\-collections/community\.crypto/pull/417)\)\.
@@ -766,12 +825,12 @@ Regular maintenance release\.
<a id="v2-2-3"></a>
## v2\.2\.3
-<a id="release-summary-31"></a>
+<a id="release-summary-33"></a>
### Release Summary
Regular bugfix release\.
-<a id="bugfixes-21"></a>
+<a id="bugfixes-23"></a>
### Bugfixes
* luks\_device \- fix parsing of <code>lsblk</code> output when device name ends with <code>crypt</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/409](https\://github\.com/ansible\-collections/community\.crypto/issues/409)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/410](https\://github\.com/ansible\-collections/community\.crypto/pull/410)\)\.
@@ -779,14 +838,14 @@ Regular bugfix release\.
<a id="v2-2-2"></a>
## v2\.2\.2
-<a id="release-summary-32"></a>
+<a id="release-summary-34"></a>
### 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\.
-<a id="bugfixes-22"></a>
+<a id="bugfixes-24"></a>
### 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/issues/399)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/403](https\://github\.com/ansible\-collections/community\.crypto/pull/403)\)\.
@@ -798,12 +857,12 @@ In this release\, we extended the test matrix to include Alpine 3\, ArchLinux\,
<a id="v2-2-1"></a>
## v2\.2\.1
-<a id="release-summary-33"></a>
+<a id="release-summary-35"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-23"></a>
+<a id="bugfixes-25"></a>
### Bugfixes
* openssh\_cert \- fixed false <code>changed</code> status for <code>host</code> certificates when using <code>full\_idempotence</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/395](https\://github\.com/ansible\-collections/community\.crypto/issues/395)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/396](https\://github\.com/ansible\-collections/community\.crypto/pull/396)\)\.
@@ -811,17 +870,17 @@ Bugfix release\.
<a id="v2-2-0"></a>
## v2\.2\.0
-<a id="release-summary-34"></a>
+<a id="release-summary-36"></a>
### Release Summary
Regular bugfix and feature release\.
-<a id="minor-changes-15"></a>
+<a id="minor-changes-16"></a>
### Minor Changes
* openssh\_cert \- added <code>ignore\_timestamps</code> parameter so it can be used semi\-idempotent with relative timestamps in <code>valid\_to</code>/<code>valid\_from</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/379](https\://github\.com/ansible\-collections/community\.crypto/issues/379)\)\.
-<a id="bugfixes-24"></a>
+<a id="bugfixes-26"></a>
### Bugfixes
* luks\_devices \- set <code>LANG</code> 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/pull/388)\, [https\://github\.com/ansible\-collections/community\.crypto/issues/385](https\://github\.com/ansible\-collections/community\.crypto/issues/385)\)\.
@@ -829,33 +888,33 @@ Regular bugfix and feature release\.
<a id="v2-1-0"></a>
## v2\.1\.0
-<a id="release-summary-35"></a>
+<a id="release-summary-37"></a>
### Release Summary
Feature and bugfix release\.
-<a id="minor-changes-16"></a>
+<a id="minor-changes-17"></a>
### Minor Changes
* Adjust error messages that indicate <code>cryptography</code> is not installed from <code>Can\'t</code> to <code>Cannot</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/374](https\://github\.com/ansible\-collections/community\.crypto/pull/374)\)\.
-<a id="bugfixes-25"></a>
+<a id="bugfixes-27"></a>
### Bugfixes
* Various modules and plugins \- use vendored version of <code>distutils\.version</code> instead of the deprecated Python standard library <code>distutils</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/353](https\://github\.com/ansible\-collections/community\.crypto/pull/353)\)\.
* certificate\_complete\_chain \- do not append root twice if the chain already ends with a root certificate \([https\://github\.com/ansible\-collections/community\.crypto/pull/360](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/issues/355)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/360](https\://github\.com/ansible\-collections/community\.crypto/pull/360)\)\.
-<a id="new-modules-1"></a>
+<a id="new-modules-2"></a>
### New Modules
-* crypto\_info \- Retrieve cryptographic capabilities
-* openssl\_privatekey\_convert \- Convert OpenSSL private keys
+* community\.crypto\.crypto\_info \- Retrieve cryptographic capabilities
+* community\.crypto\.openssl\_privatekey\_convert \- Convert OpenSSL private keys
<a id="v2-0-2"></a>
## v2\.0\.2
-<a id="release-summary-36"></a>
+<a id="release-summary-38"></a>
### Release Summary
Documentation fix release\. No actual code changes\.
@@ -863,17 +922,17 @@ Documentation fix release\. No actual code changes\.
<a id="v2-0-1"></a>
## v2\.0\.1
-<a id="release-summary-37"></a>
+<a id="release-summary-39"></a>
### Release Summary
Bugfix release with extra forward compatibility for newer versions of cryptography\.
-<a id="minor-changes-17"></a>
+<a id="minor-changes-18"></a>
### Minor Changes
* acme\_\* modules \- fix usage of <code>fetch\_url</code> with changes in latest ansible\-core <code>devel</code> branch \([https\://github\.com/ansible\-collections/community\.crypto/pull/339](https\://github\.com/ansible\-collections/community\.crypto/pull/339)\)\.
-<a id="bugfixes-26"></a>
+<a id="bugfixes-28"></a>
### Bugfixes
* acme\_certificate \- avoid passing multiple certificates to <code>cryptography</code>\'s X\.509 certificate loader when <code>fullchain\_dest</code> is used \([https\://github\.com/ansible\-collections/community\.crypto/pull/324](https\://github\.com/ansible\-collections/community\.crypto/pull/324)\)\.
@@ -884,12 +943,12 @@ Bugfix release with extra forward compatibility for newer versions of cryptograp
<a id="v2-0-0"></a>
## v2\.0\.0
-<a id="release-summary-38"></a>
+<a id="release-summary-40"></a>
### Release Summary
A new major release of the <code>community\.crypto</code> collection\. The main changes are removal of the PyOpenSSL backends for almost all modules \(<code>openssl\_pkcs12</code> being the only exception\)\, and removal of the <code>assertonly</code> provider in the <code>x509\_certificate</code> provider\. There are also some other breaking changes which should improve the user interface/experience of this collection long\-term\.
-<a id="minor-changes-18"></a>
+<a id="minor-changes-19"></a>
### Minor Changes
* acme\_certificate \- the <code>subject</code> and <code>issuer</code> fields in in the <code>select\_chain</code> entries are now more strictly validated \([https\://github\.com/ansible\-collections/community\.crypto/pull/316](https\://github\.com/ansible\-collections/community\.crypto/pull/316)\)\.
@@ -914,7 +973,7 @@ A new major release of the <code>community\.crypto</code> collection\. The main
* openssl\_privatekey\_info \- by default consistency checks are not run\; they need to be explicitly requested by passing <code>check\_consistency\=true</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/309](https\://github\.com/ansible\-collections/community\.crypto/pull/309)\)\.
* x509\_crl \- for idempotency checks\, the <code>issuer</code> order is ignored\. If order is important\, use the new <code>issuer\_ordered</code> option \([https\://github\.com/ansible\-collections/community\.crypto/pull/316](https\://github\.com/ansible\-collections/community\.crypto/pull/316)\)\.
-<a id="deprecated-features-5"></a>
+<a id="deprecated-features-6"></a>
### 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](https\://github\.com/ansible\-collections/community\.crypto/pull/288)\)\.
@@ -946,7 +1005,7 @@ A new major release of the <code>community\.crypto</code> collection\. The main
* x509\_certificate\_info \- removed the <code>pyopenssl</code> backend \([https\://github\.com/ansible\-collections/community\.crypto/pull/273](https\://github\.com/ansible\-collections/community\.crypto/pull/273)\)\.
* x509\_certificate\_pipe \- removed the <code>pyopenssl</code> backend \([https\://github\.com/ansible\-collections/community\.crypto/pull/273](https\://github\.com/ansible\-collections/community\.crypto/pull/273)\)\.
-<a id="bugfixes-27"></a>
+<a id="bugfixes-29"></a>
### Bugfixes
* cryptography backend \- improve Unicode handling for Python 2 \([https\://github\.com/ansible\-collections/community\.crypto/pull/313](https\://github\.com/ansible\-collections/community\.crypto/pull/313)\)\.
@@ -958,12 +1017,12 @@ A new major release of the <code>community\.crypto</code> collection\. The main
<a id="v1-9-4"></a>
## v1\.9\.4
-<a id="release-summary-39"></a>
+<a id="release-summary-41"></a>
### Release Summary
Regular bugfix release\.
-<a id="bugfixes-28"></a>
+<a id="bugfixes-30"></a>
### Bugfixes
* acme\_\* modules \- fix commands composed for OpenSSL backend to retrieve information on CSRs and certificates from stdin to use <code>/dev/stdin</code> instead of <code>\-</code>\. This is needed for OpenSSL 1\.0\.1 and 1\.0\.2\, apparently \([https\://github\.com/ansible\-collections/community\.crypto/pull/279](https\://github\.com/ansible\-collections/community\.crypto/pull/279)\)\.
@@ -972,12 +1031,12 @@ Regular bugfix release\.
<a id="v1-9-3"></a>
## v1\.9\.3
-<a id="release-summary-40"></a>
+<a id="release-summary-42"></a>
### Release Summary
Regular bugfix release\.
-<a id="bugfixes-29"></a>
+<a id="bugfixes-31"></a>
### Bugfixes
* openssl\_csr and openssl\_csr\_pipe \- make sure that Unicode strings are used to compare strings with the cryptography backend\. This fixes idempotency problems with non\-ASCII letters on Python 2 \([https\://github\.com/ansible\-collections/community\.crypto/issues/270](https\://github\.com/ansible\-collections/community\.crypto/issues/270)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/271](https\://github\.com/ansible\-collections/community\.crypto/pull/271)\)\.
@@ -985,7 +1044,7 @@ Regular bugfix release\.
<a id="v1-9-2"></a>
## v1\.9\.2
-<a id="release-summary-41"></a>
+<a id="release-summary-43"></a>
### Release Summary
Bugfix release to fix the changelog\. No other change compared to 1\.9\.0\.
@@ -993,7 +1052,7 @@ Bugfix release to fix the changelog\. No other change compared to 1\.9\.0\.
<a id="v1-9-1"></a>
## v1\.9\.1
-<a id="release-summary-42"></a>
+<a id="release-summary-44"></a>
### Release Summary
Accidental 1\.9\.1 release\. Identical to 1\.9\.0\.
@@ -1001,18 +1060,18 @@ Accidental 1\.9\.1 release\. Identical to 1\.9\.0\.
<a id="v1-9-0"></a>
## v1\.9\.0
-<a id="release-summary-43"></a>
+<a id="release-summary-45"></a>
### Release Summary
Regular feature release\.
-<a id="minor-changes-19"></a>
+<a id="minor-changes-20"></a>
### Minor Changes
* get\_certificate \- added <code>starttls</code> option to retrieve certificates from servers which require clients to request an encrypted connection \([https\://github\.com/ansible\-collections/community\.crypto/pull/264](https\://github\.com/ansible\-collections/community\.crypto/pull/264)\)\.
* openssh\_keypair \- added <code>diff</code> support \([https\://github\.com/ansible\-collections/community\.crypto/pull/260](https\://github\.com/ansible\-collections/community\.crypto/pull/260)\)\.
-<a id="bugfixes-30"></a>
+<a id="bugfixes-32"></a>
### Bugfixes
* keypair\_backend module utils \- simplify code to pass sanity tests \([https\://github\.com/ansible\-collections/community\.crypto/pull/263](https\://github\.com/ansible\-collections/community\.crypto/pull/263)\)\.
@@ -1023,12 +1082,12 @@ Regular feature release\.
<a id="v1-8-0"></a>
## v1\.8\.0
-<a id="release-summary-44"></a>
+<a id="release-summary-46"></a>
### Release Summary
Regular bugfix and feature release\.
-<a id="minor-changes-20"></a>
+<a id="minor-changes-21"></a>
### Minor Changes
* Avoid internal ansible\-core module\_utils in favor of equivalent public API available since at least Ansible 2\.9 \([https\://github\.com/ansible\-collections/community\.crypto/pull/253](https\://github\.com/ansible\-collections/community\.crypto/pull/253)\)\.
@@ -1036,7 +1095,7 @@ Regular bugfix and feature release\.
* openssh\_cert \- added <code>regenerate</code> option to validate additional certificate parameters which trigger regeneration of an existing certificate \([https\://github\.com/ansible\-collections/community\.crypto/pull/256](https\://github\.com/ansible\-collections/community\.crypto/pull/256)\)\.
* openssh\_cert \- adding <code>diff</code> support \([https\://github\.com/ansible\-collections/community\.crypto/pull/255](https\://github\.com/ansible\-collections/community\.crypto/pull/255)\)\.
-<a id="bugfixes-31"></a>
+<a id="bugfixes-33"></a>
### Bugfixes
* openssh\_cert \- fixed certificate generation to restore original certificate if an error is encountered \([https\://github\.com/ansible\-collections/community\.crypto/pull/255](https\://github\.com/ansible\-collections/community\.crypto/pull/255)\)\.
@@ -1045,12 +1104,12 @@ Regular bugfix and feature release\.
<a id="v1-7-1"></a>
## v1\.7\.1
-<a id="release-summary-45"></a>
+<a id="release-summary-47"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-32"></a>
+<a id="bugfixes-34"></a>
### Bugfixes
* openssl\_pkcs12 \- fix crash when loading passphrase\-protected PKCS\#12 files with <code>cryptography</code> backend \([https\://github\.com/ansible\-collections/community\.crypto/issues/247](https\://github\.com/ansible\-collections/community\.crypto/issues/247)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/248](https\://github\.com/ansible\-collections/community\.crypto/pull/248)\)\.
@@ -1058,12 +1117,12 @@ Bugfix release\.
<a id="v1-7-0"></a>
## v1\.7\.0
-<a id="release-summary-46"></a>
+<a id="release-summary-48"></a>
### Release Summary
Regular feature and bugfix release\.
-<a id="minor-changes-21"></a>
+<a id="minor-changes-22"></a>
### Minor Changes
* cryptography\_openssh module utils \- new module\_utils for managing asymmetric keypairs and OpenSSH formatted/encoded asymmetric keypairs \([https\://github\.com/ansible\-collections/community\.crypto/pull/213](https\://github\.com/ansible\-collections/community\.crypto/pull/213)\)\.
@@ -1086,27 +1145,27 @@ Regular feature and bugfix release\.
* x509\_crl\_info \- add <code>list\_revoked\_certificates</code> option to avoid enumerating all revoked certificates \([https\://github\.com/ansible\-collections/community\.crypto/pull/232](https\://github\.com/ansible\-collections/community\.crypto/pull/232)\)\.
* x509\_crl\_info \- refactor module to allow code reuse for diff mode \([https\://github\.com/ansible\-collections/community\.crypto/pull/203](https\://github\.com/ansible\-collections/community\.crypto/pull/203)\)\.
-<a id="bugfixes-33"></a>
+<a id="bugfixes-35"></a>
### Bugfixes
* openssh\_keypair \- fix <code>check\_mode</code> to populate return values for existing keypairs \([https\://github\.com/ansible\-collections/community\.crypto/issues/113](https\://github\.com/ansible\-collections/community\.crypto/issues/113)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/230](https\://github\.com/ansible\-collections/community\.crypto/pull/230)\)\.
* various modules \- prevent crashes when modules try to set attributes on not yet existing files in check mode\. This will be fixed in ansible\-core 2\.12\, but it is not backported to every Ansible version we support \([https\://github\.com/ansible\-collections/community\.crypto/issue/242](https\://github\.com/ansible\-collections/community\.crypto/issue/242)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/243](https\://github\.com/ansible\-collections/community\.crypto/pull/243)\)\.
* x509\_certificate \- fix crash when <code>assertonly</code> provider is used and some error conditions should be reported \([https\://github\.com/ansible\-collections/community\.crypto/issues/240](https\://github\.com/ansible\-collections/community\.crypto/issues/240)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/241](https\://github\.com/ansible\-collections/community\.crypto/pull/241)\)\.
-<a id="new-modules-2"></a>
+<a id="new-modules-3"></a>
### New Modules
-* openssl\_publickey\_info \- Provide information for OpenSSL public keys
+* community\.crypto\.openssl\_publickey\_info \- Provide information for OpenSSL public keys
<a id="v1-6-2"></a>
## v1\.6\.2
-<a id="release-summary-47"></a>
+<a id="release-summary-49"></a>
### Release Summary
Bugfix release\. Fixes compatibility issue of ACME modules with step\-ca\.
-<a id="bugfixes-34"></a>
+<a id="bugfixes-36"></a>
### Bugfixes
* acme\_\* modules \- avoid crashing for ACME servers where the <code>meta</code> directory key is not present \([https\://github\.com/ansible\-collections/community\.crypto/issues/220](https\://github\.com/ansible\-collections/community\.crypto/issues/220)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/221](https\://github\.com/ansible\-collections/community\.crypto/pull/221)\)\.
@@ -1114,12 +1173,12 @@ Bugfix release\. Fixes compatibility issue of ACME modules with step\-ca\.
<a id="v1-6-1"></a>
## v1\.6\.1
-<a id="release-summary-48"></a>
+<a id="release-summary-50"></a>
### Release Summary
Bugfix release\.
-<a id="bugfixes-35"></a>
+<a id="bugfixes-37"></a>
### Bugfixes
* acme\_\* modules \- fix wrong usages of <code>ACMEProtocolException</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/216](https\://github\.com/ansible\-collections/community\.crypto/pull/216)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/217](https\://github\.com/ansible\-collections/community\.crypto/pull/217)\)\.
@@ -1127,12 +1186,12 @@ Bugfix release\.
<a id="v1-6-0"></a>
## v1\.6\.0
-<a id="release-summary-49"></a>
+<a id="release-summary-51"></a>
### Release Summary
Fixes compatibility issues with the latest ansible\-core 2\.11 beta\, and contains a lot of internal refactoring for the ACME modules and support for private key passphrases for them\.
-<a id="minor-changes-22"></a>
+<a id="minor-changes-23"></a>
### Minor Changes
* acme module\_utils \- the <code>acme</code> module\_utils has been split up into several Python modules \([https\://github\.com/ansible\-collections/community\.crypto/pull/184](https\://github\.com/ansible\-collections/community\.crypto/pull/184)\)\.
@@ -1141,12 +1200,12 @@ Fixes compatibility issues with the latest ansible\-core 2\.11 beta\, and contai
* acme\_certificate\_revoke \- support revoking by private keys that are passphrase protected for <code>cryptography</code> backend \([https\://github\.com/ansible\-collections/community\.crypto/pull/207](https\://github\.com/ansible\-collections/community\.crypto/pull/207)\)\.
* acme\_challenge\_cert\_helper \- add <code>private\_key\_passphrase</code> parameter \([https\://github\.com/ansible\-collections/community\.crypto/pull/207](https\://github\.com/ansible\-collections/community\.crypto/pull/207)\)\.
-<a id="deprecated-features-6"></a>
+<a id="deprecated-features-7"></a>
### Deprecated Features
* acme module\_utils \- the <code>acme</code> module\_utils \(<code>ansible\_collections\.community\.crypto\.plugins\.module\_utils\.acme</code>\) is deprecated and will be removed in community\.crypto 2\.0\.0\. Use the new Python modules in the <code>acme</code> package instead \(<code>ansible\_collections\.community\.crypto\.plugins\.module\_utils\.acme\.xxx</code>\) \([https\://github\.com/ansible\-collections/community\.crypto/pull/184](https\://github\.com/ansible\-collections/community\.crypto/pull/184)\)\.
-<a id="bugfixes-36"></a>
+<a id="bugfixes-38"></a>
### Bugfixes
* action\_module plugin helper \- make compatible with latest changes in ansible\-core 2\.11\.0b3 \([https\://github\.com/ansible\-collections/community\.crypto/pull/202](https\://github\.com/ansible\-collections/community\.crypto/pull/202)\)\.
@@ -1155,23 +1214,23 @@ Fixes compatibility issues with the latest ansible\-core 2\.11 beta\, and contai
<a id="v1-5-0"></a>
## v1\.5\.0
-<a id="release-summary-50"></a>
+<a id="release-summary-52"></a>
### Release Summary
Regular feature and bugfix release\. Deprecates a return value\.
-<a id="minor-changes-23"></a>
+<a id="minor-changes-24"></a>
### Minor Changes
* acme\_account\_info \- when <code>retrieve\_orders</code> is not <code>ignore</code> and the ACME server allows to query orders\, the new return value <code>order\_uris</code> is always populated with a list of URIs \([https\://github\.com/ansible\-collections/community\.crypto/pull/178](https\://github\.com/ansible\-collections/community\.crypto/pull/178)\)\.
* luks\_device \- allow to specify sector size for LUKS2 containers with new <code>sector\_size</code> parameter \([https\://github\.com/ansible\-collections/community\.crypto/pull/193](https\://github\.com/ansible\-collections/community\.crypto/pull/193)\)\.
-<a id="deprecated-features-7"></a>
+<a id="deprecated-features-8"></a>
### Deprecated Features
* acme\_account\_info \- when <code>retrieve\_orders\=url\_list</code>\, <code>orders</code> will no longer be returned in community\.crypto 2\.0\.0\. Use <code>order\_uris</code> instead \([https\://github\.com/ansible\-collections/community\.crypto/pull/178](https\://github\.com/ansible\-collections/community\.crypto/pull/178)\)\.
-<a id="bugfixes-37"></a>
+<a id="bugfixes-39"></a>
### Bugfixes
* openssl\_csr \- no longer fails when comparing CSR without basic constraint when <code>basic\_constraints</code> is specified \([https\://github\.com/ansible\-collections/community\.crypto/issues/179](https\://github\.com/ansible\-collections/community\.crypto/issues/179)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/180](https\://github\.com/ansible\-collections/community\.crypto/pull/180)\)\.
@@ -1179,12 +1238,12 @@ Regular feature and bugfix release\. Deprecates a return value\.
<a id="v1-4-0"></a>
## v1\.4\.0
-<a id="release-summary-51"></a>
+<a id="release-summary-53"></a>
### Release Summary
Release with several new features and bugfixes\.
-<a id="minor-changes-24"></a>
+<a id="minor-changes-25"></a>
### Minor Changes
* The ACME module\_utils has been relicensed back from the Simplified BSD License \([https\://opensource\.org/licenses/BSD\-2\-Clause](https\://opensource\.org/licenses/BSD\-2\-Clause)\) to the GPLv3\+ \(same license used by most other code in this collection\)\. This undoes a licensing change when the original GPLv3\+ licensed code was moved to module\_utils in [https\://github\.com/ansible/ansible/pull/40697](https\://github\.com/ansible/ansible/pull/40697) \([https\://github\.com/ansible\-collections/community\.crypto/pull/165](https\://github\.com/ansible\-collections/community\.crypto/pull/165)\)\.
@@ -1194,7 +1253,7 @@ Release with several new features and bugfixes\.
* openssl\_csr\, openssl\_csr\_pipe \- allow to specify CRL distribution endpoints with <code>crl\_distribution\_points</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/147](https\://github\.com/ansible\-collections/community\.crypto/issues/147)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/167](https\://github\.com/ansible\-collections/community\.crypto/pull/167)\)\.
* openssl\_pkcs12 \- allow to specify certificate bundles in <code>other\_certificates</code> by using new option <code>other\_certificates\_parse\_all</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/149](https\://github\.com/ansible\-collections/community\.crypto/issues/149)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/166](https\://github\.com/ansible\-collections/community\.crypto/pull/166)\)\.
-<a id="bugfixes-38"></a>
+<a id="bugfixes-40"></a>
### Bugfixes
* acme\_certificate \- error when requested challenge type is not found for non\-valid challenges\, instead of hanging on step 2 \([https\://github\.com/ansible\-collections/community\.crypto/issues/171](https\://github\.com/ansible\-collections/community\.crypto/issues/171)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/173](https\://github\.com/ansible\-collections/community\.crypto/pull/173)\)\.
@@ -1202,12 +1261,12 @@ Release with several new features and bugfixes\.
<a id="v1-3-0"></a>
## v1\.3\.0
-<a id="release-summary-52"></a>
+<a id="release-summary-54"></a>
### Release Summary
Contains new modules <code>openssl\_privatekey\_pipe</code>\, <code>openssl\_csr\_pipe</code> and <code>x509\_certificate\_pipe</code> which allow to create or update private keys\, CSRs and X\.509 certificates without having to write them to disk\.
-<a id="minor-changes-25"></a>
+<a id="minor-changes-26"></a>
### Minor Changes
* openssh\_cert \- add module parameter <code>use\_agent</code> to enable using signing keys stored in ssh\-agent \([https\://github\.com/ansible\-collections/community\.crypto/issues/116](https\://github\.com/ansible\-collections/community\.crypto/issues/116)\)\.
@@ -1217,29 +1276,29 @@ Contains new modules <code>openssl\_privatekey\_pipe</code>\, <code>openssl\_csr
* x509\_certificate \- for the <code>selfsigned</code> 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/issues/32)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/129](https\://github\.com/ansible\-collections/community\.crypto/pull/129)\)\.
* x509\_certificate \- refactor module to allow code reuse by x509\_certificate\_pipe \([https\://github\.com/ansible\-collections/community\.crypto/pull/135](https\://github\.com/ansible\-collections/community\.crypto/pull/135)\)\.
-<a id="bugfixes-39"></a>
+<a id="bugfixes-41"></a>
### Bugfixes
* openssl\_pkcs12 \- report the correct state when <code>action</code> is <code>parse</code> \([https\://github\.com/ansible\-collections/community\.crypto/issues/143](https\://github\.com/ansible\-collections/community\.crypto/issues/143)\)\.
* support code \- improve handling of certificate and certificate signing request \(CSR\) loading with the <code>cryptography</code> backend when errors occur \([https\://github\.com/ansible\-collections/community\.crypto/issues/138](https\://github\.com/ansible\-collections/community\.crypto/issues/138)\, [https\://github\.com/ansible\-collections/community\.crypto/pull/139](https\://github\.com/ansible\-collections/community\.crypto/pull/139)\)\.
* x509\_certificate \- fix <code>entrust</code> provider\, which was broken since community\.crypto 0\.1\.0 due to a feature added before the collection move \([https\://github\.com/ansible\-collections/community\.crypto/pull/135](https\://github\.com/ansible\-collections/community\.crypto/pull/135)\)\.
-<a id="new-modules-3"></a>
+<a id="new-modules-4"></a>
### 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
<a id="v1-2-0"></a>
## v1\.2\.0
-<a id="release-summary-53"></a>
+<a id="release-summary-55"></a>
### Release Summary
Please note that this release fixes a security issue \(CVE\-2020\-25646\)\.
-<a id="minor-changes-26"></a>
+<a id="minor-changes-27"></a>
### Minor Changes
* acme\_certificate \- allow to pass CSR file as content with new option <code>csr\_content</code> \([https\://github\.com/ansible\-collections/community\.crypto/pull/115](https\://github\.com/ansible\-collections/community\.crypto/pull/115)\)\.
@@ -1255,7 +1314,7 @@ Please note that this release fixes a security issue \(CVE\-2020\-25646\)\.
* x509\_certificate \- the options <code>privatekey\_content</code> and <code>ownca\_privatekey\_content</code> were not marked as <code>no\_log</code>\, resulting in it being dumped into the system log by default\, and returned in the registered results in the <code>invocation</code> field \(CVE\-2020\-25646\, [https\://github\.com/ansible\-collections/community\.crypto/pull/125](https\://github\.com/ansible\-collections/community\.crypto/pull/125)\)\.
* x509\_crl \- the option <code>privatekey\_content</code> was not marked as <code>no\_log</code>\, resulting in it being dumped into the system log by default\, and returned in the registered results in the <code>invocation</code> field \(CVE\-2020\-25646\, [https\://github\.com/ansible\-collections/community\.crypto/pull/125](https\://github\.com/ansible\-collections/community\.crypto/pull/125)\)\.
-<a id="bugfixes-40"></a>
+<a id="bugfixes-42"></a>
### Bugfixes
* openssl\_pkcs12 \- do not crash when reading PKCS\#12 file which has no private key and/or no main certificate \([https\://github\.com/ansible\-collections/community\.crypto/issues/103](https\://github\.com/ansible\-collections/community\.crypto/issues/103)\)\.
@@ -1263,12 +1322,12 @@ Please note that this release fixes a security issue \(CVE\-2020\-25646\)\.
<a id="v1-1-1"></a>
## v1\.1\.1
-<a id="release-summary-54"></a>
+<a id="release-summary-56"></a>
### Release Summary
Bugfixes for Ansible 2\.10\.0\.
-<a id="bugfixes-41"></a>
+<a id="bugfixes-43"></a>
### Bugfixes
* meta/runtime\.yml \- convert Ansible version numbers for old names of modules to collection version numbers \([https\://github\.com/ansible\-collections/community\.crypto/pull/108](https\://github\.com/ansible\-collections/community\.crypto/pull/108)\)\.
@@ -1277,12 +1336,12 @@ Bugfixes for Ansible 2\.10\.0\.
<a id="v1-1-0"></a>
## v1\.1\.0
-<a id="release-summary-55"></a>
+<a id="release-summary-57"></a>
### Release Summary
Release for Ansible 2\.10\.0\.
-<a id="minor-changes-27"></a>
+<a id="minor-changes-28"></a>
### Minor Changes
* acme\_account \- add <code>external\_account\_binding</code> option to allow creation of ACME accounts with External Account Binding \([https\://github\.com/ansible\-collections/community\.crypto/issues/89](https\://github\.com/ansible\-collections/community\.crypto/issues/89)\)\.
@@ -1295,28 +1354,28 @@ Release for Ansible 2\.10\.0\.
* openssl\_csr \- add support for name constraints extension \([https\://github\.com/ansible\-collections/community\.crypto/issues/46](https\://github\.com/ansible\-collections/community\.crypto/issues/46)\)\.
* openssl\_csr\_info \- add support for name constraints extension \([https\://github\.com/ansible\-collections/community\.crypto/issues/46](https\://github\.com/ansible\-collections/community\.crypto/issues/46)\)\.
-<a id="bugfixes-42"></a>
+<a id="bugfixes-44"></a>
### Bugfixes
* acme\_inspect \- fix problem with Python 3\.5 that JSON was not decoded \([https\://github\.com/ansible\-collections/community\.crypto/issues/86](https\://github\.com/ansible\-collections/community\.crypto/issues/86)\)\.
* get\_certificate \- fix <code>ca\_cert</code> option handling when <code>proxy\_host</code> is used \([https\://github\.com/ansible\-collections/community\.crypto/pull/84](https\://github\.com/ansible\-collections/community\.crypto/pull/84)\)\.
* openssl\_\*\, x509\_\* modules \- fix handling of general names which refer to IP networks and not IP addresses \([https\://github\.com/ansible\-collections/community\.crypto/pull/92](https\://github\.com/ansible\-collections/community\.crypto/pull/92)\)\.
-<a id="new-modules-4"></a>
+<a id="new-modules-5"></a>
### 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
<a id="v1-0-0"></a>
## v1\.0\.0
-<a id="release-summary-56"></a>
+<a id="release-summary-58"></a>
### Release Summary
This is the first proper release of the <code>community\.crypto</code> collection\. This changelog contains all changes to the modules in this collection that were added after the release of Ansible 2\.9\.0\.
-<a id="minor-changes-28"></a>
+<a id="minor-changes-29"></a>
### Minor Changes
* luks\_device \- accept <code>passphrase</code>\, <code>new\_passphrase</code> and <code>remove\_passphrase</code>\.
@@ -1345,7 +1404,7 @@ This is the first proper release of the <code>community\.crypto</code> collectio
* openssl\_publickey \- allow to provide private key content via <code>private\_key\_content</code> option\.
* openssl\_publickey \- allow to return the existing/generated public key directly as <code>publickey</code> by setting <code>return\_content</code> to <code>yes</code>\.
-<a id="deprecated-features-8"></a>
+<a id="deprecated-features-9"></a>
### Deprecated Features
* openssl\_csr \- all values for the <code>version</code> option except <code>1</code> are deprecated\. The value 1 denotes the current only standardized CSR version\.
@@ -1355,7 +1414,7 @@ This is the first proper release of the <code>community\.crypto</code> collectio
* The <code>letsencrypt</code> module has been removed\. Use <code>acme\_certificate</code> instead\.
-<a id="bugfixes-43"></a>
+<a id="bugfixes-45"></a>
### Bugfixes
* ACME modules\: fix bug in ACME v1 account update code
@@ -1378,9 +1437,9 @@ This is the first proper release of the <code>community\.crypto</code> collectio
* openssl\_csr \- the module will now enforce that <code>privatekey\_path</code> is specified when <code>state\=present</code>\.
* openssl\_publickey \- fix a module crash caused when pyOpenSSL is not installed \([https\://github\.com/ansible/ansible/issues/67035](https\://github\.com/ansible/ansible/issues/67035)\)\.
-<a id="new-modules-5"></a>
+<a id="new-modules-6"></a>
### 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\)
diff --git a/ansible_collections/community/crypto/CHANGELOG.rst b/ansible_collections/community/crypto/CHANGELOG.rst
index b2dca8e3f..2ba4c8704 100644
--- a/ansible_collections/community/crypto/CHANGELOG.rst
+++ b/ansible_collections/community/crypto/CHANGELOG.rst
@@ -4,6 +4,56 @@ Community Crypto Release Notes
.. contents:: Topics
+v2.20.0
+=======
+
+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
+---------------
+
+Bugfix release.
+
+Bugfixes
+--------
+
+- 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).
+
v2.19.0
=======
@@ -31,7 +81,7 @@ Bugfixes
New Modules
-----------
-- x509_certificate_convert - Convert X.509 certificates
+- community.crypto.x509_certificate_convert - Convert X.509 certificates
v2.18.0
=======
@@ -64,8 +114,8 @@ New Plugins
Filter
~~~~~~
-- parse_serial - Convert a serial number as a colon-separated list of hex numbers to an integer
-- to_serial - Convert an integer to a colon-separated list of hex numbers
+- 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
=======
@@ -185,12 +235,12 @@ New Plugins
Filter
~~~~~~
-- gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key
+- community.crypto.gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key
Lookup
~~~~~~
-- gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key file
+- community.crypto.gpg_fingerprint - Retrieve a GPG fingerprint from a GPG public or private key file
v2.14.1
=======
@@ -333,12 +383,12 @@ New Plugins
Filter
~~~~~~
-- openssl_csr_info - Retrieve information from OpenSSL Certificate Signing Requests (CSR)
-- openssl_privatekey_info - Retrieve information from OpenSSL private keys
-- openssl_publickey_info - Retrieve information from OpenSSL public keys in PEM format
-- split_pem - Split PEM file contents into multiple objects
-- x509_certificate_info - Retrieve information from X.509 certificates in PEM format
-- x509_crl_info - Retrieve information from X.509 CRLs in PEM format
+- 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
======
@@ -637,8 +687,8 @@ Bugfixes
New Modules
-----------
-- crypto_info - Retrieve cryptographic capabilities
-- openssl_privatekey_convert - Convert OpenSSL private keys
+- community.crypto.crypto_info - Retrieve cryptographic capabilities
+- community.crypto.openssl_privatekey_convert - Convert OpenSSL private keys
v2.0.2
======
@@ -884,7 +934,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
======
@@ -1015,9 +1065,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
======
@@ -1093,8 +1143,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
======
@@ -1169,6 +1219,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)
diff --git a/ansible_collections/community/crypto/FILES.json b/ansible_collections/community/crypto/FILES.json
index e98f9499c..866c6794f 100644
--- a/ansible_collections/community/crypto/FILES.json
+++ b/ansible_collections/community/crypto/FILES.json
@@ -109,7 +109,7 @@
"name": ".azure-pipelines/azure-pipelines.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6e277188c1cbc0030cb55f73f6ebde233c77c66e2aa0d19476f9b7bae729d345",
+ "chksum_sha256": "76c14d937f8357d3124b959de775de59a0f2b6488d17b209d40bfe62217f579d",
"format": 1
},
{
@@ -130,7 +130,7 @@
"name": ".github/workflows/ansible-test.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "4250c0cd843d1a134c3d806ef9030b55f9a1b949902d1a746ccd19edb9f51a95",
+ "chksum_sha256": "5fefc24aaf86bfa9ba76bbde513f7919a48a049bb05ee7f07a054adbc21d8d1b",
"format": 1
},
{
@@ -263,7 +263,7 @@
"name": "changelogs/changelog.yaml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "7dcaea3df82e79d7b11bb628e95889ccfb31077902d6ddaee8655a83f577b418",
+ "chksum_sha256": "feb7cda6ba4ab1719a468f5a61415bcc67ba5342f67063a5687dd1d367947db9",
"format": 1
},
{
@@ -277,7 +277,7 @@
"name": "changelogs/config.yaml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "57d7548204ea1fd0b699f4cca0181e85a556bff89d5a35e52073d6dd44010b99",
+ "chksum_sha256": "3b2469377b8c18712f2566949b703be5a3b35ba07c336f060447313b9c44c58d",
"format": 1
},
{
@@ -368,7 +368,7 @@
"name": "meta/runtime.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "78f0817cd6a1f61ef403cff6f944799c473b2fe01fc618f8e4b94a996af77d75",
+ "chksum_sha256": "1a42d6a02d5627cd6b698fca894d464254b3fb3c99df0bc4880d20eefcc510f8",
"format": 1
},
{
@@ -403,7 +403,7 @@
"name": "plugins/doc_fragments/acme.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "72d44b4b7dc88ed9cedd76eec52a796b60da62283a14e68ad41e6adce69665f4",
+ "chksum_sha256": "0c59e90ff8eb2e5af6400a81d1f787dde63ceb400a37149e2c5224d9246883ce",
"format": 1
},
{
@@ -564,28 +564,28 @@
"name": "plugins/module_utils/acme/acme.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5d7f05fdab25dcbef2a8ce8d1edcd4c27fcc53c13c735a5c74e46aabc0098724",
+ "chksum_sha256": "8bb87a9023f0d079de2c496225a2e475177a98344a551d4c7e386313c3887277",
"format": 1
},
{
"name": "plugins/module_utils/acme/backend_cryptography.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5bccf4747fa4ef5853858db1683bfa4d31951fc1da78341c36063617eb4b2719",
+ "chksum_sha256": "67d1744bad142efd42898be008e4f2a94adfcd365b0ffe6f9066ea864e432fd5",
"format": 1
},
{
"name": "plugins/module_utils/acme/backend_openssl_cli.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "7a722bedddbc63e4d6e66b10659f63531c2675e19740ccc3381b70f6ed3fdba4",
+ "chksum_sha256": "c2b87c74fc95a7e58689c912e715b2444774782c0690def4ef07790ef1385f9a",
"format": 1
},
{
"name": "plugins/module_utils/acme/backends.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "e2c50aaec5b7404e7a8437d34f8246adcb6a7390dff505ce772d04b9a9530177",
+ "chksum_sha256": "aae3ba1f3e918957864d5773519e96f218b2af395a81d58b7760a525b175bb6d",
"format": 1
},
{
@@ -599,7 +599,7 @@
"name": "plugins/module_utils/acme/challenges.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "f425838b07c8ec95b9ea9afe07e58f724c7ef5cb1a876e1dcfa9e4997d7f31ab",
+ "chksum_sha256": "87536afccc41f2138a433bf3c1247ea29a60b09c77b81f3e38cf4d1387492e3d",
"format": 1
},
{
@@ -620,14 +620,14 @@
"name": "plugins/module_utils/acme/orders.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "bc139936871d65b9f4c0b0e5b772114eac5103bc291c4db907b650d0da63ea0d",
+ "chksum_sha256": "47a9091426fb8e0a1ac164aab8e38575360a9b5c101c8fcdb629e41072cb5ed1",
"format": 1
},
{
"name": "plugins/module_utils/acme/utils.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "e4856fa1ff751214f8fabdb30c4813191e891b18c778439913fef72e5c421267",
+ "chksum_sha256": "fd81b1f8eaa63794b95d8909d9497af50578ac49b9e13d9408c7bf76cb1b81b4",
"format": 1
},
{
@@ -648,7 +648,7 @@
"name": "plugins/module_utils/crypto/module_backends/certificate.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "c8858dddd2447413319f622af45c004f3b64eb6038e3e288454b76418d28d0f2",
+ "chksum_sha256": "1ac6dc3d2ed16216e8d4cb3dfffbcd83f85a92a1c357369c8ccb77dd40f5395e",
"format": 1
},
{
@@ -662,35 +662,35 @@
"name": "plugins/module_utils/crypto/module_backends/certificate_entrust.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "79486afc80b485e4c4c930b59f44c8320e5ece7eb63408bf5f5ac2bc66c3eeed",
+ "chksum_sha256": "117f7999f887c02e9d7c19657be67d169e6d1b2399d6cb3778a0e00ed1976b4a",
"format": 1
},
{
"name": "plugins/module_utils/crypto/module_backends/certificate_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "cd9c993f1c224036eba267820de7e21cec8a06a138d943b776541be214f1d14f",
+ "chksum_sha256": "95c2b6df3f6e777c30ab1f24be70a384a1d60df2b4d2e9f6d2e4bd287178cf53",
"format": 1
},
{
"name": "plugins/module_utils/crypto/module_backends/certificate_ownca.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6c58c55efbdec1869fc10fe63785242aee81f2dc8fff03397de5b75303ed724d",
+ "chksum_sha256": "10c22ee5cf387d5b09e6d07b1e12f6eff324e34b2fcacdd929c96b84cafb4ea3",
"format": 1
},
{
"name": "plugins/module_utils/crypto/module_backends/certificate_selfsigned.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "d46e3f53d82fe161efdc94f337126f66fcfb8e49647367c08a68ee2232ca6e8c",
+ "chksum_sha256": "0b4ee017b21abf3a7c2d5b65f34a2db09defa6b5725e3c5b19977672f03ee470",
"format": 1
},
{
"name": "plugins/module_utils/crypto/module_backends/common.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "7624320b1ddd2c5661df4ace026ed0b29282125755951e13356dab6f8efba374",
+ "chksum_sha256": "3dce4b1ee5f50da338fe62a245f61e4725c8702a645299d433cf082b64b18135",
"format": 1
},
{
@@ -704,7 +704,7 @@
"name": "plugins/module_utils/crypto/module_backends/csr.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "b3a7c30f3f7aaedb29f3e493fc6020c135cb0827c8e7bd53c49c68b3e97277da",
+ "chksum_sha256": "91d5019af08c4dda7e5d21611b9578d6a7bf35615f9f02335d732d2824ab6877",
"format": 1
},
{
@@ -718,14 +718,14 @@
"name": "plugins/module_utils/crypto/module_backends/privatekey.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "40ee4181c816995341ea4bd07d3de0d4c19b3d2076f7d6903fa15fc803543aff",
+ "chksum_sha256": "86051e9a9993ed441bfe364531071a202f5c60b44c45ce85026782bfd93c79bd",
"format": 1
},
{
"name": "plugins/module_utils/crypto/module_backends/privatekey_convert.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "90d164b2b338e8e15533df91394b5c8b9a425ee8d13812dbf5050e7d9b2d0388",
+ "chksum_sha256": "4d60f08fe8ee0d08a06641aef00f2ae31af22de034f4c13f3914b9b47e8193f3",
"format": 1
},
{
@@ -795,7 +795,7 @@
"name": "plugins/module_utils/crypto/math.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "d91f76043cd78a1c9dec5d22a7775be984e5796efb78b30792042ac5a4ebbee8",
+ "chksum_sha256": "abc08963caaa5bf31d68049074b9f7199d53f207cc72cf0e9359178ae34b7ea9",
"format": 1
},
{
@@ -816,7 +816,7 @@
"name": "plugins/module_utils/crypto/support.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "7d27f0bf007ccdb80a8d144381545f6406abad02396dbd39def186147a6efa91",
+ "chksum_sha256": "17a65c2c15ad982fb9efbbda36f39ed1d31dcb800cbff5e15cdc25f7d8b90acb",
"format": 1
},
{
@@ -879,7 +879,7 @@
"name": "plugins/module_utils/openssh/certificate.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "2febea40e9db5c2f560a0e399603812f520b575b00cec33bfae2e536962d24c7",
+ "chksum_sha256": "982bf6955620eeb74f586ec90e561f5ee4fc0ae94e728a3c5c3d56cd698e18bb",
"format": 1
},
{
@@ -904,6 +904,13 @@
"format": 1
},
{
+ "name": "plugins/module_utils/argspec.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2bcda588b1ee3c1972389dd575d84c4c4d9d58ec29a6056f6dc69c57baaaed21",
+ "format": 1
+ },
+ {
"name": "plugins/module_utils/io.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -918,6 +925,13 @@
"format": 1
},
{
+ "name": "plugins/module_utils/time.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7f673b24a2e5e79b35857131b416ec519b661c084397d36970963cc6f3c08aad",
+ "format": 1
+ },
+ {
"name": "plugins/module_utils/version.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -935,42 +949,63 @@
"name": "plugins/modules/acme_account.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "a9c3a88169fd7a7a8bfeedad92cace558ad6ec9aed11c641bd1f84a2382c9013",
+ "chksum_sha256": "99a0e35216a5b948f2023ed4f07198af7c76f4c09dc700dfd36be80804b908e7",
"format": 1
},
{
"name": "plugins/modules/acme_account_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "7c83dcc40c140a22ec7131f77524aef58b07556c9268f2625741fc6a2c338b6c",
+ "chksum_sha256": "636a47fe3fe6bdf98fc464e72d95aff8d18fe5a37d95e4b733e820165ffae925",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/acme_ari_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "32425edb18282abb4161a369ec62d237ff1be803440f288e289fe36eb6041398",
"format": 1
},
{
"name": "plugins/modules/acme_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "5f0f0039f146654c43e4a2a44e7be9becfc9a1d0566e11bef39ad591b472725d",
+ "chksum_sha256": "6d7f9006b87ab1c8b220668a7206cc223a72bfb992162b1d90c238dc8b148761",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/acme_certificate_deactivate_authz.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "09cf6f4fdd51244aade38600f6ed59c55640ee6652fe42705d19f4a29511328c",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/acme_certificate_renewal_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ec814b1f26e26701691f709f3abfc578f953e5200f554ce920d8673148cbedb6",
"format": 1
},
{
"name": "plugins/modules/acme_certificate_revoke.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "04f1b9f3dba82c5351a0ae1eae0c8360a6af3aad6e8df118d320271e3efb04c9",
+ "chksum_sha256": "a2d515f9a05cc38adb5e7817c991a7443582210c7843e9e7e2738864d16a18c2",
"format": 1
},
{
"name": "plugins/modules/acme_challenge_cert_helper.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "8484e4dc9508cffab80145f63ebabf0e88e6d203a3e466f2c000ca3e465596cb",
+ "chksum_sha256": "b10d22bb7a15d7f70f8bfd82991a273d6fdcd08f02e9dd61cc84051d099468ba",
"format": 1
},
{
"name": "plugins/modules/acme_inspect.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "490494d06240823440e81a5539c91c52f7359fade12e9e19aa28f8dfc5f725da",
+ "chksum_sha256": "45be39256ee327763add7f5978cc9aeae485a4e23abcfbd86052b09b2e7eb179",
"format": 1
},
{
@@ -991,7 +1026,7 @@
"name": "plugins/modules/ecs_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "0a5264350a46f4a5e7bf97a2b4bdf4d35391c4032dc8e3011ea6cdfe1a17e292",
+ "chksum_sha256": "3a81d2db16e086212b8ae6beee2300c3410649797fce664fe18174f6789bccee",
"format": 1
},
{
@@ -1005,7 +1040,7 @@
"name": "plugins/modules/get_certificate.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "3ea3b665adf3bbc3c2dc8e92cf9efd6c7b273aec0c8f1ef29e6414b0a9c50dd9",
+ "chksum_sha256": "18a87dc709c74ab15fabf4ef66e1c7db408a997b7d3154373f2066064e9c72a2",
"format": 1
},
{
@@ -1138,7 +1173,7 @@
"name": "plugins/modules/x509_certificate_info.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "e39c0d16a89cc7cac74d2c81e0406c9e5c1ebe8da0dd9c9c5ef9c26a78d5eae0",
+ "chksum_sha256": "806ee56458a3e0a54b2c8fdb0cb9ebe182f886dd9f8d93e55ae4af6e15f41cd0",
"format": 1
},
{
@@ -1152,7 +1187,7 @@
"name": "plugins/modules/x509_crl.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "eb2dfed3b7ec2739444923eb22812533901e9f6e7b6f19e127db7d34b3f6c6aa",
+ "chksum_sha256": "c19c2ab9d506103cb57b6b3f5473713b032e82074e2ff3f24cd7d562b61470b6",
"format": 1
},
{
@@ -1597,6 +1632,139 @@
"format": 1
},
{
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6f5821968427f3784e3f2ed2bf186dd0b0c198dabb3129c58aa1a5b5d487065d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a3be6b0b0acfc57621d7fa623a290e0edcbf4671f3116b5c859b362e26d9df21",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c3f80b8fa9ebbd81fa9674493ba9bc538f90f23d0e44a0262e772804714e29ba",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1b4b04590787e09f7621322d34e8dd36e2d2a7e7ec541ed89979fb8687e6ef2c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_deactivate_authz/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1846e9b081766a9ad0f4c13f42d014e82cd235f0cf8b1fd8d7eb41287c6a165f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6f5821968427f3784e3f2ed2bf186dd0b0c198dabb3129c58aa1a5b5d487065d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ad45f93e26e6683cee0b57a66ddabe0147dd5d3af96fadc2209ab4d501b407a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "28f228dbb86eb0077126a9c05fa98f95e961698eba37efda6ba251ed8dac88b1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c3f80b8fa9ebbd81fa9674493ba9bc538f90f23d0e44a0262e772804714e29ba",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2a55b59480a69b659a2d208fdff116f6dbc09e9b7075e1dd11eee88eeeff476b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/acme_certificate_renewal_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1846e9b081766a9ad0f4c13f42d014e82cd235f0cf8b1fd8d7eb41287c6a165f",
+ "format": 1
+ },
+ {
"name": "tests/integration/targets/acme_certificate_revoke",
"ftype": "dir",
"chksum_type": null,
@@ -1747,7 +1915,7 @@
"name": "tests/integration/targets/acme_inspect/tasks/impl.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "d8366b412ff8b4002d339b97fb4b7b7ee9475120e0e6890a464c3701aeef45ac",
+ "chksum_sha256": "2caf144a1cce04ab28b5e5568c79ab571d362992eecb7f3cf629d81dc8ec234c",
"format": 1
},
{
@@ -4547,7 +4715,7 @@
"name": "tests/integration/targets/x509_certificate/tasks/ownca.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "318ebf03e19480212efdeb7a1643858fc5d7eb761c414c9de4d234f5fdc24232",
+ "chksum_sha256": "fb30c2c92d5264236bc6c0f41288487347c56d7cbc174e0f75e73ae5cc8013de",
"format": 1
},
{
@@ -4561,7 +4729,7 @@
"name": "tests/integration/targets/x509_certificate/tasks/selfsigned.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6b085441e03a22f8d7a87b902b0f17d17239982fe53d7522ef8b6ef2e68b8f41",
+ "chksum_sha256": "a9cf4be468603320f28ec7107cb54bd582ba4419b689941b6ec58d474d9995ae",
"format": 1
},
{
@@ -4575,14 +4743,14 @@
"name": "tests/integration/targets/x509_certificate/tests/validate_ownca.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "af825159e41199c2d1f1db026d1440dd6b84b14e6b684de12b76971812d10ec5",
+ "chksum_sha256": "cb548f9b1aadc89104d2b4c31b9151213b56006f0a43f1f63dab2c86a302f97e",
"format": 1
},
{
"name": "tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "fea7c0daa838f7449053a70b563857991d919c281980ca1d4323123b4ae39862",
+ "chksum_sha256": "f0dfd5f5011f3416fababc4c4685c4950d4c7567effd7769bd25d70692e601ad",
"format": 1
},
{
@@ -5132,6 +5300,62 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a14802e2b54e6c33073b0fcc8a208b1e4b0c9e0da26c39e3771430327e1c32b7",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6eb915239f9f35407fa68fdc41ed6522f1fdcce11badbdcd6057548023179ac1",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "aa565ba566a86a6c4fdfe3c12d2e816a5e7dd929d03e43025e02cd79c11fd373",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6eb915239f9f35407fa68fdc41ed6522f1fdcce11badbdcd6057548023179ac1",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eced04a43a5c76e168a808dd0739d36ff38973b7d94c210df622302efc5d003e",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6eb915239f9f35407fa68fdc41ed6522f1fdcce11badbdcd6057548023179ac1",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "15452d352f015272474d30ba1e1f2c3043d658cfc2628873833a1abe940153dc",
+ "format": 1
+ },
+ {
+ "name": "tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6eb915239f9f35407fa68fdc41ed6522f1fdcce11badbdcd6057548023179ac1",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/module_utils/acme/fixtures/csr_1.pem",
"ftype": "file",
"chksum_type": "sha256",
@@ -5233,21 +5457,21 @@
"name": "tests/unit/plugins/module_utils/acme/backend_data.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "9b69baa0e28c3a4dc12e281c326a7f2280e2666fbbd3f51d8b34262b65640c52",
+ "chksum_sha256": "4b360ecd947ec58934ef86809c36a606ea84571a9f3cdc8924ab536d9b811925",
"format": 1
},
{
"name": "tests/unit/plugins/module_utils/acme/test_backend_cryptography.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "fccabe7cb7879c3189d18a3057cd9768544c6c8f5d9416aac48687b59ee20de5",
+ "chksum_sha256": "d2c5a8487bb2ef25d9f735df6e37d0bb51afecf2bfe5e845f8e37b0a1b5db487",
"format": 1
},
{
"name": "tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "810e5201c70b6e3bc1466d468c5fa822b7e8f5ed381f95de33b386b3a504cab4",
+ "chksum_sha256": "956ddae25c410ef809ace952a5d2457a68914a82065650d8f94d9abe5ce0b910",
"format": 1
},
{
@@ -5282,7 +5506,7 @@
"name": "tests/unit/plugins/module_utils/acme/test_utils.py",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "3658a226ba4a966807e1b4b07b1afb25b38663e95fe2053909fb93eb3339d72e",
+ "chksum_sha256": "98a935f74f8d0f3a597b0a63de78854795ef22c340700c3c74f121a7d6a8edda",
"format": 1
},
{
@@ -5307,6 +5531,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/module_utils/crypto/test_math.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "451713c4ad9f0efc68073978274740e8abdcab922861ae69d8a55e034cbb4c46",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/module_utils/crypto/test_pem.py",
"ftype": "file",
"chksum_type": "sha256",
@@ -5342,6 +5573,13 @@
"format": 1
},
{
+ "name": "tests/unit/plugins/module_utils/test_time.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1546079502474355c20c41a193a338aa34512af2ba192067dbd303ed50b5d57a",
+ "format": 1
+ },
+ {
"name": "tests/unit/plugins/modules",
"ftype": "dir",
"chksum_type": null,
@@ -5506,7 +5744,7 @@
"name": "CHANGELOG.md",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "ca2c6e6efdcfdb7ee6ee1b3bbb095989678081d32fa9aca67b65f57e96a37026",
+ "chksum_sha256": "4aa8a179caafc747a16ed4a1e21627199d7d97fdce382e169af3b497872c74a9",
"format": 1
},
{
@@ -5520,7 +5758,7 @@
"name": "CHANGELOG.rst",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "6ba9093a33e39e50b4a9436cbfdb36c2591a15c0aeadc8e7072b980df760ee6d",
+ "chksum_sha256": "d5b9954b1c1f6a83c292ced179112425246c2570642961d0056cc1fd3bb614c4",
"format": 1
},
{
@@ -5541,7 +5779,7 @@
"name": "README.md",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "704b9171377cd1c819ac83f7603438188432dfc6296bb7c6bfa7291c85d3b8b6",
+ "chksum_sha256": "10d998a15b33191f0fbc4a3c8ef7eebc95ee59d5144025142834e50b608f6e73",
"format": 1
}
],
diff --git a/ansible_collections/community/crypto/MANIFEST.json b/ansible_collections/community/crypto/MANIFEST.json
index 40dbbd275..7ceac0b47 100644
--- a/ansible_collections/community/crypto/MANIFEST.json
+++ b/ansible_collections/community/crypto/MANIFEST.json
@@ -2,7 +2,7 @@
"collection_info": {
"namespace": "community",
"name": "crypto",
- "version": "2.19.0",
+ "version": "2.20.0",
"authors": [
"Ansible (github.com/ansible)"
],
@@ -41,7 +41,7 @@
"name": "FILES.json",
"ftype": "file",
"chksum_type": "sha256",
- "chksum_sha256": "b8a70c677576d6f7bda75b45d97af355a4f371b9269a791bdef8976361b39f75",
+ "chksum_sha256": "18d1adbc1789a8e0f32412b5f07fbecb376933288258b709f7a91ff4c951d56f",
"format": 1
},
"format": 1
diff --git a/ansible_collections/community/crypto/README.md b/ansible_collections/community/crypto/README.md
index 99c6a4f62..7e8136227 100644
--- a/ansible_collections/community/crypto/README.md
+++ b/ansible_collections/community/crypto/README.md
@@ -66,7 +66,9 @@ If you use the Ansible package and do not update collections independently, use
- 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
diff --git a/ansible_collections/community/crypto/changelogs/changelog.yaml b/ansible_collections/community/crypto/changelogs/changelog.yaml
index 856a965ec..13eba21e1 100644
--- a/ansible_collections/community/crypto/changelogs/changelog.yaml
+++ b/ansible_collections/community/crypto/changelogs/changelog.yaml
@@ -1079,6 +1079,25 @@ releases:
name: x509_certificate_convert
namespace: ''
release_date: '2024-04-20'
+ 2.19.1:
+ changes:
+ bugfixes:
+ - 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).
+ release_summary: Bugfix release.
+ fragments:
+ - 2.19.1.yml
+ - 733-math-prime.yml
+ - 740-ecs_certificate-renewal-without-csr.yml
+ - 754-x509_certificate-time.yml
+ release_date: '2024-05-11'
2.2.0:
changes:
bugfixes:
@@ -1153,6 +1172,54 @@ releases:
- 2.2.4.yml
- 417-openssh_modules-fix-exception-reporting.yml
release_date: '2022-03-22'
+ 2.20.0:
+ changes:
+ 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).
+ 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).
+ 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).
+ 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.
+
+ '
+ fragments:
+ - 2.20.0.yml
+ - 735-acme-docs-fragment.yml
+ - 736-cert-info.yml
+ - 739-acme_certificate-include_renewal_cert_id.yml
+ - 745-absolute-time.yml
+ - 749-argspec.yml
+ modules:
+ - description: Retrieves ACME Renewal Information (ARI) for a certificate.
+ name: acme_ari_info
+ namespace: ''
+ - description: Deactivate all authz for an ACME v2 order.
+ name: acme_certificate_deactivate_authz
+ namespace: ''
+ - description: Determine whether a certificate should be renewed or not.
+ name: acme_certificate_renewal_info
+ namespace: ''
+ release_date: '2024-05-20'
2.3.0:
changes:
bugfixes:
diff --git a/ansible_collections/community/crypto/changelogs/config.yaml b/ansible_collections/community/crypto/changelogs/config.yaml
index 93ceb6472..c4cf310cf 100644
--- a/ansible_collections/community/crypto/changelogs/config.yaml
+++ b/ansible_collections/community/crypto/changelogs/config.yaml
@@ -34,3 +34,6 @@ sections:
- - known_issues
- Known Issues
title: Community Crypto
+trivial_section_name: trivial
+use_fqcn: true
+add_plugin_period: true
diff --git a/ansible_collections/community/crypto/meta/runtime.yml b/ansible_collections/community/crypto/meta/runtime.yml
index 76500748c..9c7592dc7 100644
--- a/ansible_collections/community/crypto/meta/runtime.yml
+++ b/ansible_collections/community/crypto/meta/runtime.yml
@@ -8,6 +8,7 @@ requires_ansible: '>=2.9.10'
action_groups:
acme:
- acme_inspect
+ - acme_certificate_deactivate_authz
- acme_certificate_revoke
- acme_certificate
- acme_account
diff --git a/ansible_collections/community/crypto/plugins/doc_fragments/acme.py b/ansible_collections/community/crypto/plugins/doc_fragments/acme.py
index 2b5bfc231..e5ee3ec4e 100644
--- a/ansible_collections/community/crypto/plugins/doc_fragments/acme.py
+++ b/ansible_collections/community/crypto/plugins/doc_fragments/acme.py
@@ -11,6 +11,9 @@ __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
@@ -137,3 +140,178 @@ options:
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
+'''
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/acme.py b/ansible_collections/community/crypto/plugins/module_utils/acme/acme.py
index 74d0bc1ea..7f9b954c0 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/acme.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/acme.py
@@ -21,6 +21,8 @@ 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,
)
@@ -42,7 +44,9 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
)
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
+ compute_cert_id,
nopad_b64,
+ parse_retry_after,
)
try:
@@ -153,6 +157,9 @@ class ACMEDirectory(object):
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):
'''
@@ -168,9 +175,9 @@ 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.
@@ -383,24 +390,94 @@ 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(
- 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', 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'),
)
+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)
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/backend_cryptography.py b/ansible_collections/community/crypto/plugins/module_utils/acme/backend_cryptography.py
index 0722c1f99..b652240dc 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/backend_cryptography.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/backend_cryptography.py
@@ -11,6 +11,7 @@ __metaclass__ = type
import base64
import binascii
+import datetime
import os
import traceback
@@ -19,7 +20,9 @@ from ansible.module_utils.common.text.converters import to_bytes, to_native, to_
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,27 +38,40 @@ 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.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.support import (
- get_now_datetime,
- ensure_utc_timezone,
- parse_name_field,
-)
-
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
@@ -170,6 +186,32 @@ 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.
@@ -376,7 +418,7 @@ class CryptographyBackend(CryptoBackend):
raise BackendException('Cannot parse certificate {0}: {1}'.format(cert_filename, e))
if now is None:
- now = get_now_datetime(with_timezone=CRYPTOGRAPHY_TIMEZONE)
+ now = self.get_now()
elif CRYPTOGRAPHY_TIMEZONE:
now = ensure_utc_timezone(now)
return (get_not_valid_after(cert) - now).days
@@ -386,3 +428,44 @@ class CryptographyBackend(CryptoBackend):
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,
+ )
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/backend_openssl_cli.py b/ansible_collections/community/crypto/plugins/module_utils/acme/backend_openssl_cli.py
index 9a1ed1f5a..9aab187ac 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/backend_openssl_cli.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/backend_openssl_cli.py
@@ -20,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,
)
@@ -30,6 +31,8 @@ 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.crypto.math import convert_bytes_to_int
+
try:
import ipaddress
except ImportError:
@@ -39,6 +42,33 @@ except ImportError:
_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)
@@ -89,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)
@@ -104,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':
@@ -303,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
@@ -319,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,
+ )
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/backends.py b/ansible_collections/community/crypto/plugins/module_utils/acme/backends.py
index 2d95a3ee3..7c08fae95 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/backends.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/backends.py
@@ -9,9 +9,78 @@ 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)
@@ -19,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):
'''
@@ -74,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()')
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/challenges.py b/ansible_collections/community/crypto/plugins/module_utils/acme/challenges.py
index 3a87ffec1..116ca4206 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/challenges.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/challenges.py
@@ -103,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,
@@ -283,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'
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/orders.py b/ansible_collections/community/crypto/plugins/module_utils/acme/orders.py
index 732b430df..98c28445f 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/orders.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/orders.py
@@ -32,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']
@@ -44,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 = []
@@ -62,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
@@ -76,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'])
diff --git a/ansible_collections/community/crypto/plugins/module_utils/acme/utils.py b/ansible_collections/community/crypto/plugins/module_utils/acme/utils.py
index 217b6de47..ba460444b 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/acme/utils.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/acme/utils.py
@@ -10,6 +10,7 @@ __metaclass__ = type
import base64
+import datetime
import re
import textwrap
import traceback
@@ -19,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("=", "")
@@ -65,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)
diff --git a/ansible_collections/community/crypto/plugins/module_utils/argspec.py b/ansible_collections/community/crypto/plugins/module_utils/argspec.py
new file mode 100644
index 000000000..e583609dd
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/module_utils/argspec.py
@@ -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', )
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py
index f56f22d33..1ec43e9f2 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/math.py
@@ -42,9 +42,18 @@ def quick_is_not_prime(n):
that we could not detect quickly whether it is not prime.
'''
if n <= 2:
- return True
+ return n < 2
# The constant in the next line is the product of all primes < 200
- if simple_gcd(n, 7799922041683461553249199106329813876687996789903550945093032474868511536164700810) > 1:
+ prime_product = 7799922041683461553249199106329813876687996789903550945093032474868511536164700810
+ gcd = simple_gcd(n, prime_product)
+ if gcd > 1:
+ if n < 200 and gcd == n:
+ # Explicitly check for all primes < 200
+ return n not in (
+ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
+ 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179,
+ 181, 191, 193, 197, 199,
+ )
return True
# TODO: maybe do some iterations of Miller-Rabin to increase confidence
# (https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test)
@@ -101,16 +110,27 @@ if sys.version_info[0] >= 3:
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
@@ -144,3 +164,10 @@ def convert_int_to_hex(no, digits=None):
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)
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py
index 7bc93d934..595748fb9 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate.py
@@ -15,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,
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py
index 7dc4641e1..37351daeb 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_entrust.py
@@ -18,8 +18,6 @@ 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_now_datetime,
- get_relative_time_option,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
@@ -34,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:
@@ -44,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(
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_info.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_info.py
index 5db6c3586..b612f8b18 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_info.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_info.py
@@ -23,7 +23,6 @@ from ansible_collections.community.crypto.plugins.module_utils.version import Lo
from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
load_certificate,
get_fingerprint_of_bytes,
- get_now_datetime,
)
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
@@ -40,6 +39,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
get_publickey_info,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_now_datetime,
+)
+
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
CRYPTOGRAPHY_IMP_ERR = None
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py
index 4d312e6b7..bd4860dff 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_ownca.py
@@ -22,11 +22,11 @@ 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,
@@ -44,6 +44,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
CertificateProvider,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_relative_time_option,
+)
+
try:
import cryptography
from cryptography import x509
@@ -59,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()
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py
index edd8d8d77..d7135d355 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/certificate_selfsigned.py
@@ -14,11 +14,11 @@ import os
from random import randrange
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,
@@ -34,6 +34,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
CertificateProvider,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_relative_time_option,
+)
+
try:
import cryptography
from cryptography import x509
@@ -48,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()
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py
index 67f87dd0c..6616249c4 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/common.py
@@ -10,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)
-
- def create_ansible_module(self, **kwargs):
- return self.create_ansible_module_helper(AnsibleModule, (), **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
+
+
+__all__ = ('AnsibleModule', 'ArgumentSpec')
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py
index 4ab14e527..6ce7e2438 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/csr.py
@@ -17,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_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
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
@@ -49,8 +51,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
get_csr_info,
)
-from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
-
MINIMAL_CRYPTOGRAPHY_VERSION = '1.3'
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py
index dc13107b7..36d50ae3c 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey.py
@@ -17,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 (
@@ -42,8 +44,6 @@ 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_CRYPTOGRAPHY_VERSION = '1.2.3'
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey_convert.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey_convert.py
index fdcc901e0..4a1aca600 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey_convert.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/module_backends/privatekey_convert.py
@@ -15,12 +15,14 @@ from ansible.module_utils import six
from ansible.module_utils.basic import missing_required_lib
from ansible.module_utils.common.text.converters import to_bytes
-from ansible_collections.community.crypto.plugins.module_utils.version import LooseVersion
+from ansible_collections.community.crypto.plugins.module_utils.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,
@@ -37,8 +39,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import
identify_private_key_format,
)
-from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.common import ArgumentSpec
-
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2.3'
diff --git a/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py b/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py
index 8b59a3b70..862f5b8fc 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/crypto/support.py
@@ -9,19 +9,25 @@ __metaclass__ = type
import abc
-import datetime
import errno
import hashlib
import os
-import re
from ansible.module_utils import six
-from ansible.module_utils.common.text.converters import to_native, to_bytes
+from ansible.module_utils.common.text.converters import to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
identify_pem_format,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import ( # noqa: F401, pylint: disable=unused-import
+ # These imports are for backwards compatibility
+ get_now_datetime,
+ ensure_utc_timezone,
+ convert_relative_to_datetime,
+ get_relative_time_option,
+)
+
try:
from OpenSSL import crypto
HAS_PYOPENSSL = True
@@ -279,86 +285,6 @@ def parse_ordered_name_field(input_list, name_field_name):
return result
-def get_now_datetime(with_timezone):
- if with_timezone:
- return datetime.datetime.now(tz=datetime.timezone.utc)
- return datetime.datetime.utcnow()
-
-
-def ensure_utc_timezone(timestamp):
- if timestamp.tzinfo is not None:
- return timestamp
- return timestamp.astimezone(datetime.timezone.utc)
-
-
-def convert_relative_to_datetime(relative_time_string, with_timezone=False):
- """Get a datetime.datetime or None from a string in the time format described in sshd_config(5)"""
-
- parsed_result = re.match(
- r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$",
- relative_time_string)
-
- if parsed_result is None or len(relative_time_string) == 1:
- # not matched or only a single "+" or "-"
- return None
-
- offset = datetime.timedelta(0)
- if parsed_result.group("weeks") is not None:
- offset += datetime.timedelta(weeks=int(parsed_result.group("weeks")))
- if parsed_result.group("days") is not None:
- offset += datetime.timedelta(days=int(parsed_result.group("days")))
- if parsed_result.group("hours") is not None:
- offset += datetime.timedelta(hours=int(parsed_result.group("hours")))
- if parsed_result.group("minutes") is not None:
- offset += datetime.timedelta(
- minutes=int(parsed_result.group("minutes")))
- if parsed_result.group("seconds") is not None:
- offset += datetime.timedelta(
- seconds=int(parsed_result.group("seconds")))
-
- now = get_now_datetime(with_timezone=with_timezone)
- if parsed_result.group("prefix") == "+":
- return now + offset
- else:
- return now - offset
-
-
-def get_relative_time_option(input_string, input_name, backend='cryptography', with_timezone=False):
- """Return an absolute timespec if a relative timespec or an ASN1 formatted
- string is provided.
-
- The return value will be a datetime object for the cryptography backend,
- and a ASN1 formatted string for the pyopenssl backend."""
- result = to_native(input_string)
- if result is None:
- raise OpenSSLObjectError(
- 'The timespec "%s" for %s is not valid' %
- input_string, input_name)
- # Relative time
- if result.startswith("+") or result.startswith("-"):
- result_datetime = convert_relative_to_datetime(result, with_timezone=with_timezone)
- if backend == 'pyopenssl':
- return result_datetime.strftime("%Y%m%d%H%M%SZ")
- elif backend == 'cryptography':
- return result_datetime
- # Absolute time
- if backend == 'cryptography':
- for date_fmt in ['%Y%m%d%H%M%SZ', '%Y%m%d%H%MZ', '%Y%m%d%H%M%S%z', '%Y%m%d%H%M%z']:
- try:
- res = datetime.datetime.strptime(result, date_fmt)
- except ValueError:
- pass
- else:
- if with_timezone:
- res = res.astimezone(datetime.timezone.utc)
- return res
-
- raise OpenSSLObjectError(
- 'The time spec "%s" for %s is invalid' %
- (input_string, input_name)
- )
-
-
def select_message_digest(digest_string):
digest = None
if digest_string == 'sha256':
diff --git a/ansible_collections/community/crypto/plugins/module_utils/openssh/certificate.py b/ansible_collections/community/crypto/plugins/module_utils/openssh/certificate.py
index f59766651..8efb2ad9c 100644
--- a/ansible_collections/community/crypto/plugins/module_utils/openssh/certificate.py
+++ b/ansible_collections/community/crypto/plugins/module_utils/openssh/certificate.py
@@ -31,11 +31,15 @@ from hashlib import sha256
from ansible.module_utils import six
from ansible.module_utils.common.text.converters import to_text
-from ansible_collections.community.crypto.plugins.module_utils.crypto.support import convert_relative_to_datetime
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import (
OpensshParser,
_OpensshWriter,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ add_or_remove_timezone as _add_or_remove_timezone,
+ convert_relative_to_datetime,
+ UTC as _UTC,
+)
# See https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD
_USER_TYPE = 1
@@ -66,14 +70,8 @@ _ECDSA_CURVE_IDENTIFIERS_LOOKUP = {
_USE_TIMEZONE = sys.version_info >= (3, 6)
-def _ensure_utc_timezone_if_use_timezone(value):
- if not _USE_TIMEZONE or value.tzinfo is not None:
- return value
- return value.astimezone(_datetime.timezone.utc)
-
-
-_ALWAYS = _ensure_utc_timezone_if_use_timezone(datetime(1970, 1, 1))
-_FOREVER = datetime(9999, 12, 31, 23, 59, 59, 999999, _datetime.timezone.utc) if _USE_TIMEZONE else datetime.max
+_ALWAYS = _add_or_remove_timezone(datetime(1970, 1, 1), with_timezone=_USE_TIMEZONE)
+_FOREVER = datetime(9999, 12, 31, 23, 59, 59, 999999, _UTC) if _USE_TIMEZONE else datetime.max
_CRITICAL_OPTIONS = (
'force-command',
@@ -198,7 +196,7 @@ class OpensshCertificateTimeParameters(object):
else:
for time_format in ("%Y-%m-%d", "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S"):
try:
- result = _ensure_utc_timezone_if_use_timezone(datetime.strptime(time_string, time_format))
+ result = _add_or_remove_timezone(datetime.strptime(time_string, time_format), with_timezone=_USE_TIMEZONE)
except ValueError:
pass
if result is None:
diff --git a/ansible_collections/community/crypto/plugins/module_utils/time.py b/ansible_collections/community/crypto/plugins/module_utils/time.py
new file mode 100644
index 000000000..4adc4620e
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/module_utils/time.py
@@ -0,0 +1,171 @@
+# -*- 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
+
+
+import datetime
+import re
+import sys
+
+from ansible.module_utils.common.text.converters import to_native
+
+from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
+ OpenSSLObjectError,
+)
+
+
+try:
+ UTC = datetime.timezone.utc
+except AttributeError:
+ _DURATION_ZERO = datetime.timedelta(0)
+
+ class _UTCClass(datetime.tzinfo):
+ def utcoffset(self, dt):
+ return _DURATION_ZERO
+
+ def dst(self, dt):
+ return _DURATION_ZERO
+
+ def tzname(self, dt):
+ return 'UTC'
+
+ def fromutc(self, dt):
+ return dt
+
+ def __repr__(self):
+ return 'UTC'
+
+ UTC = _UTCClass()
+
+
+def get_now_datetime(with_timezone):
+ if with_timezone:
+ return datetime.datetime.now(tz=UTC)
+ return datetime.datetime.utcnow()
+
+
+def ensure_utc_timezone(timestamp):
+ if timestamp.tzinfo is UTC:
+ return timestamp
+ if timestamp.tzinfo is None:
+ # We assume that naive datetime objects use timezone UTC!
+ return timestamp.replace(tzinfo=UTC)
+ return timestamp.astimezone(UTC)
+
+
+def remove_timezone(timestamp):
+ # Convert to native datetime object
+ if timestamp.tzinfo is None:
+ return timestamp
+ if timestamp.tzinfo is not UTC:
+ timestamp = timestamp.astimezone(UTC)
+ return timestamp.replace(tzinfo=None)
+
+
+def add_or_remove_timezone(timestamp, with_timezone):
+ return ensure_utc_timezone(timestamp) if with_timezone else remove_timezone(timestamp)
+
+
+if sys.version_info < (3, 3):
+ def get_epoch_seconds(timestamp):
+ epoch = datetime.datetime(1970, 1, 1, tzinfo=UTC if timestamp.tzinfo is not None else None)
+ delta = timestamp - epoch
+ try:
+ return delta.total_seconds()
+ except AttributeError:
+ # Python 2.6 and earlier: total_seconds() does not yet exist, so we use the formula from
+ # https://docs.python.org/2/library/datetime.html#datetime.timedelta.total_seconds
+ return (delta.microseconds + (delta.seconds + delta.days * 24 * 3600) * 10**6) / 10**6
+else:
+ def get_epoch_seconds(timestamp):
+ return timestamp.timestamp()
+
+
+def from_epoch_seconds(timestamp, with_timezone):
+ if with_timezone:
+ return datetime.datetime.fromtimestamp(timestamp, UTC)
+ return datetime.datetime.utcfromtimestamp(timestamp)
+
+
+def convert_relative_to_datetime(relative_time_string, with_timezone=False, now=None):
+ """Get a datetime.datetime or None from a string in the time format described in sshd_config(5)"""
+
+ parsed_result = re.match(
+ r"^(?P<prefix>[+-])((?P<weeks>\d+)[wW])?((?P<days>\d+)[dD])?((?P<hours>\d+)[hH])?((?P<minutes>\d+)[mM])?((?P<seconds>\d+)[sS]?)?$",
+ relative_time_string)
+
+ if parsed_result is None or len(relative_time_string) == 1:
+ # not matched or only a single "+" or "-"
+ return None
+
+ offset = datetime.timedelta(0)
+ if parsed_result.group("weeks") is not None:
+ offset += datetime.timedelta(weeks=int(parsed_result.group("weeks")))
+ if parsed_result.group("days") is not None:
+ offset += datetime.timedelta(days=int(parsed_result.group("days")))
+ if parsed_result.group("hours") is not None:
+ offset += datetime.timedelta(hours=int(parsed_result.group("hours")))
+ if parsed_result.group("minutes") is not None:
+ offset += datetime.timedelta(
+ minutes=int(parsed_result.group("minutes")))
+ if parsed_result.group("seconds") is not None:
+ offset += datetime.timedelta(
+ seconds=int(parsed_result.group("seconds")))
+
+ if now is None:
+ now = get_now_datetime(with_timezone=with_timezone)
+ else:
+ now = add_or_remove_timezone(now, with_timezone=with_timezone)
+
+ if parsed_result.group("prefix") == "+":
+ return now + offset
+ else:
+ return now - offset
+
+
+def get_relative_time_option(input_string, input_name, backend='cryptography', with_timezone=False, now=None):
+ """Return an absolute timespec if a relative timespec or an ASN1 formatted
+ string is provided.
+
+ The return value will be a datetime object for the cryptography backend,
+ and a ASN1 formatted string for the pyopenssl backend."""
+ result = to_native(input_string)
+ if result is None:
+ raise OpenSSLObjectError(
+ 'The timespec "%s" for %s is not valid' %
+ input_string, input_name)
+ # Relative time
+ if result.startswith("+") or result.startswith("-"):
+ result_datetime = convert_relative_to_datetime(result, with_timezone=with_timezone, now=now)
+ if backend == 'pyopenssl':
+ return result_datetime.strftime("%Y%m%d%H%M%SZ")
+ elif backend == 'cryptography':
+ return result_datetime
+ # Absolute time
+ if backend == 'pyopenssl':
+ return input_string
+ elif backend == 'cryptography':
+ for date_fmt, length in [
+ ('%Y%m%d%H%M%SZ', 15), # this also parses '202401020304Z', but as datetime(2024, 1, 2, 3, 0, 4)
+ ('%Y%m%d%H%MZ', 13),
+ ('%Y%m%d%H%M%S%z', 14 + 5), # this also parses '202401020304+0000', but as datetime(2024, 1, 2, 3, 0, 4, tzinfo=...)
+ ('%Y%m%d%H%M%z', 12 + 5),
+ ]:
+ if len(result) != length:
+ continue
+ try:
+ res = datetime.datetime.strptime(result, date_fmt)
+ except ValueError:
+ pass
+ else:
+ return add_or_remove_timezone(res, with_timezone=with_timezone)
+
+ raise OpenSSLObjectError(
+ 'The time spec "%s" for %s is invalid' %
+ (input_string, input_name)
+ )
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_account.py b/ansible_collections/community/crypto/plugins/modules/acme_account.py
index 1e8d64a57..960bad313 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_account.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_account.py
@@ -37,7 +37,8 @@ seealso:
- module: community.crypto.acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
@@ -169,11 +170,9 @@ account_uri:
import base64
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -188,8 +187,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec()
+ argument_spec.update_argspec(
terms_agreed=dict(type='bool', default=False),
state=dict(type='str', required=True, choices=['absent', 'present', 'changed_key']),
allow_creation=dict(type='bool', default=True),
@@ -202,14 +201,9 @@ def main():
alg=dict(type='str', required=True, choices=['HS256', 'HS384', 'HS512']),
key=dict(type='str', required=True, no_log=True),
))
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- required_one_of=(
- ['account_key_src', 'account_key_content'],
- ),
+ )
+ argument_spec.update(
mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
['new_account_key_src', 'new_account_key_content'],
),
required_if=(
@@ -217,8 +211,8 @@ def main():
# new_account_key_src and new_account_key_content are specified
['state', 'changed_key', ['new_account_key_src', 'new_account_key_content'], True],
),
- supports_check_mode=True,
)
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, True)
if module.params['external_account_binding']:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_account_info.py b/ansible_collections/community/crypto/plugins/modules/acme_account_info.py
index ac4617c90..33313fe75 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_account_info.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_account_info.py
@@ -25,7 +25,8 @@ notes:
- "This module was called C(acme_account_facts) before Ansible 2.8. The usage
did not change."
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
- community.crypto.attributes.info_module
@@ -213,11 +214,9 @@ order_uris:
version_added: 1.5.0
'''
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -270,20 +269,11 @@ def get_order(client, order_url):
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec()
+ argument_spec.update_argspec(
retrieve_orders=dict(type='str', default='ignore', choices=['ignore', 'url_list', 'object_list']),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- required_one_of=(
- ['account_key_src', 'account_key_content'],
- ),
- mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
- ),
- supports_check_mode=True,
)
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, True)
try:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_ari_info.py b/ansible_collections/community/crypto/plugins/modules/acme_ari_info.py
new file mode 100644
index 000000000..7783236f0
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/modules/acme_ari_info.py
@@ -0,0 +1,142 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 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 = '''
+---
+module: acme_ari_info
+author: "Felix Fontein (@felixfontein)"
+version_added: 2.20.0
+short_description: Retrieves ACME Renewal Information (ARI) for a certificate
+description:
+ - "Allows to retrieve renewal information on a certificate obtained with the
+ L(ACME protocol,https://tools.ietf.org/html/rfc8555)."
+ - "This module only works with the ACME v2 protocol, and requires the ACME server
+ to support the ARI extension (U(https://datatracker.ietf.org/doc/draft-ietf-acme-ari/)).
+ This module implements version 3 of the ARI draft."
+extends_documentation_fragment:
+ - community.crypto.acme.basic
+ - community.crypto.acme.no_account
+ - community.crypto.attributes
+ - community.crypto.attributes.info_module
+options:
+ certificate_path:
+ description:
+ - A path to the X.509 certificate to request information for.
+ - Exactly one of O(certificate_path) and O(certificate_content) must be provided.
+ type: path
+ certificate_content:
+ description:
+ - The content of the X.509 certificate to request information for.
+ - Exactly one of O(certificate_path) and O(certificate_content) must be provided.
+ type: str
+seealso:
+ - module: community.crypto.acme_certificate
+ description: Allows to obtain a certificate using the ACME protocol
+ - module: community.crypto.acme_certificate_revoke
+ description: Allows to revoke a certificate using the ACME protocol
+'''
+
+EXAMPLES = '''
+- name: Retrieve renewal information for a certificate
+ community.crypto.acme_ari_info:
+ certificate_path: /etc/httpd/ssl/sample.com.crt
+ register: cert_data
+
+- name: Show the certificate renewal information
+ ansible.builtin.debug:
+ var: cert_data.renewal_info
+'''
+
+RETURN = '''
+renewal_info:
+ description: The ARI renewal info object (U(https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.2)).
+ returned: success
+ type: dict
+ contains:
+ suggestedWindow:
+ description:
+ - Describes the window during which the certificate should be renewed.
+ type: dict
+ returned: always
+ contains:
+ start:
+ description:
+ - The start of the window during which the certificate should be renewed.
+ - The format is specified in L(RFC 3339,https://www.rfc-editor.org/info/rfc3339).
+ returned: always
+ type: str
+ sample: '2021-01-03T00:00:00Z'
+ end:
+ description:
+ - The end of the window during which the certificate should be renewed.
+ - The format is specified in L(RFC 3339,https://www.rfc-editor.org/info/rfc3339).
+ returned: always
+ type: str
+ sample: '2021-01-03T00:00:00Z'
+ explanationURL:
+ description:
+ - A URL pointing to a page which may explain why the suggested renewal window is what it is.
+ - For example, it may be a page explaining the CA's dynamic load-balancing strategy, or a
+ page documenting which certificates are affected by a mass revocation event. Should be shown
+ to the user.
+ returned: depends on the ACME server
+ type: str
+ sample: https://example.com/docs/ari
+ retryAfter:
+ description:
+ - A timestamp before the next retry to ask for this information should not be made.
+ returned: depends on the ACME server
+ type: str
+ sample: '2024-04-29T01:17:10.236921+00:00'
+'''
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
+ create_backend,
+ create_default_argspec,
+ ACMEClient,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
+
+
+def main():
+ argument_spec = create_default_argspec(with_account=False)
+ argument_spec.update_argspec(
+ certificate_path=dict(type='path'),
+ certificate_content=dict(type='str'),
+ )
+ argument_spec.update(
+ required_one_of=(
+ ['certificate_path', 'certificate_content'],
+ ),
+ mutually_exclusive=(
+ ['certificate_path', 'certificate_content'],
+ ),
+ )
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
+ backend = create_backend(module, True)
+
+ try:
+ client = ACMEClient(module, backend)
+ if not client.directory.has_renewal_info_endpoint():
+ module.fail_json(msg='The ACME endpoint does not support ACME Renewal Information retrieval')
+ renewal_info = client.get_renewal_info(
+ cert_filename=module.params['certificate_path'],
+ cert_content=module.params['certificate_content'],
+ include_retry_after=True,
+ )
+ module.exit_json(renewal_info=renewal_info)
+ except ModuleFailException as e:
+ e.do_fail(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate.py
index 21a6d6ae9..8729996c0 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_certificate.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate.py
@@ -58,7 +58,7 @@ seealso:
link: https://tools.ietf.org/html/rfc8555
- name: ACME TLS ALPN Challenge Extension
description: The specification of the V(tls-alpn-01) challenge (RFC 8737).
- link: https://www.rfc-editor.org/rfc/rfc8737.html-05
+ link: https://www.rfc-editor.org/rfc/rfc8737.html
- module: community.crypto.acme_challenge_cert_helper
description: Helps preparing V(tls-alpn-01) challenges.
- module: community.crypto.openssl_privatekey
@@ -77,8 +77,12 @@ seealso:
description: Allows to create, modify or delete an ACME account.
- module: community.crypto.acme_inspect
description: Allows to debug problems.
+ - module: community.crypto.acme_certificate_deactivate_authz
+ description: Allows to deactivate (invalidate) ACME v2 orders.
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
+ - community.crypto.acme.certificate
- community.crypto.attributes
- community.crypto.attributes.files
- community.crypto.attributes.actiongroup_acme
@@ -138,32 +142,8 @@ options:
- 'tls-alpn-01'
- 'no challenge'
csr:
- description:
- - "File containing the CSR for the new certificate."
- - "Can be created with M(community.crypto.openssl_csr) or C(openssl req ...)."
- - "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."
- - "I(Note): the private key used to create the CSR I(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
aliases: ['src']
csr_content:
- description:
- - "Content of the CSR for the new certificate."
- - "Can be created with M(community.crypto.openssl_csr_pipe) or C(openssl req ...)."
- - "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."
- - "I(Note): the private key used to create the CSR I(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
version_added: 1.2.0
data:
description:
@@ -292,6 +272,32 @@ options:
- "The identifier must be of the form
V(C4:A7:B1:A4:7B:2C:71:FA:DB:E1:4B:90:75:FF:C4:15:60:85:89:10)."
type: str
+ include_renewal_cert_id:
+ description:
+ - Determines whether to request renewal of an existing certificate according to
+ L(the ACME ARI draft 3, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-5).
+ - This is only used when the certificate specified in O(dest) or O(fullchain_dest) already exists.
+ - V(never) never sends the certificate ID of the certificate to renew. V(always) will always send it.
+ - V(when_ari_supported) only sends the certificate ID if the ARI endpoint is found in the ACME directory.
+ - Generally you should use V(when_ari_supported) if you know that the ACME service supports a compatible
+ draft (or final version, once it is out) of the ARI extension. V(always) should never be necessary.
+ If you are not sure, or if you receive strange errors on invalid C(replaces) values in order objects,
+ use V(never), which also happens to be the default.
+ - ACME servers might refuse to create new orders with C(replaces) for certificates that already have an
+ existing order. This can happen if this module is used to create an order, and then the playbook/role
+ fails in case the challenges cannot be set up. If the playbook/role does not record the order data to
+ continue with the existing order, but tries to create a new one on the next run, creating the new order
+ might fail. For this reason, this option should only be set to a value different from V(never) if the
+ role/playbook using it keeps track of order data accross restarts, or if it takes care to deactivate
+ orders whose processing is aborted. Orders can be deactivated with the
+ M(community.crypto.acme_certificate_deactivate_authz) module.
+ type: str
+ choices:
+ - never
+ - when_ari_supported
+ - always
+ default: never
+ version_added: 2.20.0
'''
EXAMPLES = r'''
@@ -375,7 +381,7 @@ EXAMPLES = r'''
# state: present
# wait: true
# # Note: route53 requires TXT entries to be enclosed in quotes
-# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | regex_replace('^(.*)$', '\"\\1\"') }}"
+# value: "{{ sample_com_challenge.challenge_data['sample.com']['dns-01'].resource_value | community.dns.quote_txt(always_quote=true) }}"
# when: sample_com_challenge is changed and 'sample.com' in sample_com_challenge.challenge_data
#
# Alternative way:
@@ -390,7 +396,7 @@ EXAMPLES = r'''
# wait: true
# # Note: item.value is a list of TXT entries, and route53
# # requires every entry to be enclosed in quotes
-# value: "{{ item.value | map('regex_replace', '^(.*)$', '\"\\1\"' ) | list }}"
+# value: "{{ item.value | map('community.dns.quote_txt', always_quote=true) | list }}"
# loop: "{{ sample_com_challenge.challenge_data_dns | dict2items }}"
# when: sample_com_challenge is changed
@@ -446,39 +452,55 @@ challenge_data:
- Per identifier / challenge type challenge data.
- Since Ansible 2.8.5, only challenges which are not yet valid are returned.
returned: changed
- type: list
- elements: dict
+ type: dict
contains:
- resource:
- description: The challenge resource that must be created for validation.
- returned: changed
- type: str
- sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA
- resource_original:
- description:
- - The original challenge resource including type identifier for V(tls-alpn-01)
- challenges.
- returned: changed and O(challenge) is V(tls-alpn-01)
- type: str
- sample: DNS:example.com
- resource_value:
+ identifier:
description:
- - The value the resource has to produce for the validation.
- - For V(http-01) and V(dns-01) challenges, the value can be used as-is.
- - "For V(tls-alpn-01) challenges, note that this return value contains a
- Base64 encoded version of the correct binary blob which has to be put
- into the acmeValidation x509 extension; see
- U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3)
- for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter
- to extract the binary blob from this return value."
+ - For every identifier, provides a dictionary of challenge types mapping to challenge data.
+ - The keys in this dictionary are the identifiers. C(identifier) is a placeholder used in the documentation.
+ - Note that the keys are not valid Jinja2 identifiers.
returned: changed
- type: str
- sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA
- record:
- description: The full DNS record's name for the challenge.
- returned: changed and challenge is V(dns-01)
- type: str
- sample: _acme-challenge.example.com
+ type: dict
+ contains:
+ challenge-type:
+ description:
+ - Data for every challenge type.
+ - The keys in this dictionary are the challenge types. C(challenge-type) is a placeholder used in the documentation.
+ Possible keys are V(http-01), V(dns-01), and V(tls-alpn-01).
+ - Note that the keys are not valid Jinja2 identifiers.
+ returned: changed
+ type: dict
+ contains:
+ resource:
+ description: The challenge resource that must be created for validation.
+ returned: changed
+ type: str
+ sample: .well-known/acme-challenge/evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA
+ resource_original:
+ description:
+ - The original challenge resource including type identifier for V(tls-alpn-01)
+ challenges.
+ returned: changed and O(challenge) is V(tls-alpn-01)
+ type: str
+ sample: DNS:example.com
+ resource_value:
+ description:
+ - The value the resource has to produce for the validation.
+ - For V(http-01) and V(dns-01) challenges, the value can be used as-is.
+ - "For V(tls-alpn-01) challenges, note that this return value contains a
+ Base64 encoded version of the correct binary blob which has to be put
+ into the acmeValidation x509 extension; see
+ U(https://www.rfc-editor.org/rfc/rfc8737.html#section-3)
+ for details. To do this, you might need the P(ansible.builtin.b64decode#filter) Jinja filter
+ to extract the binary blob from this return value."
+ returned: changed
+ type: str
+ sample: IlirfxKKXA...17Dt3juxGJ-PCt92wr-oA
+ record:
+ description: The full DNS record's name for the challenge.
+ returned: changed and challenge is V(dns-01)
+ type: str
+ sample: _acme-challenge.example.com
challenge_data_dns:
description:
- List of TXT values per DNS record, in case challenge is V(dns-01).
@@ -547,11 +569,9 @@ all_chains:
import os
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -585,6 +605,7 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.orders impor
)
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
+ compute_cert_id,
pem_to_der,
)
@@ -621,6 +642,7 @@ class ACMECertificateClient(object):
self.order_uri = self.data.get('order_uri') if self.data else None
self.all_chains = None
self.select_chain_matcher = []
+ self.include_renewal_cert_id = module.params['include_renewal_cert_id']
if self.module.params['select_chain']:
for criterium_idx, criterium in enumerate(self.module.params['select_chain']):
@@ -678,6 +700,15 @@ class ACMECertificateClient(object):
# stored in self.order_uri by the constructor).
return self.order_uri is None
+ def _get_cert_info_or_none(self):
+ if self.module.params.get('dest'):
+ filename = self.module.params['dest']
+ else:
+ filename = self.module.params['fullchain_dest']
+ if not os.path.exists(filename):
+ return None
+ return self.client.backend.get_cert_information(cert_filename=filename)
+
def start_challenges(self):
'''
Create new authorizations for all identifiers of the CSR,
@@ -692,7 +723,19 @@ class ACMECertificateClient(object):
authz = Authorization.create(self.client, identifier_type, identifier)
self.authorizations[authz.combined_identifier] = authz
else:
- self.order = Order.create(self.client, self.identifiers)
+ replaces_cert_id = None
+ if (
+ self.include_renewal_cert_id == 'always' or
+ (self.include_renewal_cert_id == 'when_ari_supported' and self.client.directory.has_renewal_info_endpoint())
+ ):
+ cert_info = self._get_cert_info_or_none()
+ if cert_info is not None:
+ replaces_cert_id = compute_cert_id(
+ self.client.backend,
+ cert_info=cert_info,
+ none_if_required_information_is_missing=True,
+ )
+ self.order = Order.create(self.client, self.identifiers, replaces_cert_id)
self.order_uri = self.order.url
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
@@ -854,15 +897,14 @@ class ACMECertificateClient(object):
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec(with_certificate=True)
+ argument_spec.argument_spec['csr']['aliases'] = ['src']
+ argument_spec.update_argspec(
modify_account=dict(type='bool', default=True),
account_email=dict(type='str'),
agreement=dict(type='str'),
terms_agreed=dict(type='bool', default=False),
challenge=dict(type='str', default='http-01', choices=['http-01', 'dns-01', 'tls-alpn-01', NO_CHALLENGE]),
- csr=dict(type='path', aliases=['src']),
- csr_content=dict(type='str'),
data=dict(type='dict'),
dest=dict(type='path', aliases=['cert']),
fullchain_dest=dict(type='path', aliases=['fullchain']),
@@ -878,20 +920,14 @@ def main():
subject_key_identifier=dict(type='str'),
authority_key_identifier=dict(type='str'),
)),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- required_one_of=(
- ['account_key_src', 'account_key_content'],
+ include_renewal_cert_id=dict(type='str', choices=['never', 'when_ari_supported', 'always'], default='never'),
+ )
+ argument_spec.update(
+ required_one_of=[
['dest', 'fullchain_dest'],
- ['csr', 'csr_content'],
- ),
- mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
- ['csr', 'csr_content'],
- ),
- supports_check_mode=True,
+ ],
)
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, False)
try:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py
new file mode 100644
index 000000000..133f777d6
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate_deactivate_authz.py
@@ -0,0 +1,119 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# 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
+
+
+DOCUMENTATION = '''
+---
+module: acme_certificate_deactivate_authz
+author: "Felix Fontein (@felixfontein)"
+version_added: 2.20.0
+short_description: Deactivate all authz for an ACME v2 order
+description:
+ - "Deactivate all authentication objects (authz) for an ACME v2 order,
+ which effectively deactivates (invalidates) the order itself."
+ - "Authentication objects are bound to an account key and remain valid
+ for a certain amount of time, and can be used to issue certificates
+ without having to re-authenticate the domain. This can be a security
+ concern."
+ - "Another reason to use this module is to deactivate an order whose
+ processing failed when using O(community.crypto.acme_certificate#module:include_renewal_cert_id)."
+seealso:
+ - module: community.crypto.acme_certificate
+extends_documentation_fragment:
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
+ - community.crypto.attributes
+ - community.crypto.attributes.actiongroup_acme
+attributes:
+ check_mode:
+ support: full
+ diff_mode:
+ support: none
+options:
+ order_uri:
+ description:
+ - The ACME v2 order to deactivate.
+ - Can be obtained from RV(community.crypto.acme_certificate#module:order_uri).
+ type: str
+ required: true
+'''
+
+EXAMPLES = r'''
+- name: Deactivate all authzs for an order
+ community.crypto.acme_certificate_deactivate_authz:
+ account_key_content: "{{ account_private_key }}"
+ order_uri: "{{ certificate_result.order_uri }}"
+'''
+
+RETURN = '''#'''
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
+ create_backend,
+ create_default_argspec,
+ ACMEClient,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.account import (
+ ACMEAccount,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.errors import (
+ ModuleFailException,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.orders import (
+ Order,
+)
+
+
+def main():
+ argument_spec = create_default_argspec()
+ argument_spec.update_argspec(
+ order_uri=dict(type='str', required=True),
+ )
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
+ if module.params['acme_version'] == 1:
+ module.fail_json('The module does not support acme_version=1')
+
+ backend = create_backend(module, False)
+
+ try:
+ client = ACMEClient(module, backend)
+ account = ACMEAccount(client)
+
+ dummy, account_data = account.setup_account(allow_creation=False)
+ if account_data is None:
+ raise ModuleFailException(msg='Account does not exist or is deactivated.')
+
+ order = Order.from_url(client, module.params['order_uri'])
+ order.load_authorizations(client)
+
+ changed = False
+ for authz in order.authorizations.values():
+ if not authz.can_deactivate():
+ continue
+ changed = True
+ if module.check_mode:
+ continue
+ try:
+ authz.deactivate(client)
+ except Exception:
+ # ignore errors
+ pass
+ if authz.status != 'deactivated':
+ module.warn(warning='Could not deactivate authz object {0}.'.format(authz.url))
+
+ module.exit_json(changed=changed)
+ except ModuleFailException as e:
+ e.do_fail(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py
new file mode 100644
index 000000000..1e2b16918
--- /dev/null
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate_renewal_info.py
@@ -0,0 +1,245 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2018 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 = '''
+---
+module: acme_certificate_renewal_info
+author: "Felix Fontein (@felixfontein)"
+version_added: 2.20.0
+short_description: Determine whether a certificate should be renewed or not
+description:
+ - Uses various information to determine whether a certificate should be renewed or not.
+ - If available, the ARI extension (ACME Renewal Information, U(https://datatracker.ietf.org/doc/draft-ietf-acme-ari/))
+ is used. This module implements version 3 of the ARI draft."
+extends_documentation_fragment:
+ - community.crypto.acme.basic
+ - community.crypto.acme.no_account
+ - community.crypto.attributes
+ - community.crypto.attributes.info_module
+options:
+ certificate_path:
+ description:
+ - A path to the X.509 certificate to determine renewal of.
+ - In case the certificate does not exist, the module will always return RV(should_renew=true).
+ - O(certificate_path) and O(certificate_content) are mutually exclusive.
+ type: path
+ certificate_content:
+ description:
+ - The content of the X.509 certificate to determine renewal of.
+ - O(certificate_path) and O(certificate_content) are mutually exclusive.
+ type: str
+ use_ari:
+ description:
+ - Whether to use ARI information, if available.
+ - Set this to V(false) if the ACME server implements ARI in a way that is incompatible with this module.
+ type: bool
+ default: true
+ ari_algorithm:
+ description:
+ - If ARI information is used, selects which algorithm is used to determine whether to renew now.
+ - V(standard) selects the L(algorithm provided in the the ARI specification,
+ https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#name-renewalinfo-objects).
+ - V(start) returns RV(should_renew=true) once the start of the renewal interval has been reached.
+ type: str
+ choices:
+ - standard
+ - start
+ default: standard
+ remaining_days:
+ description:
+ - The number of days the certificate must have left being valid.
+ - For example, if O(remaining_days=20), this check causes RV(should_renew=true) if the
+ certificate is valid for less than 20 days.
+ type: int
+ remaining_percentage:
+ description:
+ - The percentage of the certificate's validity period that should be left.
+ - For example, if O(remaining_percentage=0.1), and the certificate's validity period is 90 days,
+ this check causes RV(should_renew=true) if the certificate is valid for less than 9 days.
+ - Must be a value between 0 and 1.
+ type: float
+ now:
+ description:
+ - Use this timestamp instead of the current timestamp to determine whether a certificate should be renewed.
+ - 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]) (for example V(+32w1d2h)).
+ type: str
+seealso:
+ - module: community.crypto.acme_certificate
+ description: Allows to obtain a certificate using the ACME protocol
+ - module: community.crypto.acme_ari_info
+ description: Obtain renewal information for a certificate
+'''
+
+EXAMPLES = '''
+- name: Retrieve renewal information for a certificate
+ community.crypto.acme_certificate_renewal_info:
+ certificate_path: /etc/httpd/ssl/sample.com.crt
+ register: cert_data
+
+- name: Should the certificate be renewed?
+ ansible.builtin.debug:
+ var: cert_data.should_renew
+'''
+
+RETURN = '''
+should_renew:
+ description:
+ - Whether the certificate should be renewed.
+ - If no certificate is provided, or the certificate is expired, will always be V(true).
+ returned: success
+ type: bool
+ sample: true
+
+msg:
+ description:
+ - Information on the reason for renewal.
+ - Should be shown to the user, as in case of ARI triggered renewal it can contain important
+ information, for example on forced revocations for misissued certificates.
+ type: str
+ returned: success
+ sample: The certificate does not exist.
+
+supports_ari:
+ description:
+ - Whether ARI information was used to determine renewal. This can be used to determine whether to
+ specify O(community.crypto.acme_certificate#module:include_renewal_cert_id=when_ari_supported)
+ for the M(community.crypto.acme_certificate) module.
+ - If O(use_ari=false), this will always be V(false).
+ returned: success
+ type: bool
+ sample: true
+
+cert_id:
+ description:
+ - The certificate ID according to the L(ARI specification, https://www.ietf.org/archive/id/draft-ietf-acme-ari-03.html#section-4.1).
+ returned: success, the certificate exists, and has an Authority Key Identifier X.509 extension
+ type: str
+ sample: aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE
+'''
+
+import os
+import random
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
+ create_backend,
+ create_default_argspec,
+ ACMEClient,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.errors import ModuleFailException
+
+from ansible_collections.community.crypto.plugins.module_utils.acme.utils import compute_cert_id
+
+
+def main():
+ argument_spec = create_default_argspec(with_account=False)
+ argument_spec.update_argspec(
+ certificate_path=dict(type='path'),
+ certificate_content=dict(type='str'),
+ use_ari=dict(type='bool', default=True),
+ ari_algorithm=dict(type='str', choices=['standard', 'start'], default='standard'),
+ remaining_days=dict(type='int'),
+ remaining_percentage=dict(type='float'),
+ now=dict(type='str'),
+ )
+ argument_spec.update(
+ mutually_exclusive=(
+ ['certificate_path', 'certificate_content'],
+ ),
+ )
+ module = argument_spec.create_ansible_module(supports_check_mode=True)
+ backend = create_backend(module, True)
+
+ result = dict(
+ changed=False,
+ msg='The certificate is still valid and no condition was reached',
+ supports_ari=False,
+ )
+
+ def complete(should_renew, **kwargs):
+ result['should_renew'] = should_renew
+ result.update(kwargs)
+ module.exit_json(**result)
+
+ if not module.params['certificate_path'] and not module.params['certificate_content']:
+ complete(True, msg='No certificate was specified')
+
+ if module.params['certificate_path'] is not None and not os.path.exists(module.params['certificate_path']):
+ complete(True, msg='The certificate file does not exist')
+
+ try:
+ cert_info = backend.get_cert_information(
+ cert_filename=module.params['certificate_path'],
+ cert_content=module.params['certificate_content'],
+ )
+ cert_id = compute_cert_id(backend, cert_info=cert_info, none_if_required_information_is_missing=True)
+ if cert_id is not None:
+ result['cert_id'] = cert_id
+
+ if module.params['now']:
+ now = backend.parse_module_parameter(module.params['now'], 'now')
+ else:
+ now = backend.get_now()
+
+ if now >= cert_info.not_valid_after:
+ complete(True, msg='The certificate has already expired')
+
+ client = ACMEClient(module, backend)
+ if cert_id is not None and module.params['use_ari'] and client.directory.has_renewal_info_endpoint():
+ renewal_info = client.get_renewal_info(cert_id=cert_id)
+ window_start = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['start'])
+ window_end = backend.parse_acme_timestamp(renewal_info['suggestedWindow']['end'])
+ msg_append = ''
+ if 'explanationURL' in renewal_info:
+ msg_append = '. Information on renewal interval: {0}'.format(renewal_info['explanationURL'])
+ result['supports_ari'] = True
+ if now > window_end:
+ complete(True, msg='The suggested renewal interval provided by ARI is in the past{0}'.format(msg_append))
+ if module.params['ari_algorithm'] == 'start':
+ if now > window_start:
+ complete(True, msg='The suggested renewal interval provided by ARI has begun{0}'.format(msg_append))
+ else:
+ random_time = backend.interpolate_timestamp(window_start, window_end, random.random())
+ if now > random_time:
+ complete(
+ True,
+ msg='The picked random renewal time {0} in sugested renewal internal provided by ARI is in the past{1}'.format(
+ random_time,
+ msg_append,
+ ),
+ )
+
+ if module.params['remaining_days'] is not None:
+ remaining_days = (cert_info.not_valid_after - now).days
+ if remaining_days < module.params['remaining_days']:
+ complete(True, msg='The certificate expires in {0} days'.format(remaining_days))
+
+ if module.params['remaining_percentage'] is not None:
+ timestamp = backend.interpolate_timestamp(cert_info.not_valid_before, cert_info.not_valid_after, 1 - module.params['remaining_percentage'])
+ if timestamp < now:
+ complete(
+ True,
+ msg="The remaining percentage {0}% of the certificate's lifespan was reached on {1}".format(
+ module.params['remaining_percentage'] * 100,
+ timestamp,
+ ),
+ )
+
+ complete(False)
+ except ModuleFailException as e:
+ e.do_fail(module)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py b/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py
index 022862e60..2661a1525 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_certificate_revoke.py
@@ -37,7 +37,8 @@ seealso:
- module: community.crypto.acme_inspect
description: Allows to debug problems.
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
@@ -127,11 +128,9 @@ EXAMPLES = '''
RETURN = '''#'''
-from ansible.module_utils.basic import AnsibleModule
-
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -152,24 +151,23 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.utils import
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec(require_account_key=False)
+ argument_spec.update_argspec(
private_key_src=dict(type='path'),
private_key_content=dict(type='str', no_log=True),
private_key_passphrase=dict(type='str', no_log=True),
certificate=dict(type='path', required=True),
revoke_reason=dict(type='int'),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
+ )
+ argument_spec.update(
required_one_of=(
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
),
mutually_exclusive=(
['account_key_src', 'account_key_content', 'private_key_src', 'private_key_content'],
),
- supports_check_mode=False,
)
+ module = argument_spec.create_ansible_module()
backend = create_backend(module, False)
try:
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py b/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py
index 48b65f998..edd2c3331 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_challenge_cert_helper.py
@@ -165,16 +165,16 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.io import (
read_file,
)
-from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
- get_now_datetime,
-)
-
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
set_not_valid_after,
set_not_valid_before,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_now_datetime,
+)
+
CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography
diff --git a/ansible_collections/community/crypto/plugins/modules/acme_inspect.py b/ansible_collections/community/crypto/plugins/modules/acme_inspect.py
index a2c76507e..c7ee49765 100644
--- a/ansible_collections/community/crypto/plugins/modules/acme_inspect.py
+++ b/ansible_collections/community/crypto/plugins/modules/acme_inspect.py
@@ -42,7 +42,8 @@ seealso:
description: The specification of the C(tls-alpn-01) challenge (RFC 8737).
link: https://www.rfc-editor.org/rfc/rfc8737.html
extends_documentation_fragment:
- - community.crypto.acme
+ - community.crypto.acme.basic
+ - community.crypto.acme.account
- community.crypto.attributes
- community.crypto.attributes.actiongroup_acme
attributes:
@@ -247,12 +248,11 @@ output_json:
- ...
'''
-from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text
from ansible_collections.community.crypto.plugins.module_utils.acme.acme import (
create_backend,
- get_default_argspec,
+ create_default_argspec,
ACMEClient,
)
@@ -263,18 +263,14 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
def main():
- argument_spec = get_default_argspec()
- argument_spec.update(dict(
+ argument_spec = create_default_argspec(require_account_key=False)
+ argument_spec.update_argspec(
url=dict(type='str'),
method=dict(type='str', choices=['get', 'post', 'directory-only'], default='get'),
content=dict(type='str'),
fail_on_acme_error=dict(type='bool', default=True),
- ))
- module = AnsibleModule(
- argument_spec=argument_spec,
- mutually_exclusive=(
- ['account_key_src', 'account_key_content'],
- ),
+ )
+ argument_spec.update(
required_if=(
['method', 'get', ['url']],
['method', 'post', ['url', 'content']],
@@ -282,6 +278,7 @@ def main():
['method', 'post', ['account_key_src', 'account_key_content'], True],
),
)
+ module = argument_spec.create_ansible_module()
backend = create_backend(module, False)
result = dict()
diff --git a/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py b/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py
index 2c1238d48..0276556ab 100644
--- a/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py
+++ b/ansible_collections/community/crypto/plugins/modules/ecs_certificate.py
@@ -938,8 +938,8 @@ def main():
module.fail_json(msg='The cert_expiry field is invalid when request_type="reissue".')
elif module.params['cert_lifetime']:
module.fail_json(msg='The cert_lifetime field is invalid when request_type="reissue".')
- # Only a reissued request can omit the CSR
- else:
+ # Reissued or renew request can omit the CSR
+ elif module.params['request_type'] != 'renew':
module_params_csr = module.params['csr']
if module_params_csr is None:
module.fail_json(msg='The csr field is required when request_type={0}'.format(module.params['request_type']))
diff --git a/ansible_collections/community/crypto/plugins/modules/get_certificate.py b/ansible_collections/community/crypto/plugins/modules/get_certificate.py
index 6ae9439d3..d4b38afbd 100644
--- a/ansible_collections/community/crypto/plugins/modules/get_certificate.py
+++ b/ansible_collections/community/crypto/plugins/modules/get_certificate.py
@@ -220,10 +220,6 @@ 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.support import (
- get_now_datetime,
-)
-
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
cryptography_oid_to_name,
@@ -232,6 +228,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
get_not_valid_before,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_now_datetime,
+)
+
MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'
CREATE_DEFAULT_CONTEXT_IMP_ERR = None
diff --git a/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py b/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py
index 8379937f7..9e8c20e29 100644
--- a/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py
+++ b/ansible_collections/community/crypto/plugins/modules/x509_certificate_info.py
@@ -406,10 +406,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.basic impo
OpenSSLObjectError,
)
-from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
- get_relative_time_option,
-)
-
from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
CRYPTOGRAPHY_TIMEZONE,
)
@@ -418,6 +414,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
select_backend,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_relative_time_option,
+)
+
def main():
module = AnsibleModule(
diff --git a/ansible_collections/community/crypto/plugins/modules/x509_crl.py b/ansible_collections/community/crypto/plugins/modules/x509_crl.py
index 527975b88..f8eb8d85e 100644
--- a/ansible_collections/community/crypto/plugins/modules/x509_crl.py
+++ b/ansible_collections/community/crypto/plugins/modules/x509_crl.py
@@ -470,7 +470,6 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
load_certificate,
parse_name_field,
parse_ordered_name_field,
- get_relative_time_option,
select_message_digest,
)
@@ -506,6 +505,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.module_bac
get_crl_info,
)
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ get_relative_time_option,
+)
+
MINIMAL_CRYPTOGRAPHY_VERSION = '1.2'
CRYPTOGRAPHY_IMP_ERR = None
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases
new file mode 100644
index 000000000..b7f6d4f48
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/aliases
@@ -0,0 +1,10 @@
+# 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
+
+azp/generic/1
+azp/posix/1
+cloud/acme
+
+# For some reason connecting to helper containers does not work on the Alpine VMs
+skip/alpine
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml
new file mode 100644
index 000000000..2e8ad10b8
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/meta/main.yml
@@ -0,0 +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
+
+dependencies:
+ - setup_acme
+ - setup_remote_tmp_dir
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml
new file mode 100644
index 000000000..28a889684
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/impl.yml
@@ -0,0 +1,154 @@
+---
+# 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
+
+- vars:
+ certificate_name: cert-1
+ subject_alt_name: DNS:example.com
+ account_email: example@example.org
+ block:
+ - name: Generate account key
+ openssl_privatekey:
+ path: "{{ remote_tmp_dir }}/account-ec256.pem"
+ type: ECC
+ curve: secp256r1
+ force: true
+ - name: Create cert private key
+ openssl_privatekey:
+ path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key"
+ type: ECC
+ curve: secp256r1
+ force: true
+ - name: Create cert CSR
+ openssl_csr:
+ path: "{{ remote_tmp_dir }}/{{ certificate_name }}.csr"
+ privatekey_path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key"
+ subject_alt_name: "{{ subject_alt_name }}"
+ - name: Start process of obtaining certificate
+ acme_certificate:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ modify_account: true
+ csr: "{{ remote_tmp_dir }}/{{ certificate_name }}.csr"
+ dest: "{{ remote_tmp_dir }}/{{ certificate_name }}.pem"
+ challenge: http-01
+ force: true
+ terms_agreed: true
+ account_email: "{{ account_email }}"
+ register: certificate_data
+
+- name: Inspect order
+ acme_inspect:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ url: "{{ certificate_data.order_uri }}"
+ method: get
+ register: order_1
+- name: Show order
+ debug:
+ var: order_1.output_json
+
+- name: Deactivate order (check mode)
+ acme_certificate_deactivate_authz:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ order_uri: "{{ certificate_data.order_uri }}"
+ check_mode: true
+ register: deactivate_1
+
+- name: Inspect order again
+ acme_inspect:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ url: "{{ certificate_data.order_uri }}"
+ method: get
+ register: order_2
+- name: Show order
+ debug:
+ var: order_2.output_json
+
+- name: Deactivate order
+ acme_certificate_deactivate_authz:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ order_uri: "{{ certificate_data.order_uri }}"
+ register: deactivate_2
+
+- name: Inspect order again
+ acme_inspect:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ url: "{{ certificate_data.order_uri }}"
+ method: get
+ register: order_3
+- name: Show order
+ debug:
+ var: order_3.output_json
+
+- name: Deactivate order (check mode, idempotent)
+ acme_certificate_deactivate_authz:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ order_uri: "{{ certificate_data.order_uri }}"
+ check_mode: true
+ register: deactivate_3
+
+- name: Inspect order again
+ acme_inspect:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ url: "{{ certificate_data.order_uri }}"
+ method: get
+ register: order_4
+- name: Show order
+ debug:
+ var: order_4.output_json
+
+- name: Deactivate order (idempotent)
+ acme_certificate_deactivate_authz:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ order_uri: "{{ certificate_data.order_uri }}"
+ register: deactivate_4
+
+- name: Inspect order again
+ acme_inspect:
+ acme_directory: https://{{ acme_host }}:14000/dir
+ acme_version: 2
+ validate_certs: false
+ account_key_src: "{{ remote_tmp_dir }}/account-ec256.pem"
+ account_uri: "{{ certificate_data.account_uri }}"
+ url: "{{ certificate_data.order_uri }}"
+ method: get
+ register: order_5
+- name: Show order
+ debug:
+ var: order_5.output_json
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml
new file mode 100644
index 000000000..68d47973d
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+# 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
+
+####################################################################
+# WARNING: These are designed specifically for Ansible tests #
+# and should not be used as examples of how to write Ansible roles #
+####################################################################
+
+- block:
+ - name: Running tests with OpenSSL backend
+ include_tasks: impl.yml
+ vars:
+ select_crypto_backend: openssl
+
+ - import_tasks: ../tests/validate.yml
+
+ # Old 0.9.8 versions have insufficient CLI support for signing with EC keys
+ when: openssl_version.stdout is version('1.0.0', '>=')
+
+- name: Remove output directory
+ file:
+ path: "{{ remote_tmp_dir }}"
+ state: absent
+
+- name: Re-create output directory
+ file:
+ path: "{{ remote_tmp_dir }}"
+ state: directory
+
+- block:
+ - name: Running tests with cryptography backend
+ include_tasks: impl.yml
+ vars:
+ select_crypto_backend: cryptography
+
+ - import_tasks: ../tests/validate.yml
+
+ when: cryptography_version.stdout is version('1.5', '>=')
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml
new file mode 100644
index 000000000..603c7d7cc
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_deactivate_authz/tests/validate.yml
@@ -0,0 +1,17 @@
+---
+# 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: Checks
+ assert:
+ that:
+ - order_1.output_json.status == 'pending'
+ - deactivate_1 is changed
+ - order_2.output_json.status == 'pending'
+ - deactivate_2 is changed
+ - order_3.output_json.status == 'deactivated'
+ - deactivate_3 is not changed
+ - order_4.output_json.status == 'deactivated'
+ - deactivate_4 is not changed
+ - order_5.output_json.status == 'deactivated'
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases
new file mode 100644
index 000000000..b7f6d4f48
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/aliases
@@ -0,0 +1,10 @@
+# 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
+
+azp/generic/1
+azp/posix/1
+cloud/acme
+
+# For some reason connecting to helper containers does not work on the Alpine VMs
+skip/alpine
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml
new file mode 100644
index 000000000..2e8ad10b8
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/meta/main.yml
@@ -0,0 +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
+
+dependencies:
+ - setup_acme
+ - setup_remote_tmp_dir
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml
new file mode 100644
index 000000000..b30808ed5
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/impl.yml
@@ -0,0 +1,145 @@
+---
+# 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
+
+## SET UP ACCOUNT KEYS ########################################################################
+- block:
+ - name: Generate account keys
+ openssl_privatekey:
+ path: "{{ remote_tmp_dir }}/{{ item.name }}.pem"
+ type: "{{ item.type }}"
+ size: "{{ item.size | default(omit) }}"
+ curve: "{{ item.curve | default(omit) }}"
+ force: true
+ loop: "{{ account_keys }}"
+
+ vars:
+ account_keys:
+ - name: account-ec256
+ type: ECC
+ curve: secp256r1
+## CREATE ACCOUNTS AND OBTAIN CERTIFICATES ####################################################
+- name: Obtain cert 1
+ include_tasks: obtain-cert.yml
+ vars:
+ certgen_title: Certificate 1 for renewal check
+ certificate_name: cert-1
+ key_type: rsa
+ rsa_bits: "{{ default_rsa_key_size }}"
+ subject_alt_name: "DNS:example.com"
+ subject_alt_name_critical: false
+ account_key: account-ec256
+ challenge: http-01
+ modify_account: true
+ deactivate_authzs: false
+ force: true
+ remaining_days: "{{ omit }}"
+ terms_agreed: true
+ account_email: "example@example.org"
+## OBTAIN CERTIFICATE INFOS ###################################################################
+- name: Dump OpenSSL x509 info
+ command:
+ cmd: openssl x509 -in {{ remote_tmp_dir }}/cert-1.pem -noout -text
+- name: Obtain certificate information
+ x509_certificate_info:
+ path: "{{ remote_tmp_dir }}/cert-1.pem"
+ register: cert_1_info
+- name: Read certificate
+ slurp:
+ src: '{{ remote_tmp_dir }}/cert-1.pem'
+ register: slurp_cert_1
+- name: Obtain certificate information (1/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ # Certificate is valid for ~1826 days
+ register: cert_1_renewal_1
+- name: Obtain certificate information (2/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ # Certificate is valid for ~1826 days
+ remaining_days: 1000
+ remaining_percentage: 0.5
+ register: cert_1_renewal_2
+- name: Obtain certificate information (3/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_content: "{{ slurp_cert_1.content | b64decode }}"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ now: +1800d
+ # Certificate is valid for ~26 days
+ register: cert_1_renewal_3
+- name: Obtain certificate information (4/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ now: +1800d
+ # Certificate is valid for ~26 days
+ remaining_days: 30
+ remaining_percentage: 0.1
+ register: cert_1_renewal_4
+- name: Obtain certificate information (5/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ now: +1800d
+ # Certificate is valid for ~26 days
+ remaining_days: 30
+ remaining_percentage: 0.01
+ register: cert_1_renewal_5
+- name: Obtain certificate information (6/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ now: +1800d
+ # Certificate is valid for ~26 days
+ remaining_days: 10
+ remaining_percentage: 0.03
+ register: cert_1_renewal_6
+- name: Obtain certificate information (7/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-1.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ now: +1830d
+ # Certificate is no longer valid
+ register: cert_1_renewal_7
+- name: Obtain certificate information (8/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ now: +1830d
+ # Certificate is no longer valid
+ register: cert_1_renewal_8
+- name: Obtain certificate information (9/9)
+ acme_certificate_renewal_info:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ certificate_path: "{{ remote_tmp_dir }}/cert-does-not-exist.pem"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ # Certificate is no longer valid
+ register: cert_1_renewal_9
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml
new file mode 100644
index 000000000..68d47973d
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/main.yml
@@ -0,0 +1,40 @@
+---
+# 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
+
+####################################################################
+# WARNING: These are designed specifically for Ansible tests #
+# and should not be used as examples of how to write Ansible roles #
+####################################################################
+
+- block:
+ - name: Running tests with OpenSSL backend
+ include_tasks: impl.yml
+ vars:
+ select_crypto_backend: openssl
+
+ - import_tasks: ../tests/validate.yml
+
+ # Old 0.9.8 versions have insufficient CLI support for signing with EC keys
+ when: openssl_version.stdout is version('1.0.0', '>=')
+
+- name: Remove output directory
+ file:
+ path: "{{ remote_tmp_dir }}"
+ state: absent
+
+- name: Re-create output directory
+ file:
+ path: "{{ remote_tmp_dir }}"
+ state: directory
+
+- block:
+ - name: Running tests with cryptography backend
+ include_tasks: impl.yml
+ vars:
+ select_crypto_backend: cryptography
+
+ - import_tasks: ../tests/validate.yml
+
+ when: cryptography_version.stdout is version('1.5', '>=')
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml
new file mode 100644
index 000000000..6882e5339
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tasks/obtain-cert.yml
@@ -0,0 +1,159 @@
+---
+# 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
+
+## PRIVATE KEY ################################################################################
+- name: ({{ certgen_title }}) Create cert private key
+ openssl_privatekey:
+ path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key"
+ type: "{{ 'RSA' if key_type == 'rsa' else 'ECC' }}"
+ size: "{{ rsa_bits if key_type == 'rsa' else omit }}"
+ curve: >-
+ {{ omit if key_type == 'rsa' else
+ 'secp256r1' if key_type == 'ec256' else
+ 'secp384r1' if key_type == 'ec384' else
+ 'secp521r1' if key_type == 'ec521' else
+ 'invalid value for key_type!' }}
+ passphrase: "{{ certificate_passphrase | default(omit) | default(omit, true) }}"
+ cipher: "{{ 'auto' if certificate_passphrase | default() else omit }}"
+ force: true
+## CSR ########################################################################################
+- name: ({{ certgen_title }}) Create cert CSR
+ openssl_csr:
+ path: "{{ remote_tmp_dir }}/{{ certificate_name }}.csr"
+ privatekey_path: "{{ remote_tmp_dir }}/{{ certificate_name }}.key"
+ privatekey_passphrase: "{{ certificate_passphrase | default(omit) | default(omit, true) }}"
+ subject_alt_name: "{{ subject_alt_name }}"
+ subject_alt_name_critical: "{{ subject_alt_name_critical }}"
+ return_content: true
+ register: csr_result
+## ACME STEP 1 ################################################################################
+- name: ({{ certgen_title }}) Obtain cert, step 1
+ acme_certificate:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ account_key: "{{ (remote_tmp_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}"
+ account_key_content: "{{ account_key_content | default(omit) }}"
+ account_key_passphrase: "{{ account_key_passphrase | default(omit) | default(omit, true) }}"
+ modify_account: "{{ modify_account }}"
+ csr: "{{ omit if use_csr_content | default(false) else remote_tmp_dir ~ '/' ~ certificate_name ~ '.csr' }}"
+ csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}"
+ dest: "{{ remote_tmp_dir }}/{{ certificate_name }}.pem"
+ fullchain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-fullchain.pem"
+ chain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-chain.pem"
+ challenge: "{{ challenge }}"
+ deactivate_authzs: "{{ deactivate_authzs }}"
+ force: "{{ force }}"
+ remaining_days: "{{ remaining_days }}"
+ terms_agreed: "{{ terms_agreed }}"
+ account_email: "{{ account_email }}"
+ register: challenge_data
+- name: ({{ certgen_title }}) Print challenge data
+ debug:
+ var: challenge_data
+- name: ({{ certgen_title }}) Create HTTP challenges
+ uri:
+ url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}"
+ method: PUT
+ body_format: raw
+ body: "{{ item.value['http-01'].resource_value }}"
+ headers:
+ content-type: "application/octet-stream"
+ with_dict: "{{ challenge_data.challenge_data }}"
+ when: "challenge_data is changed and challenge == 'http-01'"
+- name: ({{ certgen_title }}) Create DNS challenges
+ uri:
+ url: "http://{{ acme_host }}:5000/dns/{{ item.key }}"
+ method: PUT
+ body_format: json
+ body: "{{ item.value }}"
+ with_dict: "{{ challenge_data.challenge_data_dns }}"
+ when: "challenge_data is changed and challenge == 'dns-01'"
+- name: ({{ certgen_title }}) Create TLS ALPN challenges (acme_challenge_cert_helper)
+ acme_challenge_cert_helper:
+ challenge: tls-alpn-01
+ challenge_data: "{{ item.value['tls-alpn-01'] }}"
+ private_key_src: "{{ remote_tmp_dir }}/{{ certificate_name }}.key"
+ private_key_passphrase: "{{ certificate_passphrase | default(omit) | default(omit, true) }}"
+ with_dict: "{{ challenge_data.challenge_data if challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper') else {} }}"
+ register: tls_alpn_challenges
+ when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper')"
+- name: ({{ certgen_title }}) Read private key
+ slurp:
+ src: '{{ remote_tmp_dir }}/{{ certificate_name }}.key'
+ register: slurp
+ when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper')"
+- name: ({{ certgen_title }}) Set TLS ALPN challenges (acme_challenge_cert_helper)
+ uri:
+ url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.domain }}/{{ item.identifier }}/certificate-and-key"
+ method: PUT
+ body_format: raw
+ body: "{{ item.challenge_certificate }}\n{{ slurp.content | b64decode }}"
+ headers:
+ content-type: "application/pem-certificate-chain"
+ with_items: "{{ tls_alpn_challenges.results if challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper') else [] }}"
+ when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'acme_challenge_cert_helper')"
+- name: ({{ certgen_title }}) Create TLS ALPN challenges (der-value-b64)
+ uri:
+ url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}/{{ item.value['tls-alpn-01'].resource_original }}/der-value-b64"
+ method: PUT
+ body_format: raw
+ body: "{{ item.value['tls-alpn-01'].resource_value }}"
+ headers:
+ content-type: "application/octet-stream"
+ with_dict: "{{ challenge_data.challenge_data if challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'der-value-b64') else {} }}"
+ when: "challenge_data is changed and challenge == 'tls-alpn-01' and (challenge_alpn_tls | default('der-value-b64') == 'der-value-b64')"
+## ACME STEP 2 ################################################################################
+- name: ({{ certgen_title }}) Obtain cert, step 2
+ acme_certificate:
+ select_crypto_backend: "{{ select_crypto_backend }}"
+ acme_version: 2
+ acme_directory: https://{{ acme_host }}:14000/dir
+ validate_certs: false
+ account_key: "{{ (remote_tmp_dir ~ '/' ~ account_key ~ '.pem') if account_key_content is not defined else omit }}"
+ account_key_content: "{{ account_key_content | default(omit) }}"
+ account_key_passphrase: "{{ account_key_passphrase | default(omit) | default(omit, true) }}"
+ account_uri: "{{ challenge_data.account_uri }}"
+ modify_account: "{{ modify_account }}"
+ csr: "{{ omit if use_csr_content | default(false) else remote_tmp_dir ~ '/' ~ certificate_name ~ '.csr' }}"
+ csr_content: "{{ csr_result.csr if use_csr_content | default(false) else omit }}"
+ dest: "{{ remote_tmp_dir }}/{{ certificate_name }}.pem"
+ fullchain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-fullchain.pem"
+ chain_dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-chain.pem"
+ challenge: "{{ challenge }}"
+ deactivate_authzs: "{{ deactivate_authzs }}"
+ force: "{{ force }}"
+ remaining_days: "{{ remaining_days }}"
+ terms_agreed: "{{ terms_agreed }}"
+ account_email: "{{ account_email }}"
+ data: "{{ challenge_data }}"
+ retrieve_all_alternates: "{{ retrieve_all_alternates | default(omit) }}"
+ select_chain: "{{ select_chain | default(omit) if select_crypto_backend == 'cryptography' else omit }}"
+ register: certificate_obtain_result
+ when: challenge_data is changed
+- name: ({{ certgen_title }}) Deleting HTTP challenges
+ uri:
+ url: "http://{{ acme_host }}:5000/http/{{ item.key }}/{{ item.value['http-01'].resource[('.well-known/acme-challenge/'|length):] }}"
+ method: DELETE
+ with_dict: "{{ challenge_data.challenge_data }}"
+ when: "challenge_data is changed and challenge == 'http-01'"
+- name: ({{ certgen_title }}) Deleting DNS challenges
+ uri:
+ url: "http://{{ acme_host }}:5000/dns/{{ item.key }}"
+ method: DELETE
+ with_dict: "{{ challenge_data.challenge_data_dns }}"
+ when: "challenge_data is changed and challenge == 'dns-01'"
+- name: ({{ certgen_title }}) Deleting TLS ALPN challenges
+ uri:
+ url: "http://{{ acme_host }}:5000/tls-alpn/{{ item.value['tls-alpn-01'].resource }}"
+ method: DELETE
+ with_dict: "{{ challenge_data.challenge_data }}"
+ when: "challenge_data is changed and challenge == 'tls-alpn-01'"
+- name: ({{ certgen_title }}) Get root certificate
+ get_url:
+ url: "http://{{ acme_host }}:5000/root-certificate-for-ca/{{ acme_expected_root_number | default(0) if select_crypto_backend == 'cryptography' else 0 }}"
+ dest: "{{ remote_tmp_dir }}/{{ certificate_name }}-root.pem"
+###############################################################################################
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml
new file mode 100644
index 000000000..116e524c4
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_certificate_renewal_info/tests/validate.yml
@@ -0,0 +1,47 @@
+---
+# 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: Validate results
+ assert:
+ that:
+ - cert_1_renewal_1.should_renew == false
+ - cert_1_renewal_1.msg == 'The certificate is still valid and no condition was reached'
+ - cert_1_renewal_1.supports_ari == supports_ari
+ - cert_1_renewal_1.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_2.should_renew == false
+ - cert_1_renewal_2.msg == 'The certificate is still valid and no condition was reached'
+ - cert_1_renewal_2.supports_ari == supports_ari
+ - cert_1_renewal_2.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_3.should_renew == false
+ - cert_1_renewal_3.msg == 'The certificate is still valid and no condition was reached'
+ - cert_1_renewal_3.supports_ari == supports_ari
+ - cert_1_renewal_3.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_4.should_renew == true
+ - cert_1_renewal_4.msg == 'The certificate expires in 25 days'
+ - cert_1_renewal_4.supports_ari == supports_ari
+ - cert_1_renewal_4.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_5.should_renew == true
+ - cert_1_renewal_5.msg == 'The certificate expires in 25 days'
+ - cert_1_renewal_5.supports_ari == supports_ari
+ - cert_1_renewal_5.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_6.should_renew == true
+ - cert_1_renewal_6.msg.startswith("The remaining percentage 3.0% of the certificate's lifespan was reached on ")
+ - cert_1_renewal_6.supports_ari == supports_ari
+ - cert_1_renewal_6.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_7.should_renew == true
+ - cert_1_renewal_7.msg == 'The certificate has already expired'
+ - cert_1_renewal_7.supports_ari == false
+ - cert_1_renewal_7.cert_id is string or not can_have_cert_id
+ - cert_1_renewal_8.should_renew == true
+ - cert_1_renewal_8.msg == 'No certificate was specified'
+ - cert_1_renewal_8.supports_ari == false
+ - cert_1_renewal_8.cert_id is not defined
+ - cert_1_renewal_9.should_renew == true
+ - cert_1_renewal_9.msg == 'The certificate file does not exist'
+ - cert_1_renewal_9.supports_ari == false
+ - cert_1_renewal_9.cert_id is not defined
+ vars:
+ can_have_cert_id: cert_1_info.authority_key_identifier is string
+ supports_ari: false
diff --git a/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml b/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml
index 4eed1031a..d87501884 100644
--- a/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml
+++ b/ansible_collections/community/crypto/tests/integration/targets/acme_inspect/tasks/impl.yml
@@ -28,6 +28,7 @@
acme_version: 2
validate_certs: false
method: directory-only
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: directory
- debug: var=directory
@@ -40,6 +41,7 @@
url: "{{ directory.directory.newAccount}}"
method: post
content: '{"termsOfServiceAgreed":true}'
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: account_creation
# account_creation.headers.location contains the account URI
# if creation was successful
@@ -54,6 +56,7 @@
account_uri: "{{ account_creation.headers.location }}"
url: "{{ account_creation.headers.location }}"
method: get
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: account_get
- debug: var=account_get
@@ -67,6 +70,7 @@
url: "{{ account_creation.headers.location }}"
method: post
content: '{{ account_info | to_json }}'
+ select_crypto_backend: "{{ select_crypto_backend }}"
vars:
account_info:
# For valid values, see
@@ -86,6 +90,7 @@
url: "{{ directory.directory.newOrder }}"
method: post
content: '{{ create_order | to_json }}'
+ select_crypto_backend: "{{ select_crypto_backend }}"
vars:
create_order:
# For valid values, see
@@ -108,6 +113,7 @@
account_uri: "{{ account_creation.headers.location }}"
url: "{{ new_order.headers.location }}"
method: get
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: order
- debug: var=order
@@ -120,6 +126,7 @@
account_uri: "{{ account_creation.headers.location }}"
url: "{{ item }}"
method: get
+ select_crypto_backend: "{{ select_crypto_backend }}"
loop: "{{ order.output_json.authorizations }}"
register: authz
- debug: var=authz
@@ -133,6 +140,7 @@
account_uri: "{{ account_creation.headers.location }}"
url: "{{ (item.challenges | selectattr('type', 'equalto', 'http-01') | list)[0].url }}"
method: get
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: http01challenge
loop: "{{ authz.results | map(attribute='output_json') | list }}"
- debug: var=http01challenge
@@ -147,6 +155,7 @@
url: "{{ item.url }}"
method: post
content: '{}'
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: activation
loop: "{{ http01challenge.results | map(attribute='output_json') | list }}"
- debug: var=activation
@@ -160,6 +169,7 @@
account_uri: "{{ account_creation.headers.location }}"
url: "{{ item.url }}"
method: get
+ select_crypto_backend: "{{ select_crypto_backend }}"
register: validation_result
loop: "{{ http01challenge.results | map(attribute='output_json') | list }}"
until: "validation_result.output_json.status not in ['pending', 'processing']"
diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml
index 99832a517..4bbd818ee 100644
--- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml
+++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/ownca.yml
@@ -249,10 +249,24 @@
ownca_not_after: 20191023133742Z
path: "{{ remote_tmp_dir }}/ownca_cert3.pem"
csr_path: "{{ remote_tmp_dir }}/csr.csr"
- privatekey_path: "{{ remote_tmp_dir }}/privatekey3.pem"
+ privatekey_path: "{{ remote_tmp_dir }}/privatekey.pem"
+ ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
+ ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
+ select_crypto_backend: '{{ select_crypto_backend }}'
+
+- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with notBefore and notAfter (idempotent)
+ x509_certificate:
+ provider: ownca
+ ownca_not_before: 20181023133742Z
+ ownca_not_after: 20191023133742Z
+ ignore_timestamps: false
+ path: "{{ remote_tmp_dir }}/ownca_cert3.pem"
+ csr_path: "{{ remote_tmp_dir }}/csr.csr"
+ privatekey_path: "{{ remote_tmp_dir }}/privatekey.pem"
ownca_path: '{{ remote_tmp_dir }}/ca_cert.pem'
ownca_privatekey_path: '{{ remote_tmp_dir }}/ca_privatekey.pem'
select_crypto_backend: '{{ select_crypto_backend }}'
+ register: ownca_cert3_idem
- name: (OwnCA, {{select_crypto_backend}}) Create ownca certificate with relative notBefore and notAfter
x509_certificate:
diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml
index a0f23643b..eeea25ddd 100644
--- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml
+++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tasks/selfsigned.yml
@@ -220,6 +220,18 @@
privatekey_path: "{{ remote_tmp_dir }}/privatekey3.pem"
select_crypto_backend: '{{ select_crypto_backend }}'
+- name: (Selfsigned, {{select_crypto_backend}}) Create certificate3 with notBefore and notAfter (idempotent)
+ x509_certificate:
+ provider: selfsigned
+ selfsigned_not_before: 20181023133742Z
+ selfsigned_not_after: 20191023133742Z
+ ignore_timestamps: false
+ path: "{{ remote_tmp_dir }}/cert3.pem"
+ csr_path: "{{ remote_tmp_dir }}/csr3.pem"
+ privatekey_path: "{{ remote_tmp_dir }}/privatekey3.pem"
+ select_crypto_backend: '{{ select_crypto_backend }}'
+ register: cert3_selfsigned_idem
+
- name: (Selfsigned, {{select_crypto_backend}}) Generate privatekey
openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_ecc.pem'
diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml
index ac25b6295..ade7e6f51 100644
--- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml
+++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_ownca.yml
@@ -98,6 +98,11 @@
that:
- ownca_cert3_notAfter.stdout == 'Oct 23 13:37:42 2019'
+- name: (OwnCA validation, {{select_crypto_backend}}) Validate idempotency
+ assert:
+ that:
+ - ownca_cert3_idem is not changed
+
- name: (OwnCA validation, {{select_crypto_backend}}) Validate ownca ECC certificate (test - ownca certificate pubkey)
shell: '{{ openssl_binary }} x509 -noout -pubkey -in {{ remote_tmp_dir }}/ownca_cert_ecc.pem'
register: ownca_cert_ecc_pubkey
diff --git a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml
index c76310437..c7254eb3e 100644
--- a/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml
+++ b/ansible_collections/community/crypto/tests/integration/targets/x509_certificate/tests/validate_selfsigned.yml
@@ -139,6 +139,11 @@
that:
- cert3_notAfter.stdout == 'Oct 23 13:37:42 2019'
+- name: (Selfsigned validation, {{select_crypto_backend}}) Validate idempotency
+ assert:
+ that:
+ - cert3_selfsigned_idem is not changed
+
- name: (Selfsigned validation, {{select_crypto_backend}}) Validate ECC certificate (test - privatekey's pubkey)
shell: '{{ openssl_binary }} ec -pubout -in {{ remote_tmp_dir }}/privatekey_ecc.pem'
register: privatekey_ecc_pubkey
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py
index 988bcdaeb..c4aa09a6a 100644
--- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/backend_data.py
@@ -9,8 +9,10 @@ __metaclass__ = type
import base64
import datetime
import os
+import sys
from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
+ CertificateInformation,
CryptoBackend,
)
@@ -79,6 +81,12 @@ TEST_CSRS = [
TEST_CERT = load_fixture("cert_1.pem")
+TEST_CERT_2 = load_fixture("cert_2.pem")
+
+
+TEST_CERT_OPENSSL_OUTPUT = load_fixture("cert_1.txt") # OpenSSL 3.3.0 output
+TEST_CERT_OPENSSL_OUTPUT_2 = load_fixture("cert_2.txt") # OpenSSL 3.3.0 output
+TEST_CERT_OPENSSL_OUTPUT_2B = load_fixture("cert_2-b.txt") # OpenSSL 1.1.1f output
TEST_CERT_DAYS = [
@@ -88,6 +96,81 @@ TEST_CERT_DAYS = [
]
+TEST_CERT_INFO = CertificateInformation(
+ not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24),
+ not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23),
+ serial_number=1,
+ subject_key_identifier=b'\x98\xD2\xFD\x3C\xCC\xCD\x69\x45\xFB\xE2\x8C\x30\x2C\x54\x62\x18\x34\xB7\x07\x73',
+ authority_key_identifier=None,
+)
+
+
+TEST_CERT_INFO_2 = CertificateInformation(
+ not_valid_before=datetime.datetime(2024, 5, 4, 20, 42, 21),
+ not_valid_after=datetime.datetime(2029, 5, 4, 20, 42, 20),
+ serial_number=4218235397573492796,
+ subject_key_identifier=b'\x17\xE5\x83\x22\x14\xEF\x74\xD3\xBE\x7E\x30\x76\x56\x1F\x51\x74\x65\x1F\xE9\xF0',
+ authority_key_identifier=b'\x13\xC3\x4C\x3E\x59\x45\xDD\xE3\x63\x51\xA3\x46\x80\xC4\x08\xC7\x14\xC0\x64\x4E',
+)
+
+
+TEST_CERT_INFO = [
+ (TEST_CERT, TEST_CERT_INFO, TEST_CERT_OPENSSL_OUTPUT),
+ (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2),
+ (TEST_CERT_2, TEST_CERT_INFO_2, TEST_CERT_OPENSSL_OUTPUT_2B),
+]
+
+
+TEST_PARSE_ACME_TIMESTAMP = [
+ (
+ '2024-01-01T00:11:22Z',
+ dict(year=2024, month=1, day=1, hour=0, minute=11, second=22),
+ ),
+ (
+ '2024-01-01T00:11:22.123Z',
+ dict(year=2024, month=1, day=1, hour=0, minute=11, second=22, microsecond=123000),
+ ),
+ (
+ '2024-04-17T06:54:13.333333334Z',
+ dict(year=2024, month=4, day=17, hour=6, minute=54, second=13, microsecond=333333),
+ ),
+]
+
+if sys.version_info >= (3, 5):
+ TEST_PARSE_ACME_TIMESTAMP.extend([
+ (
+ '2024-01-01T00:11:22+0100',
+ dict(year=2023, month=12, day=31, hour=23, minute=11, second=22),
+ ),
+ (
+ '2024-01-01T00:11:22.123+0100',
+ dict(year=2023, month=12, day=31, hour=23, minute=11, second=22, microsecond=123000),
+ ),
+ ])
+
+
+TEST_INTERPOLATE_TIMESTAMP = [
+ (
+ dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
+ dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
+ 0.0,
+ dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
+ ),
+ (
+ dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
+ dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
+ 0.5,
+ dict(year=2024, month=1, day=1, hour=0, minute=30, second=0),
+ ),
+ (
+ dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
+ dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
+ 1.0,
+ dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
+ ),
+]
+
+
class FakeBackend(CryptoBackend):
def parse_key(self, key_file=None, key_content=None, passphrase=None):
raise BackendException('Not implemented in fake backend')
@@ -98,6 +181,9 @@ class FakeBackend(CryptoBackend):
def create_mac_key(self, alg, key):
raise BackendException('Not implemented in fake backend')
+ def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
+ raise BackendException('Not implemented in fake backend')
+
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
raise BackendException('Not implemented in fake backend')
@@ -106,3 +192,6 @@ class FakeBackend(CryptoBackend):
def create_chain_matcher(self, criterium):
raise BackendException('Not implemented in fake backend')
+
+ def get_cert_information(self, cert_filename=None, cert_content=None):
+ raise BackendException('Not implemented in fake backend')
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt
new file mode 100644
index 000000000..e989d914d
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt
@@ -0,0 +1,38 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: ecdsa-with-SHA256
+ Issuer: CN=ansible.com
+ Validity
+ Not Before: Nov 25 15:28:23 2018 GMT
+ Not After : Nov 26 15:28:24 2018 GMT
+ Subject: CN=ansible.com
+ Subject Public Key Info:
+ Public Key Algorithm: id-ecPublicKey
+ Public-Key: (256 bit)
+ pub:
+ 04:00:9c:f4:c8:00:17:03:01:26:3a:14:d1:92:35:
+ f1:c2:07:9d:6d:63:ba:82:86:d8:33:79:56:b3:3a:
+ d2:eb:c1:bc:41:2c:e1:5d:1e:80:99:0d:c8:cd:90:
+ e2:9a:74:d3:5c:ee:d7:85:5c:a5:0d:3f:12:2f:31:
+ 38:e3:f1:29:9b
+ ASN1 OID: prime256v1
+ NIST CURVE: P-256
+ X509v3 extensions:
+ X509v3 Subject Alternative Name:
+ DNS:example.com, DNS:example.org
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Key Usage: critical
+ Digital Signature
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication
+ X509v3 Subject Key Identifier:
+ 98:D2:FD:3C:CC:CD:69:45:FB:E2:8C:30:2C:54:62:18:34:B7:07:73
+ Signature Algorithm: ecdsa-with-SHA256
+ Signature Value:
+ 30:46:02:21:00:bc:fb:52:bf:7a:93:2d:0e:7c:ce:43:f4:cc:
+ 05:98:28:36:8d:c7:2a:9b:f5:20:94:62:3d:fb:82:9e:38:42:
+ 32:02:21:00:c0:55:f8:b5:d9:65:41:2a:dd:d4:76:3f:8c:cb:
+ 07:c1:d2:b9:c0:7d:c9:90:af:fd:f9:f1:b0:c9:13:f5:d5:52
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license
new file mode 100644
index 000000000..edff8c768
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_1.txt.license
@@ -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
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt
new file mode 100644
index 000000000..78326443b
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt
@@ -0,0 +1,57 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4218235397573492796 (0x3a8a2ebeb358c03c)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN = Pebble Intermediate CA 734609
+ Validity
+ Not Before: May 4 20:42:21 2024 GMT
+ Not After : May 4 20:42:20 2029 GMT
+ Subject: CN = example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public-Key: (1024 bit)
+ Modulus:
+ 00:c1:43:a5:f9:ad:00:b7:bb:1b:73:27:00:b3:a2:
+ 4e:27:0d:ff:ae:64:3e:a0:7e:f9:28:56:48:47:21:
+ 9e:0f:d8:fb:69:b5:21:e8:98:84:60:6c:aa:73:b9:
+ 6e:d9:f6:19:ad:85:e0:c2:f6:80:d3:22:b8:5a:d6:
+ 3a:89:3e:2a:7a:fc:1d:bf:fc:69:20:e5:91:b8:34:
+ 52:26:c8:15:74:e1:36:0c:cd:ab:01:4a:ad:83:f5:
+ 0b:77:96:31:cf:1c:ea:6f:88:75:23:ac:51:a6:d8:
+ 77:43:1b:b3:44:93:2c:8d:05:25:fb:77:41:36:94:
+ 81:d5:ca:56:ff:b5:23:b2:a5
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Key Encipherment
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 17:E5:83:22:14:EF:74:D3:BE:7E:30:76:56:1F:51:74:65:1F:E9:F0
+ X509v3 Authority Key Identifier:
+ keyid:13:C3:4C:3E:59:45:DD:E3:63:51:A3:46:80:C4:08:C7:14:C0:64:4E
+
+ Authority Information Access:
+ OCSP - URI:http://10.88.0.74:5000/ocsp
+
+ X509v3 Subject Alternative Name:
+ DNS:example.com
+ Signature Algorithm: sha256WithRSAEncryption
+ 31:43:de:b6:48:f4:b8:30:46:25:65:e6:91:22:33:1b:d1:ba:
+ 3f:60:f8:c3:18:32:72:e9:f8:d1:88:11:5a:0a:86:dc:1d:6d:
+ a5:ea:58:cd:05:ea:cd:5e:40:86:c1:ae:d5:cd:2e:8a:ca:50:
+ ee:df:bd:cf:6c:d9:20:3b:4b:49:f8:d5:8a:e3:be:f3:dd:24:
+ b2:7f:3f:3b:bf:e6:8d:7a:f8:8f:4b:6e:25:60:80:33:6f:0f:
+ 53:b7:7d:94:2a:d2:4a:db:3a:2f:70:79:d7:bf:05:ed:df:10:
+ 61:e7:24:ac:b2:fc:03:bd:ad:8c:e1:f3:1d:cc:78:99:e3:22:
+ 59:bf:c5:92:57:95:92:56:35:fc:05:8b:26:10:c5:1b:87:17:
+ 64:0b:bd:33:a9:54:d5:c0:2b:43:56:1b:52:d3:4f:8b:6f:25:
+ 06:58:7f:6f:aa:27:35:05:d5:57:6d:83:a0:73:de:40:3f:67:
+ 1c:5a:92:c6:37:e6:8f:c7:b8:91:d7:50:b9:4d:d4:f2:92:1f:
+ 8b:93:0c:e2:b4:b8:d7:1d:8e:ce:6d:19:dc:8f:12:8e:c0:f2:
+ 92:3b:95:5a:8c:c8:69:0e:0b:f7:fa:1f:55:62:80:7c:e2:f6:
+ 41:3f:7d:69:36:9e:7c:90:7e:d7:3b:e6:a3:15:de:a4:7d:95:
+ 13:46:c6:1a
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license
new file mode 100644
index 000000000..edff8c768
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2-b.txt.license
@@ -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
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem
new file mode 100644
index 000000000..92aecb621
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDDjCCAfagAwIBAgIIOoouvrNYwDwwDQYJKoZIhvcNAQELBQAwKDEmMCQGA1UE
+AxMdUGViYmxlIEludGVybWVkaWF0ZSBDQSA3MzQ2MDkwHhcNMjQwNTA0MjA0MjIx
+WhcNMjkwNTA0MjA0MjIwWjAWMRQwEgYDVQQDEwtleGFtcGxlLmNvbTCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEAwUOl+a0At7sbcycAs6JOJw3/rmQ+oH75KFZI
+RyGeD9j7abUh6JiEYGyqc7lu2fYZrYXgwvaA0yK4WtY6iT4qevwdv/xpIOWRuDRS
+JsgVdOE2DM2rAUqtg/ULd5Yxzxzqb4h1I6xRpth3QxuzRJMsjQUl+3dBNpSB1cpW
+/7UjsqUCAwEAAaOB0TCBzjAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYB
+BQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFBflgyIU73TT
+vn4wdlYfUXRlH+nwMB8GA1UdIwQYMBaAFBPDTD5ZRd3jY1GjRoDECMcUwGROMDcG
+CCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYbaHR0cDovLzEwLjg4LjAuNzQ6NTAw
+MC9vY3NwMBYGA1UdEQQPMA2CC2V4YW1wbGUuY29tMA0GCSqGSIb3DQEBCwUAA4IB
+AQAxQ962SPS4MEYlZeaRIjMb0bo/YPjDGDJy6fjRiBFaCobcHW2l6ljNBerNXkCG
+wa7VzS6KylDu373PbNkgO0tJ+NWK477z3SSyfz87v+aNeviPS24lYIAzbw9Tt32U
+KtJK2zovcHnXvwXt3xBh5ySssvwDva2M4fMdzHiZ4yJZv8WSV5WSVjX8BYsmEMUb
+hxdkC70zqVTVwCtDVhtS00+LbyUGWH9vqic1BdVXbYOgc95AP2ccWpLGN+aPx7iR
+11C5TdTykh+LkwzitLjXHY7ObRncjxKOwPKSO5VajMhpDgv3+h9VYoB84vZBP31p
+Np58kH7XO+ajFd6kfZUTRsYa
+-----END CERTIFICATE-----
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license
new file mode 100644
index 000000000..edff8c768
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.pem.license
@@ -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
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt
new file mode 100644
index 000000000..3cda74955
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt
@@ -0,0 +1,56 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 4218235397573492796 (0x3a8a2ebeb358c03c)
+ Signature Algorithm: sha256WithRSAEncryption
+ Issuer: CN=Pebble Intermediate CA 734609
+ Validity
+ Not Before: May 4 20:42:21 2024 GMT
+ Not After : May 4 20:42:20 2029 GMT
+ Subject: CN=example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ Public-Key: (1024 bit)
+ Modulus:
+ 00:c1:43:a5:f9:ad:00:b7:bb:1b:73:27:00:b3:a2:
+ 4e:27:0d:ff:ae:64:3e:a0:7e:f9:28:56:48:47:21:
+ 9e:0f:d8:fb:69:b5:21:e8:98:84:60:6c:aa:73:b9:
+ 6e:d9:f6:19:ad:85:e0:c2:f6:80:d3:22:b8:5a:d6:
+ 3a:89:3e:2a:7a:fc:1d:bf:fc:69:20:e5:91:b8:34:
+ 52:26:c8:15:74:e1:36:0c:cd:ab:01:4a:ad:83:f5:
+ 0b:77:96:31:cf:1c:ea:6f:88:75:23:ac:51:a6:d8:
+ 77:43:1b:b3:44:93:2c:8d:05:25:fb:77:41:36:94:
+ 81:d5:ca:56:ff:b5:23:b2:a5
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Key Usage: critical
+ Digital Signature, Key Encipherment
+ X509v3 Extended Key Usage:
+ TLS Web Server Authentication, TLS Web Client Authentication
+ X509v3 Basic Constraints: critical
+ CA:FALSE
+ X509v3 Subject Key Identifier:
+ 17:E5:83:22:14:EF:74:D3:BE:7E:30:76:56:1F:51:74:65:1F:E9:F0
+ X509v3 Authority Key Identifier:
+ 13:C3:4C:3E:59:45:DD:E3:63:51:A3:46:80:C4:08:C7:14:C0:64:4E
+ Authority Information Access:
+ OCSP - URI:http://10.88.0.74:5000/ocsp
+ X509v3 Subject Alternative Name:
+ DNS:example.com
+ Signature Algorithm: sha256WithRSAEncryption
+ Signature Value:
+ 31:43:de:b6:48:f4:b8:30:46:25:65:e6:91:22:33:1b:d1:ba:
+ 3f:60:f8:c3:18:32:72:e9:f8:d1:88:11:5a:0a:86:dc:1d:6d:
+ a5:ea:58:cd:05:ea:cd:5e:40:86:c1:ae:d5:cd:2e:8a:ca:50:
+ ee:df:bd:cf:6c:d9:20:3b:4b:49:f8:d5:8a:e3:be:f3:dd:24:
+ b2:7f:3f:3b:bf:e6:8d:7a:f8:8f:4b:6e:25:60:80:33:6f:0f:
+ 53:b7:7d:94:2a:d2:4a:db:3a:2f:70:79:d7:bf:05:ed:df:10:
+ 61:e7:24:ac:b2:fc:03:bd:ad:8c:e1:f3:1d:cc:78:99:e3:22:
+ 59:bf:c5:92:57:95:92:56:35:fc:05:8b:26:10:c5:1b:87:17:
+ 64:0b:bd:33:a9:54:d5:c0:2b:43:56:1b:52:d3:4f:8b:6f:25:
+ 06:58:7f:6f:aa:27:35:05:d5:57:6d:83:a0:73:de:40:3f:67:
+ 1c:5a:92:c6:37:e6:8f:c7:b8:91:d7:50:b9:4d:d4:f2:92:1f:
+ 8b:93:0c:e2:b4:b8:d7:1d:8e:ce:6d:19:dc:8f:12:8e:c0:f2:
+ 92:3b:95:5a:8c:c8:69:0e:0b:f7:fa:1f:55:62:80:7c:e2:f6:
+ 41:3f:7d:69:36:9e:7c:90:7e:d7:3b:e6:a3:15:de:a4:7d:95:
+ 13:46:c6:1a
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license
new file mode 100644
index 000000000..edff8c768
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/fixtures/cert_2.txt.license
@@ -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
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py
index 59da68a3b..9186e2430 100644
--- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_cryptography.py
@@ -16,11 +16,22 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.backend_cryp
CryptographyBackend,
)
+from ansible_collections.community.crypto.plugins.module_utils.crypto.support import (
+ ensure_utc_timezone,
+)
+
+from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptography_support import (
+ CRYPTOGRAPHY_TIMEZONE,
+)
+
from .backend_data import (
TEST_KEYS,
TEST_CSRS,
TEST_CERT,
TEST_CERT_DAYS,
+ TEST_CERT_INFO,
+ TEST_PARSE_ACME_TIMESTAMP,
+ TEST_INTERPOLATE_TIMESTAMP,
)
@@ -64,3 +75,49 @@ def test_certdays_cryptography(now, expected_days, tmpdir):
assert days == expected_days
days = backend.get_cert_days(cert_content=TEST_CERT, now=now)
assert days == expected_days
+
+
+@pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO)
+def test_get_cert_information(cert_content, expected_cert_info, openssl_output, tmpdir):
+ fn = tmpdir / 'test-cert.pem'
+ fn.write(cert_content)
+ module = MagicMock()
+ backend = CryptographyBackend(module)
+
+ if CRYPTOGRAPHY_TIMEZONE:
+ expected_cert_info = expected_cert_info._replace(
+ not_valid_after=ensure_utc_timezone(expected_cert_info.not_valid_after),
+ not_valid_before=ensure_utc_timezone(expected_cert_info.not_valid_before),
+ )
+
+ cert_info = backend.get_cert_information(cert_filename=str(fn))
+ assert cert_info == expected_cert_info
+ cert_info = backend.get_cert_information(cert_content=cert_content)
+ assert cert_info == expected_cert_info
+
+
+def test_now():
+ module = MagicMock()
+ backend = CryptographyBackend(module)
+ now = backend.get_now()
+ assert CRYPTOGRAPHY_TIMEZONE == (now.tzinfo is not None)
+
+
+@pytest.mark.parametrize("input, expected", TEST_PARSE_ACME_TIMESTAMP)
+def test_parse_acme_timestamp(input, expected):
+ module = MagicMock()
+ backend = CryptographyBackend(module)
+ ts_expected = backend.get_utc_datetime(**expected)
+ timestamp = backend.parse_acme_timestamp(input)
+ assert ts_expected == timestamp
+
+
+@pytest.mark.parametrize("start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP)
+def test_interpolate_timestamp(start, end, percentage, expected):
+ module = MagicMock()
+ backend = CryptographyBackend(module)
+ ts_start = backend.get_utc_datetime(**start)
+ ts_end = backend.get_utc_datetime(**end)
+ ts_expected = backend.get_utc_datetime(**expected)
+ timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage)
+ assert ts_expected == timestamp
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py
index dd30cf795..5138a6202 100644
--- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_backend_openssl_cli.py
@@ -18,6 +18,12 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.backend_open
from .backend_data import (
TEST_KEYS,
TEST_CSRS,
+ TEST_CERT,
+ TEST_CERT_OPENSSL_OUTPUT,
+ TEST_CERT_DAYS,
+ TEST_CERT_INFO,
+ TEST_PARSE_ACME_TIMESTAMP,
+ TEST_INTERPOLATE_TIMESTAMP,
)
@@ -61,3 +67,56 @@ def test_normalize_ip(ip, result):
module = MagicMock()
backend = OpenSSLCLIBackend(module, openssl_binary='openssl')
assert backend._normalize_ip(ip) == result
+
+
+@pytest.mark.parametrize("now, expected_days", TEST_CERT_DAYS)
+def test_certdays_cryptography(now, expected_days, tmpdir):
+ fn = tmpdir / 'test-cert.pem'
+ fn.write(TEST_CERT)
+ module = MagicMock()
+ module.run_command = MagicMock(return_value=(0, TEST_CERT_OPENSSL_OUTPUT, 0))
+ backend = OpenSSLCLIBackend(module, openssl_binary='openssl')
+ days = backend.get_cert_days(cert_filename=str(fn), now=now)
+ assert days == expected_days
+ days = backend.get_cert_days(cert_content=TEST_CERT, now=now)
+ assert days == expected_days
+
+
+@pytest.mark.parametrize("cert_content, expected_cert_info, openssl_output", TEST_CERT_INFO)
+def test_get_cert_information(cert_content, expected_cert_info, openssl_output, tmpdir):
+ fn = tmpdir / 'test-cert.pem'
+ fn.write(cert_content)
+ module = MagicMock()
+ module.run_command = MagicMock(return_value=(0, openssl_output, 0))
+ backend = OpenSSLCLIBackend(module, openssl_binary='openssl')
+ cert_info = backend.get_cert_information(cert_filename=str(fn))
+ assert cert_info == expected_cert_info
+ cert_info = backend.get_cert_information(cert_content=cert_content)
+ assert cert_info == expected_cert_info
+
+
+def test_now():
+ module = MagicMock()
+ backend = OpenSSLCLIBackend(module, openssl_binary='openssl')
+ now = backend.get_now()
+ assert now.tzinfo is None
+
+
+@pytest.mark.parametrize("input, expected", TEST_PARSE_ACME_TIMESTAMP)
+def test_parse_acme_timestamp(input, expected):
+ module = MagicMock()
+ backend = OpenSSLCLIBackend(module, openssl_binary='openssl')
+ ts_expected = backend.get_utc_datetime(**expected)
+ timestamp = backend.parse_acme_timestamp(input)
+ assert ts_expected == timestamp
+
+
+@pytest.mark.parametrize("start, end, percentage, expected", TEST_INTERPOLATE_TIMESTAMP)
+def test_interpolate_timestamp(start, end, percentage, expected):
+ module = MagicMock()
+ backend = OpenSSLCLIBackend(module, openssl_binary='openssl')
+ ts_start = backend.get_utc_datetime(**start)
+ ts_end = backend.get_utc_datetime(**end)
+ ts_expected = backend.get_utc_datetime(**expected)
+ timestamp = backend.interpolate_timestamp(ts_start, ts_end, percentage)
+ assert ts_expected == timestamp
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py
index 9bdd8eb6e..5cc318ac2 100644
--- a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/acme/test_utils.py
@@ -6,12 +6,20 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
+import datetime
+
import pytest
+from ansible_collections.community.crypto.plugins.module_utils.acme.backends import (
+ CertificateInformation,
+)
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
nopad_b64,
pem_to_der,
+ process_links,
+ parse_retry_after,
+ compute_cert_id,
)
from .backend_data import (
@@ -27,6 +35,73 @@ NOPAD_B64 = [
]
+TEST_LINKS_HEADER = [
+ (
+ {},
+ [],
+ ),
+ (
+ {
+ 'link': '<foo>; rel="bar"'
+ },
+ [
+ ('foo', 'bar'),
+ ],
+ ),
+ (
+ {
+ 'link': '<foo>; rel="bar", <baz>; rel="bam"'
+ },
+ [
+ ('foo', 'bar'),
+ ('baz', 'bam'),
+ ],
+ ),
+ (
+ {
+ 'link': '<https://one.example.com>; rel="preconnect", <https://two.example.com>; rel="preconnect", <https://three.example.com>; rel="preconnect"'
+ },
+ [
+ ('https://one.example.com', 'preconnect'),
+ ('https://two.example.com', 'preconnect'),
+ ('https://three.example.com', 'preconnect'),
+ ],
+ ),
+]
+
+
+TEST_RETRY_AFTER_HEADER = [
+ ('120', datetime.datetime(2024, 4, 29, 0, 2, 0)),
+ ('Wed, 21 Oct 2015 07:28:00 GMT', datetime.datetime(2015, 10, 21, 7, 28, 0)),
+]
+
+
+TEST_COMPUTE_CERT_ID = [
+ (
+ CertificateInformation(
+ not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24),
+ not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23),
+ serial_number=1,
+ subject_key_identifier=None,
+ authority_key_identifier=b'\x00\xff',
+ ),
+ 'AP8.AQ',
+ ),
+ (
+ # AKI, serial number, and expected result taken from
+ # https://letsencrypt.org/2024/04/25/guide-to-integrating-ari-into-existing-acme-clients.html#step-3-constructing-the-ari-certid
+ CertificateInformation(
+ not_valid_after=datetime.datetime(2018, 11, 26, 15, 28, 24),
+ not_valid_before=datetime.datetime(2018, 11, 25, 15, 28, 23),
+ serial_number=0x87654321,
+ subject_key_identifier=None,
+ authority_key_identifier=b'\x69\x88\x5B\x6B\x87\x46\x40\x41\xE1\xB3\x7B\x84\x7B\xA0\xAE\x2C\xDE\x01\xC8\xD4',
+ ),
+ 'aYhba4dGQEHhs3uEe6CuLN4ByNQ.AIdlQyE',
+ ),
+]
+
+
@pytest.mark.parametrize("value, result", NOPAD_B64)
def test_nopad_b64(value, result):
assert nopad_b64(value.encode('utf-8')) == result
@@ -37,3 +112,25 @@ def test_pem_to_der(pem, der, tmpdir):
fn = tmpdir / 'test.pem'
fn.write(pem)
assert pem_to_der(str(fn)) == der
+
+
+@pytest.mark.parametrize("value, expected_result", TEST_LINKS_HEADER)
+def test_process_links(value, expected_result):
+ data = []
+
+ def callback(url, rel):
+ data.append((url, rel))
+
+ process_links(value, callback)
+
+ assert expected_result == data
+
+
+@pytest.mark.parametrize("value, expected_result", TEST_RETRY_AFTER_HEADER)
+def test_parse_retry_after(value, expected_result):
+ assert expected_result == parse_retry_after(value, now=datetime.datetime(2024, 4, 29, 0, 0, 0))
+
+
+@pytest.mark.parametrize("cert_info, expected_result", TEST_COMPUTE_CERT_ID)
+def test_compute_cert_id(cert_info, expected_result):
+ assert expected_result == compute_cert_id(backend=None, cert_info=cert_info)
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py
new file mode 100644
index 000000000..4fd917713
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/crypto/test_math.py
@@ -0,0 +1,117 @@
+# -*- 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
+
+import pytest
+
+from ansible_collections.community.crypto.plugins.module_utils.crypto.math import (
+ binary_exp_mod,
+ simple_gcd,
+ quick_is_not_prime,
+ convert_int_to_bytes,
+ convert_int_to_hex,
+ convert_bytes_to_int,
+)
+
+
+@pytest.mark.parametrize('f, e, m, result', [
+ (0, 0, 5, 1),
+ (0, 1, 5, 0),
+ (2, 1, 5, 2),
+ (2, 2, 5, 4),
+ (2, 3, 5, 3),
+ (2, 10, 5, 4),
+])
+def test_binary_exp_mod(f, e, m, result):
+ value = binary_exp_mod(f, e, m)
+ print(value)
+ assert value == result
+
+
+@pytest.mark.parametrize('a, b, result', [
+ (0, -123, -123),
+ (0, 123, 123),
+ (-123, 0, -123),
+ (123, 0, 123),
+ (-123, 1, 1),
+ (123, 1, 1),
+ (1, -123, -1),
+ (1, 123, 1),
+ (1024, 10, 2),
+])
+def test_simple_gcd(a, b, result):
+ value = simple_gcd(a, b)
+ print(value)
+ assert value == result
+
+
+@pytest.mark.parametrize('n, result', [
+ (-2, True),
+ (0, True),
+ (1, True),
+ (2, False),
+ (3, False),
+ (4, True),
+ (5, False),
+ (6, True),
+ (7, False),
+ (8, True),
+ (9, True),
+ (10, True),
+ (211, False), # the smallest prime number >= 200
+])
+def test_quick_is_not_prime(n, result):
+ value = quick_is_not_prime(n)
+ print(value)
+ assert value == result
+
+
+@pytest.mark.parametrize('no, count, result', [
+ (0, None, b''),
+ (0, 1, b'\x00'),
+ (0, 2, b'\x00\x00'),
+ (1, None, b'\x01'),
+ (1, 2, b'\x00\x01'),
+ (255, None, b'\xff'),
+ (256, None, b'\x01\x00'),
+])
+def test_convert_int_to_bytes(no, count, result):
+ value = convert_int_to_bytes(no, count=count)
+ print(value)
+ assert value == result
+
+
+@pytest.mark.parametrize('no, digits, result', [
+ (0, None, '0'),
+ (1, None, '1'),
+ (16, None, '10'),
+ (1, 3, '001'),
+ (255, None, 'ff'),
+ (256, None, '100'),
+ (256, 2, '100'),
+ (256, 3, '100'),
+ (256, 4, '0100'),
+])
+def test_convert_int_to_hex(no, digits, result):
+ value = convert_int_to_hex(no, digits=digits)
+ print(value)
+ assert value == result
+
+
+@pytest.mark.parametrize('data, result', [
+ (b'', 0),
+ (b'\x00', 0),
+ (b'\x00\x01', 1),
+ (b'\x01', 1),
+ (b'\xff', 255),
+ (b'\x01\x00', 256),
+])
+def test_convert_bytes_to_int(data, result):
+ value = convert_bytes_to_int(data)
+ print(value)
+ assert value == result
diff --git a/ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py
new file mode 100644
index 000000000..35a83f4e4
--- /dev/null
+++ b/ansible_collections/community/crypto/tests/unit/plugins/module_utils/test_time.py
@@ -0,0 +1,323 @@
+# 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
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+import datetime
+import sys
+
+import pytest
+
+
+from ansible_collections.community.crypto.plugins.module_utils.time import (
+ add_or_remove_timezone,
+ get_now_datetime,
+ convert_relative_to_datetime,
+ ensure_utc_timezone,
+ from_epoch_seconds,
+ get_epoch_seconds,
+ get_relative_time_option,
+ remove_timezone,
+ UTC,
+)
+
+
+TEST_REMOVE_TIMEZONE = [
+ (
+ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC),
+ datetime.datetime(2024, 1, 1, 0, 1, 2),
+ ),
+ (
+ datetime.datetime(2024, 1, 1, 0, 1, 2),
+ datetime.datetime(2024, 1, 1, 0, 1, 2),
+ ),
+]
+
+TEST_UTC_TIMEZONE = [
+ (
+ datetime.datetime(2024, 1, 1, 0, 1, 2),
+ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC),
+ ),
+ (
+ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC),
+ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=UTC),
+ ),
+]
+
+TEST_EPOCH_SECONDS = [
+ (0, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=0)),
+ (1E-6, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1)),
+ (1E-3, dict(year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1000)),
+ (3691.2, dict(year=1970, day=1, month=1, hour=1, minute=1, second=31, microsecond=200000)),
+]
+
+TEST_EPOCH_TO_SECONDS = [
+ (datetime.datetime(1970, 1, 1, 0, 1, 2, 0), 62),
+ (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=UTC), 62),
+]
+
+TEST_CONVERT_RELATIVE_TO_DATETIME = [
+ (
+ '+0',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ ),
+ (
+ '+1s',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC),
+ datetime.datetime(2024, 1, 1, 0, 0, 1),
+ ),
+ (
+ '-10w20d30h40m50s',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC),
+ datetime.datetime(2023, 10, 1, 17, 19, 10),
+ ),
+ (
+ '+0',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC),
+ ),
+ (
+ '+1s',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0, tzinfo=UTC),
+ datetime.datetime(2024, 1, 1, 0, 0, 1, tzinfo=UTC),
+ ),
+ (
+ '-10w20d30h40m50s',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2023, 10, 1, 17, 19, 10, tzinfo=UTC),
+ ),
+]
+
+TEST_GET_RELATIVE_TIME_OPTION = [
+ (
+ '+1d2h3m4s',
+ 'foo',
+ 'cryptography',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 2, 3, 4),
+ ),
+ (
+ '-1w10d24h',
+ 'foo',
+ 'cryptography',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2023, 12, 14, 0, 0, 0),
+ ),
+ (
+ '20240102040506Z',
+ 'foo',
+ 'cryptography',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 4, 5, 6),
+ ),
+ (
+ '202401020405Z',
+ 'foo',
+ 'cryptography',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 4, 5, 0),
+ ),
+ (
+ '+1d2h3m4s',
+ 'foo',
+ 'cryptography',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 2, 3, 4, tzinfo=UTC),
+ ),
+ (
+ '-1w10d24h',
+ 'foo',
+ 'cryptography',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2023, 12, 14, 0, 0, 0, tzinfo=UTC),
+ ),
+ (
+ '20240102040506Z',
+ 'foo',
+ 'cryptography',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 4, 5, 6, tzinfo=UTC),
+ ),
+ (
+ '202401020405Z',
+ 'foo',
+ 'cryptography',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 4, 5, 0, tzinfo=UTC),
+ ),
+ (
+ '+1d2h3m4s',
+ 'foo',
+ 'pyopenssl',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ '20240102020304Z',
+ ),
+ (
+ '-1w10d24h',
+ 'foo',
+ 'pyopenssl',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ '20231214000000Z',
+ ),
+ (
+ '20240102040506Z',
+ 'foo',
+ 'pyopenssl',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ '20240102040506Z',
+ ),
+ (
+ '202401020405Z',
+ 'foo',
+ 'pyopenssl',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ '202401020405Z',
+ ),
+]
+
+
+if sys.version_info >= (3, 5):
+ ONE_HOUR_PLUS = datetime.timezone(datetime.timedelta(hours=1))
+
+ TEST_REMOVE_TIMEZONE.extend([
+ (
+ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS),
+ datetime.datetime(2023, 12, 31, 23, 1, 2),
+ ),
+ ])
+ TEST_UTC_TIMEZONE.extend([
+ (
+ datetime.datetime(2024, 1, 1, 0, 1, 2, tzinfo=ONE_HOUR_PLUS),
+ datetime.datetime(2023, 12, 31, 23, 1, 2, tzinfo=UTC),
+ ),
+ ])
+ TEST_EPOCH_TO_SECONDS.extend([
+ (datetime.datetime(1970, 1, 1, 0, 1, 2, 0, tzinfo=ONE_HOUR_PLUS), 62 - 3600),
+ ])
+ TEST_GET_RELATIVE_TIME_OPTION.extend([
+ (
+ '20240102040506+0100',
+ 'foo',
+ 'cryptography',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 3, 5, 6),
+ ),
+ (
+ '202401020405+0100',
+ 'foo',
+ 'cryptography',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 3, 5, 0),
+ ),
+ (
+ '20240102040506+0100',
+ 'foo',
+ 'cryptography',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 3, 5, 6, tzinfo=UTC),
+ ),
+ (
+ '202401020405+0100',
+ 'foo',
+ 'cryptography',
+ True,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ datetime.datetime(2024, 1, 2, 3, 5, 0, tzinfo=UTC),
+ ),
+ (
+ '20240102040506+0100',
+ 'foo',
+ 'pyopenssl',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ '20240102040506+0100',
+ ),
+ (
+ '202401020405+0100',
+ 'foo',
+ 'pyopenssl',
+ False,
+ datetime.datetime(2024, 1, 1, 0, 0, 0),
+ '202401020405+0100',
+ ),
+ ])
+
+
+@pytest.mark.parametrize("input, expected", TEST_REMOVE_TIMEZONE)
+def test_remove_timezone(input, expected):
+ output_1 = remove_timezone(input)
+ assert expected == output_1
+ output_2 = add_or_remove_timezone(input, with_timezone=False)
+ assert expected == output_2
+
+
+@pytest.mark.parametrize("input, expected", TEST_UTC_TIMEZONE)
+def test_utc_timezone(input, expected):
+ output_1 = ensure_utc_timezone(input)
+ assert expected == output_1
+ output_2 = add_or_remove_timezone(input, with_timezone=True)
+ assert expected == output_2
+
+
+def test_get_now_datetime():
+ output_1 = get_now_datetime(with_timezone=False)
+ assert output_1.tzinfo is None
+ output_2 = get_now_datetime(with_timezone=True)
+ assert output_2.tzinfo is not None
+ assert output_2.tzinfo == UTC
+
+
+@pytest.mark.parametrize("seconds, timestamp", TEST_EPOCH_SECONDS)
+def test_epoch_seconds(seconds, timestamp):
+ ts_wo_tz = datetime.datetime(**timestamp)
+ assert seconds == get_epoch_seconds(ts_wo_tz)
+ timestamp_w_tz = dict(timestamp)
+ timestamp_w_tz['tzinfo'] = UTC
+ ts_w_tz = datetime.datetime(**timestamp_w_tz)
+ assert seconds == get_epoch_seconds(ts_w_tz)
+ output_1 = from_epoch_seconds(seconds, with_timezone=False)
+ assert ts_wo_tz == output_1
+ output_2 = from_epoch_seconds(seconds, with_timezone=True)
+ assert ts_w_tz == output_2
+
+
+@pytest.mark.parametrize("timestamp, expected_seconds", TEST_EPOCH_TO_SECONDS)
+def test_epoch_to_seconds(timestamp, expected_seconds):
+ assert expected_seconds == get_epoch_seconds(timestamp)
+
+
+@pytest.mark.parametrize("relative_time_string, with_timezone, now, expected", TEST_CONVERT_RELATIVE_TO_DATETIME)
+def test_convert_relative_to_datetime(relative_time_string, with_timezone, now, expected):
+ output = convert_relative_to_datetime(relative_time_string, with_timezone=with_timezone, now=now)
+ assert expected == output
+
+
+@pytest.mark.parametrize("input_string, input_name, backend, with_timezone, now, expected", TEST_GET_RELATIVE_TIME_OPTION)
+def test_get_relative_time_option(input_string, input_name, backend, with_timezone, now, expected):
+ output = get_relative_time_option(input_string, input_name, backend=backend, with_timezone=with_timezone, now=now)
+ assert expected == output