Bump version to 3.0.0-dev0, remove deprecated functionality and implement announced breaking changes (#873)

* Bump verison to 3.0.0-dev0.

* Change check mode behavior for *_pipe modules.

* Remove PyOpenSSL backend.

* Remove PyOpenSSL setup.

* Change default of asn1_base64.

* Remove deprecated common module utils.

* Remove get_default_argspec().

* Mark two methods as abstract.

* Remove ACME v1 support.

* Remove retrieve_acme_v1_certificate().

* Remove deprecated docs fragment.

* Change meaning of mode parameter.

* Mark no longer used option as 'to deprecate'.
This commit is contained in:
Felix Fontein
2025-04-29 08:12:44 +02:00
committed by GitHub
parent f73a1ce590
commit d368d1943d
41 changed files with 194 additions and 937 deletions

View File

@@ -102,13 +102,13 @@ options:
description:
- URI to a terms of service document you agree to when using the ACME v1 service at O(acme_directory).
- Default is latest gathered from O(acme_directory) URL.
- This option will only be used when O(acme_version) is 1.
- This option has no longer any effect.
# TODO: deprecate!
type: str
terms_agreed:
description:
- Boolean indicating whether you agree to the terms of service document.
- ACME servers can require this to be true.
- This option will only be used when O(acme_version) is not 1.
type: bool
default: false
modify_account:
@@ -433,7 +433,6 @@ EXAMPLES = r"""
data: "{{ sample_com_challenge }}"
# We use Let's Encrypt's ACME v2 endpoint
acme_directory: https://acme-v02.api.letsencrypt.org/directory
acme_version: 2
# The following makes sure that if a chain with /CN=DST Root CA X3 in its issuer is provided
# as an alternative, it will be selected. These are the roots cross-signed by IdenTrust.
# As long as Let's Encrypt provides alternate chains with the cross-signed root(s) when
@@ -580,10 +579,8 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.acme import
from ansible_collections.community.crypto.plugins.module_utils.acme.certificates import (
CertificateChain,
Criterium,
retrieve_acme_v1_certificate,
)
from ansible_collections.community.crypto.plugins.module_utils.acme.challenges import (
Authorization,
combine_identifier,
normalize_combined_identifier,
split_identifier,
@@ -669,33 +666,21 @@ class ACMECertificateClient(object):
# Make sure account exists
modify_account = module.params["modify_account"]
if modify_account or self.version > 1:
contact = []
if module.params["account_email"]:
contact.append("mailto:" + module.params["account_email"])
created, account_data = self.account.setup_account(
contact,
agreement=module.params.get("agreement"),
terms_agreed=module.params.get("terms_agreed"),
allow_creation=modify_account,
)
if account_data is None:
raise ModuleFailException(
msg="Account does not exist or is deactivated."
)
updated = False
if not created and account_data and modify_account:
updated, account_data = self.account.update_account(
account_data, contact
)
self.changed = created or updated
else:
# This happens if modify_account is False and the ACME v1
# protocol is used. In this case, we do not call setup_account()
# to avoid accidental creation of an account. This is OK
# since for ACME v1, the account URI is not needed to send a
# signed ACME request.
pass
contact = []
if module.params["account_email"]:
contact.append("mailto:" + module.params["account_email"])
created, account_data = self.account.setup_account(
contact,
agreement=module.params.get("agreement"),
terms_agreed=module.params.get("terms_agreed"),
allow_creation=modify_account,
)
if account_data is None:
raise ModuleFailException(msg="Account does not exist or is deactivated.")
updated = False
if not created and account_data and modify_account:
updated, account_data = self.account.update_account(account_data, contact)
self.changed = created or updated
if self.csr is not None and not os.path.exists(self.csr):
raise ModuleFailException("CSR %s not found" % (self.csr))
@@ -712,13 +697,9 @@ class ACMECertificateClient(object):
"""
if self.data is None:
return True
if self.version == 1:
# As soon as self.data is a non-empty object, we are in the second stage.
return not self.data
else:
# We are in the second stage if data.order_uri is given (which has been
# stored in self.order_uri by the constructor).
return self.order_uri is None
# We are in the second stage if data.order_uri is given (which has been
# 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"):
@@ -735,40 +716,30 @@ class ACMECertificateClient(object):
respectively start a new order for ACME v2.
"""
self.authorizations = {}
if self.version == 1:
for identifier_type, identifier in self.identifiers:
if identifier_type != "dns":
raise ModuleFailException("ACME v1 only supports DNS identifiers!")
for identifier_type, identifier in self.identifiers:
authz = Authorization.create(self.client, identifier_type, identifier)
self.authorizations[
normalize_combined_identifier(authz.combined_identifier)
] = authz
else:
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_with_error_handling(
self.client,
self.identifiers,
error_strategy=self.order_creation_error_strategy,
error_max_retries=self.order_creation_max_retries,
replaces_cert_id=replaces_cert_id,
profile=self.profile,
message_callback=self.module.warn,
)
self.order_uri = self.order.url
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
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_with_error_handling(
self.client,
self.identifiers,
error_strategy=self.order_creation_error_strategy,
error_max_retries=self.order_creation_max_retries,
replaces_cert_id=replaces_cert_id,
profile=self.profile,
message_callback=self.module.warn,
)
self.order_uri = self.order.url
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
self.changed = True
def get_challenges_data(self, first_step):
@@ -815,20 +786,11 @@ class ACMECertificateClient(object):
self.authorizations = {}
# Step 1: obtain challenge information
if self.version == 1:
# For ACME v1, we attempt to create new authzs. Existing ones
# will be returned instead.
for identifier_type, identifier in self.identifiers:
authz = Authorization.create(self.client, identifier_type, identifier)
self.authorizations[combine_identifier(identifier_type, identifier)] = (
authz
)
else:
# For ACME v2, we obtain the order object by fetching the
# order URI, and extract the information from there.
self.order = Order.from_url(self.client, self.order_uri)
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
# For ACME v2, we obtain the order object by fetching the
# order URI, and extract the information from there.
self.order = Order.from_url(self.client, self.order_uri)
self.order.load_authorizations(self.client)
self.authorizations.update(self.order.authorizations)
# Step 2: validate pending challenges
authzs_to_wait_for = []
@@ -897,33 +859,25 @@ class ACMECertificateClient(object):
module=self.module,
)
if self.version == 1:
cert = retrieve_acme_v1_certificate(
self.client, pem_to_der(self.csr, self.csr_content)
)
else:
self.order.finalize(self.client, pem_to_der(self.csr, self.csr_content))
cert = CertificateChain.download(self.client, self.order.certificate_uri)
if (
self.module.params["retrieve_all_alternates"]
or self.select_chain_matcher
):
# Retrieve alternate chains
alternate_chains = self.download_alternate_chains(cert)
self.order.finalize(self.client, pem_to_der(self.csr, self.csr_content))
cert = CertificateChain.download(self.client, self.order.certificate_uri)
if self.module.params["retrieve_all_alternates"] or self.select_chain_matcher:
# Retrieve alternate chains
alternate_chains = self.download_alternate_chains(cert)
# Prepare return value for all alternate chains
if self.module.params["retrieve_all_alternates"]:
self.all_chains = [cert.to_json()]
for alt_chain in alternate_chains:
self.all_chains.append(alt_chain.to_json())
# Prepare return value for all alternate chains
if self.module.params["retrieve_all_alternates"]:
self.all_chains = [cert.to_json()]
for alt_chain in alternate_chains:
self.all_chains.append(alt_chain.to_json())
# Try to select alternate chain depending on criteria
if self.select_chain_matcher:
matching_chain = self.find_matching_chain([cert] + alternate_chains)
if matching_chain:
cert = matching_chain
else:
self.module.debug("Found no matching alternative chain")
# Try to select alternate chain depending on criteria
if self.select_chain_matcher:
matching_chain = self.find_matching_chain([cert] + alternate_chains)
if matching_chain:
cert = matching_chain
else:
self.module.debug("Found no matching alternative chain")
if cert.cert is not None:
pem_cert = cert.cert

View File

@@ -76,8 +76,6 @@ def main():
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)

View File

@@ -215,7 +215,6 @@ EXAMPLES = r"""
- name: Create a challenge for sample.com using a account key file.
community.crypto.acme_certificate_order_create:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
register: sample_com_challenge
@@ -238,7 +237,6 @@ EXAMPLES = r"""
- name: Let the challenge be validated
community.crypto.acme_certificate_order_validate:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
order_uri: "{{ sample_com_challenge.order_uri }}"
challenge: dns-01
@@ -246,7 +244,6 @@ EXAMPLES = r"""
- name: Retrieve the cert and intermediate certificate
community.crypto.acme_certificate_order_finalize:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
order_uri: "{{ sample_com_challenge.order_uri }}"
@@ -405,8 +402,6 @@ def main():
order_creation_max_retries=dict(type="int", default=3),
)
module = argument_spec.create_ansible_module()
if module.params["acme_version"] == 1:
module.fail_json("The module does not support acme_version=1")
backend = create_backend(module, False)

View File

@@ -227,7 +227,6 @@ EXAMPLES = r"""
- name: Create a challenge for sample.com using a account key file.
community.crypto.acme_certificate_order_create:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
register: sample_com_challenge
@@ -250,7 +249,6 @@ EXAMPLES = r"""
- name: Let the challenge be validated
community.crypto.acme_certificate_order_validate:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
order_uri: "{{ sample_com_challenge.order_uri }}"
challenge: dns-01
@@ -258,7 +256,6 @@ EXAMPLES = r"""
- name: Retrieve the cert and intermediate certificate
community.crypto.acme_certificate_order_finalize:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
order_uri: "{{ sample_com_challenge.order_uri }}"
@@ -365,8 +362,6 @@ def main():
),
)
module = argument_spec.create_ansible_module()
if module.params["acme_version"] == 1:
module.fail_json("The module does not support acme_version=1")
backend = create_backend(module, False)

View File

@@ -380,8 +380,6 @@ def main():
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)

View File

@@ -152,7 +152,6 @@ EXAMPLES = r"""
- name: Create a challenge for sample.com using a account key file.
community.crypto.acme_certificate_order_create:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
register: sample_com_challenge
@@ -175,7 +174,6 @@ EXAMPLES = r"""
- name: Let the challenge be validated
community.crypto.acme_certificate_order_validate:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
order_uri: "{{ sample_com_challenge.order_uri }}"
challenge: dns-01
@@ -183,7 +181,6 @@ EXAMPLES = r"""
- name: Retrieve the cert and intermediate certificate
community.crypto.acme_certificate_order_finalize:
acme_directory: https://acme-v01.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
csr: /etc/pki/cert/csr/sample.com.csr
order_uri: "{{ sample_com_challenge.order_uri }}"
@@ -257,8 +254,6 @@ def main():
deactivate_authzs=dict(type="bool", default=True),
)
module = argument_spec.create_ansible_module()
if module.params["acme_version"] == 1:
module.fail_json("The module does not support acme_version=1")
backend = create_backend(module, False)

View File

@@ -174,12 +174,7 @@ def main():
payload = {"certificate": certificate}
if module.params.get("revoke_reason") is not None:
payload["reason"] = module.params.get("revoke_reason")
# Determine endpoint
if module.params.get("acme_version") == 1:
endpoint = client.directory["revoke-cert"]
payload["resource"] = "revoke-cert"
else:
endpoint = client.directory["revokeCert"]
endpoint = client.directory["revokeCert"]
# Get hold of private key (if available) and make sure it comes from disk
private_key = module.params.get("private_key_src")
private_key_content = module.params.get("private_key_content")
@@ -227,12 +222,8 @@ def main():
already_revoked = True
else:
# Hack for Boulder errors
if module.params.get("acme_version") == 1:
error_type = "urn:acme:error:malformed"
else:
error_type = "urn:ietf:params:acme:error:malformed"
if (
result.get("type") == error_type
result.get("type") == "urn:ietf:params:acme:error:malformed"
and result.get("detail") == "Certificate already revoked"
):
# Fallback: boulder returns this in case the certificate was already revoked.

View File

@@ -27,7 +27,7 @@ notes:
- "Using the C(ansible) tool, M(community.crypto.acme_inspect) can be used to directly execute ACME requests without the
need of writing a playbook. For example, the following command retrieves the ACME account with ID 1 from Let's Encrypt
(assuming C(/path/to/key) is the correct private account key): C(ansible localhost -m acme_inspect -a \"account_key_src=/path/to/key
acme_directory=https://acme-v02.api.letsencrypt.org/directory acme_version=2 account_uri=https://acme-v02.api.letsencrypt.org/acme/acct/1
acme_directory=https://acme-v02.api.letsencrypt.org/directory account_uri=https://acme-v02.api.letsencrypt.org/acme/acct/1
method=get url=https://acme-v02.api.letsencrypt.org/acme/acct/1\")."
seealso:
- name: Automatic Certificate Management Environment (ACME)
@@ -83,14 +83,12 @@ EXAMPLES = r"""
- name: Get directory
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
method: directory-only
register: directory
- name: Create an account
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
url: "{{ directory.newAccount}}"
method: post
@@ -102,7 +100,6 @@ EXAMPLES = r"""
- name: Get account information
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ account_creation.headers.location }}"
@@ -111,7 +108,6 @@ EXAMPLES = r"""
- name: Update account contacts
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ account_creation.headers.location }}"
@@ -127,7 +123,6 @@ EXAMPLES = r"""
- name: Create certificate order
community.crypto.acme_certificate:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
csr: /etc/pki/cert/csr/sample.com.csr
@@ -141,7 +136,6 @@ EXAMPLES = r"""
- name: Get order information
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ certificate_request.order_uri }}"
@@ -151,7 +145,6 @@ EXAMPLES = r"""
- name: Get first authz for order
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ order.output_json.authorizations[0] }}"
@@ -161,7 +154,6 @@ EXAMPLES = r"""
- name: Get HTTP-01 challenge for authz
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ authz.output_json.challenges | selectattr('type', 'equalto', 'http-01') }}"
@@ -171,7 +163,6 @@ EXAMPLES = r"""
- name: Activate HTTP-01 challenge manually
community.crypto.acme_inspect:
acme_directory: https://acme-staging-v02.api.letsencrypt.org/directory
acme_version: 2
account_key_src: /etc/pki/cert/private/account.key
account_uri: "{{ account_creation.headers.location }}"
url: "{{ http01challenge.url }}"

View File

@@ -100,8 +100,9 @@ options:
- Whether to encode the ASN.1 values in the RV(extensions) return value with Base64 or not.
- The documentation claimed for a long time that the values are Base64 encoded, but they never were. For compatibility
this option is set to V(false).
- The default value V(false) is B(deprecated) and will change to V(true) in community.crypto 3.0.0.
- The default value was changed from V(false) to V(true) incommunity.crypto 3.0.0.
type: bool
default: true
version_added: 2.12.0
tls_ctx_options:
description:
@@ -351,7 +352,7 @@ def main():
),
starttls=dict(type="str", choices=["mysql"]),
ciphers=dict(type="list", elements="str"),
asn1_base64=dict(type="bool"),
asn1_base64=dict(type="bool", default=True),
tls_ctx_options=dict(type="list", elements="raw"),
get_certificate_chain=dict(type="bool", default=False),
),
@@ -370,16 +371,6 @@ def main():
tls_ctx_options = module.params["tls_ctx_options"]
get_certificate_chain = module.params["get_certificate_chain"]
if asn1_base64 is None:
module.deprecate(
"The default value `false` for asn1_base64 is deprecated and will change to `true` in "
"community.crypto 3.0.0. If you need this value, it is best to set the value explicitly "
"and adjust your roles/playbooks to use `asn1_base64=true` as soon as possible",
version="3.0.0",
collection_name="community.crypto",
)
asn1_base64 = False
if get_certificate_chain and sys.version_info < (3, 10):
module.fail_json(
msg="get_certificate_chain=true can only be used with Python 3.10 (Python 3.13+ officially supports this). "

View File

@@ -29,10 +29,7 @@ attributes:
check_mode:
support: full
details:
- Currently in check mode, private keys will not be (re-)generated, only the changed status is set. This will change
in community.crypto 3.0.0.
- From community.crypto 3.0.0 on, the module will ignore check mode and always behave as if check mode is not active.
If you think this breaks your use-case of this module, please create an issue in the community.crypto repository.
- Since community.crypto 3.0.0, the module ignores check mode and always behaves as if check mode is not active.
options:
content:
description:
@@ -157,18 +154,7 @@ class CertificateSigningRequestModule(object):
def generate(self, module):
"""Generate the certificate signing request."""
if self.module_backend.needs_regeneration():
if not self.check_mode:
self.module_backend.generate_csr()
else:
self.module.deprecate(
"Check mode support for openssl_csr_pipe will change in community.crypto 3.0.0"
" to behave the same as without check mode. You can get that behavior right now"
" by adding `check_mode: false` to the openssl_csr_pipe task. If you think this"
" breaks your use-case of this module, please create an issue in the"
" community.crypto repository",
version="3.0.0",
collection_name="community.crypto",
)
self.module_backend.generate_csr()
self.changed = True
def dump(self):

View File

@@ -18,11 +18,9 @@ author:
short_description: Generate OpenSSL PKCS#12 archive
description:
- This module allows one to (re-)generate PKCS#12.
- The module can use the cryptography Python library, or the pyOpenSSL Python library. By default, it tries to detect which
one is available, assuming none of the O(iter_size) and O(maciter_size) options are used. This can be overridden with
the O(select_crypto_backend) option.
- The module uses the cryptography Python library.
requirements:
- PyOpenSSL >= 0.15, < 23.3.0 or cryptography >= 3.0
- cryptography >= 3.0
extends_documentation_fragment:
- ansible.builtin.files
- community.crypto.attributes
@@ -95,15 +93,16 @@ options:
description:
- Number of times to repeat the encryption step.
- This is B(not considered during idempotency checks).
- This is only used by the C(pyopenssl) backend, or when O(encryption_level=compatibility2022).
- When using it, the default is V(2048) for C(pyopenssl) and V(50000) for C(cryptography).
- This is only used when O(encryption_level=compatibility2022).
- When using it, the default is V(50000).
type: int
maciter_size:
description:
- Number of times to repeat the MAC step.
- This is B(not considered during idempotency checks).
- This is only used by the C(pyopenssl) backend. When using it, the default is V(1).
- This value is B(not used).
type: int
# TODO: deprecate!
encryption_level:
description:
- Determines the encryption level used.
@@ -170,15 +169,12 @@ options:
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(pyopenssl). If
O(iter_size) is used together with O(encryption_level) is not V(compatibility2022), or if O(maciter_size) is used,
V(auto) will always result in C(pyopenssl) to be chosen for backwards compatibility.
- If set to V(pyopenssl), will try to use the L(pyOpenSSL,https://pypi.org/project/pyOpenSSL/) library.
- The default choice is V(auto), which tries to use C(cryptography) if available.
- If set to V(cryptography), will try to use the L(cryptography,https://cryptography.io/) library.
- B(Note) that the V(pyopenssl) backend is deprecated and will be removed from community.crypto 3.0.0.
- The value V(pyopenssl) has been removed for community.crypto 3.0.0.
type: str
default: auto
choices: [auto, cryptography, pyopenssl]
choices: [auto, cryptography]
version_added: 1.7.0
seealso:
- module: community.crypto.x509_certificate
@@ -315,23 +311,6 @@ from ansible_collections.community.crypto.plugins.module_utils.version import (
MINIMAL_CRYPTOGRAPHY_VERSION = "3.0"
MINIMAL_PYOPENSSL_VERSION = "0.15"
MAXIMAL_PYOPENSSL_VERSION = "23.3.0"
PYOPENSSL_IMP_ERR = None
try:
import OpenSSL
from OpenSSL import crypto
from OpenSSL.crypto import (
load_pkcs12 as _load_pkcs12, # this got removed in pyOpenSSL 23.3.0
)
PYOPENSSL_VERSION = LooseVersion(OpenSSL.__version__)
except (ImportError, AttributeError):
PYOPENSSL_IMP_ERR = traceback.format_exc()
PYOPENSSL_FOUND = False
else:
PYOPENSSL_FOUND = True
CRYPTOGRAPHY_IMP_ERR = None
try:
@@ -628,88 +607,6 @@ class Pkcs(OpenSSLObject):
self.pkcs12_bytes = content
class PkcsPyOpenSSL(Pkcs):
def __init__(self, module):
super(PkcsPyOpenSSL, self).__init__(module, "pyopenssl")
if self.encryption_level != "auto":
module.fail_json(
msg="The PyOpenSSL backend only supports encryption_level = auto"
)
def generate_bytes(self, module):
"""Generate PKCS#12 file archive."""
self.pkcs12 = crypto.PKCS12()
if self.other_certificates:
self.pkcs12.set_ca_certificates(self.other_certificates)
if self.certificate_content:
self.pkcs12.set_certificate(
load_certificate(
None, content=self.certificate_content, backend=self.backend
)
)
if self.friendly_name:
self.pkcs12.set_friendlyname(to_bytes(self.friendly_name))
if self.privatekey_content:
try:
self.pkcs12.set_privatekey(
load_privatekey(
None,
content=self.privatekey_content,
passphrase=self.privatekey_passphrase,
backend=self.backend,
)
)
except OpenSSLBadPassphraseError as exc:
raise PkcsError(exc)
return self.pkcs12.export(self.passphrase, self.iter_size, self.maciter_size)
def parse_bytes(self, pkcs12_content):
try:
p12 = crypto.load_pkcs12(pkcs12_content, self.passphrase)
pkey = p12.get_privatekey()
if pkey is not None:
pkey = crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)
crt = p12.get_certificate()
if crt is not None:
crt = crypto.dump_certificate(crypto.FILETYPE_PEM, crt)
other_certs = []
if p12.get_ca_certificates() is not None:
other_certs = [
crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert)
for other_cert in p12.get_ca_certificates()
]
friendly_name = p12.get_friendlyname()
return (pkey, crt, other_certs, friendly_name)
except crypto.Error as exc:
raise PkcsError(exc)
def _dump_privatekey(self, pkcs12):
pk = pkcs12.get_privatekey()
return crypto.dump_privatekey(crypto.FILETYPE_PEM, pk) if pk else None
def _dump_certificate(self, pkcs12):
cert = pkcs12.get_certificate()
return crypto.dump_certificate(crypto.FILETYPE_PEM, cert) if cert else None
def _dump_other_certificates(self, pkcs12):
if pkcs12.get_ca_certificates() is None:
return []
return [
crypto.dump_certificate(crypto.FILETYPE_PEM, other_cert)
for other_cert in pkcs12.get_ca_certificates()
]
def _get_friendly_name(self, pkcs12):
return pkcs12.get_friendlyname()
class PkcsCryptography(Pkcs):
def __init__(self, module):
super(PkcsCryptography, self).__init__(
@@ -839,52 +736,20 @@ def select_backend(module, backend):
CRYPTOGRAPHY_FOUND
and CRYPTOGRAPHY_VERSION >= LooseVersion(MINIMAL_CRYPTOGRAPHY_VERSION)
)
can_use_pyopenssl = (
PYOPENSSL_FOUND
and PYOPENSSL_VERSION >= LooseVersion(MINIMAL_PYOPENSSL_VERSION)
and PYOPENSSL_VERSION < LooseVersion(MAXIMAL_PYOPENSSL_VERSION)
)
# If no restrictions are provided, first try cryptography, then pyOpenSSL
if (
module.params["iter_size"] is not None
and module.params["encryption_level"] != "compatibility2022"
) or module.params["maciter_size"] is not None:
# If iter_size (for encryption_level != compatibility2022) or maciter_size is specified, use pyOpenSSL backend
backend = "pyopenssl"
elif can_use_cryptography:
if can_use_cryptography:
backend = "cryptography"
elif can_use_pyopenssl:
backend = "pyopenssl"
# Success?
if backend == "auto":
module.fail_json(
msg=(
"Cannot detect any of the required Python libraries "
"cryptography (>= {0}) or PyOpenSSL (>= {1}, < {2})"
"Cannot detect the required Python library cryptography (>= {0})"
).format(
MINIMAL_CRYPTOGRAPHY_VERSION,
MINIMAL_PYOPENSSL_VERSION,
MAXIMAL_PYOPENSSL_VERSION,
)
)
if backend == "pyopenssl":
if not PYOPENSSL_FOUND:
msg = missing_required_lib(
"pyOpenSSL >= {0}, < {1}".format(
MINIMAL_PYOPENSSL_VERSION, MAXIMAL_PYOPENSSL_VERSION
)
)
module.fail_json(msg=msg, exception=PYOPENSSL_IMP_ERR)
module.deprecate(
"The module is using the PyOpenSSL backend. This backend has been deprecated",
version="3.0.0",
collection_name="community.crypto",
)
return backend, PkcsPyOpenSSL(module)
elif backend == "cryptography":
if backend == "cryptography":
if not CRYPTOGRAPHY_FOUND:
module.fail_json(
msg=missing_required_lib(
@@ -924,7 +789,7 @@ def main():
backup=dict(type="bool", default=False),
return_content=dict(type="bool", default=False),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography", "pyopenssl"]
type="str", default="auto", choices=["auto", "cryptography"]
),
)

View File

@@ -39,10 +39,7 @@ attributes:
check_mode:
support: full
details:
- Currently in check mode, private keys will not be (re-)generated, only the changed status is set. This will change
in community.crypto 3.0.0.
- From community.crypto 3.0.0 on, the module will ignore check mode and always behave as if check mode is not active.
If you think this breaks your use-case of this module, please create an issue in the community.crypto repository.
- Since community.crypto 3.0.0, the module ignores check mode and always behaves as if check mode is not active.
options:
content:
description:

View File

@@ -33,10 +33,7 @@ attributes:
check_mode:
support: full
details:
- Currently in check mode, private keys will not be (re-)generated, only the changed status is set. This will change
in community.crypto 3.0.0.
- From community.crypto 3.0.0 on, the module will ignore check mode and always behave as if check mode is not active.
If you think this breaks your use-case of this module, please create an issue in the community.crypto repository.
- Since community.crypto 3.0.0 the module ignores check mode and always behaves as if check mode is not active.
options:
provider:
description:
@@ -162,18 +159,7 @@ class GenericCertificate(object):
def generate(self, module):
if self.module_backend.needs_regeneration():
if not self.check_mode:
self.module_backend.generate_certificate()
else:
self.module.deprecate(
"Check mode support for x509_certificate_pipe will change in community.crypto 3.0.0"
" to behave the same as without check mode. You can get that behavior right now"
" by adding `check_mode: false` to the x509_certificate_pipe task. If you think this"
" breaks your use-case of this module, please create an issue in the"
" community.crypto repository",
version="3.0.0",
collection_name="community.crypto",
)
self.module_backend.generate_certificate()
self.changed = True
def dump(self, check_mode=False):

View File

@@ -59,17 +59,9 @@ options:
- This parameter was called O(mode) before community.crypto 2.13.0. It has been renamed to avoid a collision with the
common O(mode) parameter for setting the CRL file's access mode.
type: str
# default: generate
default: generate
choices: [generate, update]
version_added: 2.13.0
mode:
description:
- This parameter has been renamed to O(crl_mode). The old name O(mode) is now deprecated and will be removed in community.crypto
3.0.0. Replace usage of this parameter with O(crl_mode).
- Note that from community.crypto 3.0.0 on, O(mode) will be used for the CRL file's mode.
type: str
# default: generate
choices: [generate, update]
force:
description:
@@ -968,16 +960,9 @@ def main():
state=dict(type="str", default="present", choices=["present", "absent"]),
crl_mode=dict(
type="str",
# default='generate',
default="generate",
choices=["generate", "update"],
),
mode=dict(
type="str",
# default='generate',
choices=["generate", "update"],
removed_in_version="3.0.0",
removed_from_collection="community.crypto",
),
force=dict(type="bool", default=False),
backup=dict(type="bool", default=False),
path=dict(type="path", required=True),
@@ -1044,16 +1029,6 @@ def main():
add_file_common_args=True,
)
if module.params["mode"]:
if module.params["crl_mode"]:
module.fail_json(
"You cannot use both `mode` and `crl_mode`. Use `crl_mode`."
)
module.params["crl_mode"] = module.params["mode"]
# TODO: in 3.0.0, once the option `mode` has been removed, remove this:
module.params.pop("mode", None)
# From then on, `mode` will be the file mode of the CRL file
if not CRYPTOGRAPHY_FOUND:
module.fail_json(
msg=missing_required_lib(