mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-03-26 21:33:25 +00:00
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:
14
changelogs/fragments/873-deprecation-removals.yml
Normal file
14
changelogs/fragments/873-deprecation-removals.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
removed_features:
|
||||
- "openssl_csr_pipe - the module now ignores check mode and will always behave as if check mode is not active (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "openssl_privatekey_pipe - the module now ignores check mode and will always behave as if check mode is not active (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "x509_certificate_pipe - the module now ignores check mode and will always behave as if check mode is not active (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "openssl_pkcs12 - support for the ``pyopenssl`` backend has been removed (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "crypto.module_backends.common module utils - this module utils has been removed. Use the ``argspec`` module utils instead (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "acme.acme module utils - the ``get_default_argspec()`` function has been removed. Use ``create_default_argspec()`` instead (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "acme.backends module utils - the methods ``get_ordered_csr_identifiers()`` and ``get_cert_information()`` of ``CryptoBackend`` now must be implemented (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "acme_* modules - support for ACME v1 has been removed (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "acme.documentation docs fragment - the ``documentation`` docs fragment has been removed. Use both the ``basic`` and ``account`` docs fragments in ``acme`` instead (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
breaking_changes:
|
||||
- "get_certificate - the default for ``asn1_base64`` changed from ``false`` to ``true`` (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "acme.certificates module utils - the ``retrieve_acme_v1_certificate()`` helper function has been removed (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
- "x509_crl - the ``mode`` parameter no longer denotes the update mode, but the CRL file mode. Use ``crl_mode`` instead for the update mode (https://github.com/ansible-collections/community.crypto/pull/873)."
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
namespace: community
|
||||
name: crypto
|
||||
version: 2.26.1
|
||||
version: 3.0.0-dev0
|
||||
readme: README.md
|
||||
authors:
|
||||
- Ansible (github.com/ansible)
|
||||
|
||||
@@ -50,37 +50,15 @@ class PrivateKeyModule(object):
|
||||
|
||||
if self.module_backend.needs_regeneration():
|
||||
# Regenerate
|
||||
if not self.check_mode:
|
||||
self.module_backend.generate_private_key()
|
||||
privatekey_data = self.module_backend.get_private_key_data()
|
||||
self.privatekey_bytes = privatekey_data
|
||||
else:
|
||||
self.module.deprecate(
|
||||
"Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0"
|
||||
" to behave the same as without check mode. You can get that behavior right now"
|
||||
" by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this"
|
||||
" breaks your use-case of this module, please create an issue in the"
|
||||
" community.crypto repository",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
self.module_backend.generate_private_key()
|
||||
privatekey_data = self.module_backend.get_private_key_data()
|
||||
self.privatekey_bytes = privatekey_data
|
||||
self.changed = True
|
||||
elif self.module_backend.needs_conversion():
|
||||
# Convert
|
||||
if not self.check_mode:
|
||||
self.module_backend.convert_private_key()
|
||||
privatekey_data = self.module_backend.get_private_key_data()
|
||||
self.privatekey_bytes = privatekey_data
|
||||
else:
|
||||
self.module.deprecate(
|
||||
"Check mode support for openssl_privatekey_pipe will change in community.crypto 3.0.0"
|
||||
" to behave the same as without check mode. You can get that behavior right now"
|
||||
" by adding `check_mode: false` to the openssl_privatekey_pipe task. If you think this"
|
||||
" breaks your use-case of this module, please create an issue in the"
|
||||
" community.crypto repository",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
self.module_backend.convert_private_key()
|
||||
privatekey_data = self.module_backend.get_private_key_data()
|
||||
self.privatekey_bytes = privatekey_data
|
||||
self.changed = True
|
||||
|
||||
def dump(self):
|
||||
|
||||
@@ -12,107 +12,6 @@ __metaclass__ = type
|
||||
|
||||
class ModuleDocFragment(object):
|
||||
|
||||
# Standard files documentation fragment
|
||||
#
|
||||
# NOTE: This document fragment is DEPRECATED and will be removed from community.crypto 3.0.0.
|
||||
# Use both the BASIC and ACCOUNT fragments as a replacement.
|
||||
DOCUMENTATION = r"""
|
||||
notes:
|
||||
- If a new enough version of the C(cryptography) library is available (see Requirements for details), it will be used instead
|
||||
of the C(openssl) binary. This can be explicitly disabled or enabled with the 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)).
|
||||
- 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:
|
||||
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
|
||||
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
|
||||
"""
|
||||
|
||||
# Basic documentation fragment without account data
|
||||
BASIC = r"""
|
||||
notes:
|
||||
@@ -130,11 +29,12 @@ 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
|
||||
- Must be V(2) for standardized ACME v2 endpoints.
|
||||
- The value V(1) is no longer supported since community.crypto 3.0.0.
|
||||
type: int
|
||||
choices: [1, 2]
|
||||
default: 2
|
||||
choices:
|
||||
- 2
|
||||
acme_directory:
|
||||
description:
|
||||
- The ACME directory to use. This is the entry point URL to access the ACME CA server API.
|
||||
|
||||
@@ -53,61 +53,49 @@ class ACMEAccount(object):
|
||||
"""
|
||||
contact = contact or []
|
||||
|
||||
if self.client.version == 1:
|
||||
new_reg = {"resource": "new-reg", "contact": contact}
|
||||
if agreement:
|
||||
new_reg["agreement"] = agreement
|
||||
else:
|
||||
new_reg["agreement"] = self.client.directory["meta"]["terms-of-service"]
|
||||
if external_account_binding is not None:
|
||||
raise ModuleFailException(
|
||||
"External account binding is not supported for ACME v1"
|
||||
)
|
||||
url = self.client.directory["new-reg"]
|
||||
else:
|
||||
if (
|
||||
external_account_binding is not None
|
||||
or self.client.directory["meta"].get("externalAccountRequired")
|
||||
) and allow_creation:
|
||||
# Some ACME servers such as ZeroSSL do not like it when you try to register an existing account
|
||||
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
|
||||
# to see whether the account already exists.
|
||||
if (
|
||||
external_account_binding is not None
|
||||
or self.client.directory["meta"].get("externalAccountRequired")
|
||||
) and allow_creation:
|
||||
# Some ACME servers such as ZeroSSL do not like it when you try to register an existing account
|
||||
# and provide external_account_binding credentials. Thus we first send a request with allow_creation=False
|
||||
# to see whether the account already exists.
|
||||
|
||||
# Note that we pass contact here: ZeroSSL does not accept registration calls without contacts, even
|
||||
# if onlyReturnExisting is set to true.
|
||||
created, data = self._new_reg(contact=contact, allow_creation=False)
|
||||
if data:
|
||||
# An account already exists! Return data
|
||||
return created, data
|
||||
# An account does not yet exist. Try to create one next.
|
||||
# Note that we pass contact here: ZeroSSL does not accept registration calls without contacts, even
|
||||
# if onlyReturnExisting is set to true.
|
||||
created, data = self._new_reg(contact=contact, allow_creation=False)
|
||||
if data:
|
||||
# An account already exists! Return data
|
||||
return created, data
|
||||
# An account does not yet exist. Try to create one next.
|
||||
|
||||
new_reg = {"contact": contact}
|
||||
if not allow_creation:
|
||||
# https://tools.ietf.org/html/rfc8555#section-7.3.1
|
||||
new_reg["onlyReturnExisting"] = True
|
||||
if terms_agreed:
|
||||
new_reg["termsOfServiceAgreed"] = True
|
||||
url = self.client.directory["newAccount"]
|
||||
if external_account_binding is not None:
|
||||
new_reg["externalAccountBinding"] = self.client.sign_request(
|
||||
{
|
||||
"alg": external_account_binding["alg"],
|
||||
"kid": external_account_binding["kid"],
|
||||
"url": url,
|
||||
},
|
||||
self.client.account_jwk,
|
||||
self.client.backend.create_mac_key(
|
||||
external_account_binding["alg"], external_account_binding["key"]
|
||||
),
|
||||
)
|
||||
elif (
|
||||
self.client.directory["meta"].get("externalAccountRequired")
|
||||
and allow_creation
|
||||
):
|
||||
raise ModuleFailException(
|
||||
"To create an account, an external account binding must be specified. "
|
||||
"Use the acme_account module with the external_account_binding option."
|
||||
)
|
||||
new_reg = {"contact": contact}
|
||||
if not allow_creation:
|
||||
# https://tools.ietf.org/html/rfc8555#section-7.3.1
|
||||
new_reg["onlyReturnExisting"] = True
|
||||
if terms_agreed:
|
||||
new_reg["termsOfServiceAgreed"] = True
|
||||
url = self.client.directory["newAccount"]
|
||||
if external_account_binding is not None:
|
||||
new_reg["externalAccountBinding"] = self.client.sign_request(
|
||||
{
|
||||
"alg": external_account_binding["alg"],
|
||||
"kid": external_account_binding["kid"],
|
||||
"url": url,
|
||||
},
|
||||
self.client.account_jwk,
|
||||
self.client.backend.create_mac_key(
|
||||
external_account_binding["alg"], external_account_binding["key"]
|
||||
),
|
||||
)
|
||||
elif (
|
||||
self.client.directory["meta"].get("externalAccountRequired")
|
||||
and allow_creation
|
||||
):
|
||||
raise ModuleFailException(
|
||||
"To create an account, an external account binding must be specified. "
|
||||
"Use the acme_account module with the external_account_binding option."
|
||||
)
|
||||
|
||||
result, info = self.client.send_signed_request(
|
||||
url, new_reg, fail_on_error=False
|
||||
@@ -120,12 +108,12 @@ class ACMEAccount(object):
|
||||
content=result,
|
||||
)
|
||||
|
||||
if info["status"] in ([200, 201] if self.client.version == 1 else [201]):
|
||||
if info["status"] == 201:
|
||||
# Account did not exist
|
||||
if "location" in info:
|
||||
self.client.set_account_uri(info["location"])
|
||||
return True, result
|
||||
elif info["status"] == (409 if self.client.version == 1 else 200):
|
||||
elif info["status"] == 200:
|
||||
# Account did exist
|
||||
if result.get("status") == "deactivated":
|
||||
# A bug in Pebble (https://github.com/letsencrypt/pebble/issues/179) and
|
||||
@@ -178,28 +166,21 @@ class ACMEAccount(object):
|
||||
"""
|
||||
if self.client.account_uri is None:
|
||||
raise ModuleFailException("Account URI unknown")
|
||||
if self.client.version == 1:
|
||||
# try POST-as-GET first (draft-15 or newer)
|
||||
data = None
|
||||
result, info = self.client.send_signed_request(
|
||||
self.client.account_uri, data, fail_on_error=False
|
||||
)
|
||||
# check whether that failed with a malformed request error
|
||||
if (
|
||||
info["status"] >= 400
|
||||
and result.get("type") == "urn:ietf:params:acme:error:malformed"
|
||||
):
|
||||
# retry as a regular POST (with no changed data) for pre-draft-15 ACME servers
|
||||
data = {}
|
||||
data["resource"] = "reg"
|
||||
result, info = self.client.send_signed_request(
|
||||
self.client.account_uri, data, fail_on_error=False
|
||||
)
|
||||
else:
|
||||
# try POST-as-GET first (draft-15 or newer)
|
||||
data = None
|
||||
result, info = self.client.send_signed_request(
|
||||
self.client.account_uri, data, fail_on_error=False
|
||||
)
|
||||
# check whether that failed with a malformed request error
|
||||
if (
|
||||
info["status"] >= 400
|
||||
and result.get("type") == "urn:ietf:params:acme:error:malformed"
|
||||
):
|
||||
# retry as a regular POST (with no changed data) for pre-draft-15 ACME servers
|
||||
data = {}
|
||||
result, info = self.client.send_signed_request(
|
||||
self.client.account_uri, data, fail_on_error=False
|
||||
)
|
||||
if not isinstance(result, Mapping):
|
||||
raise ACMEProtocolException(
|
||||
self.client.module,
|
||||
@@ -319,8 +300,6 @@ class ACMEAccount(object):
|
||||
account_data = dict(account_data)
|
||||
account_data.update(update_request)
|
||||
else:
|
||||
if self.client.version == 1:
|
||||
update_request["resource"] = "reg"
|
||||
account_data, info = self.client.send_signed_request(
|
||||
self.client.account_uri, update_request
|
||||
)
|
||||
|
||||
@@ -142,12 +142,6 @@ class ACMEDirectory(object):
|
||||
self.request_timeout = module.params["request_timeout"]
|
||||
|
||||
# Check whether self.version matches what we expect
|
||||
if self.version == 1:
|
||||
for key in ("new-reg", "new-authz", "new-cert"):
|
||||
if key not in self.directory:
|
||||
raise ModuleFailException(
|
||||
"ACME directory does not seem to follow protocol ACME v1"
|
||||
)
|
||||
if self.version == 2:
|
||||
for key in ("newNonce", "newAccount", "newOrder"):
|
||||
if key not in self.directory:
|
||||
@@ -168,7 +162,7 @@ class ACMEDirectory(object):
|
||||
return self.directory.get(key, default_value)
|
||||
|
||||
def get_nonce(self, resource=None):
|
||||
url = self.directory_root if self.version == 1 else self.directory["newNonce"]
|
||||
url = self.directory["newNonce"]
|
||||
if resource is not None:
|
||||
url = resource
|
||||
retry_count = 0
|
||||
@@ -260,9 +254,8 @@ class ACMEClient(object):
|
||||
requests.
|
||||
"""
|
||||
self.account_uri = uri
|
||||
if self.version != 1:
|
||||
self.account_jws_header.pop("jwk")
|
||||
self.account_jws_header["kid"] = self.account_uri
|
||||
self.account_jws_header.pop("jwk")
|
||||
self.account_jws_header["kid"] = self.account_uri
|
||||
|
||||
def parse_key(self, key_file=None, key_content=None, passphrase=None):
|
||||
"""
|
||||
@@ -339,8 +332,7 @@ class ACMEClient(object):
|
||||
while True:
|
||||
protected = copy.deepcopy(jws_header)
|
||||
protected["nonce"] = self.directory.get_nonce()
|
||||
if self.version != 1:
|
||||
protected["url"] = url
|
||||
protected["url"] = url
|
||||
|
||||
self._log("URL", url)
|
||||
self._log("protected", protected)
|
||||
@@ -348,10 +340,6 @@ class ACMEClient(object):
|
||||
data = self.sign_request(
|
||||
protected, payload, key_data, encode_payload=encode_payload
|
||||
)
|
||||
if self.version == 1:
|
||||
data["header"] = jws_header.copy()
|
||||
for k, v in protected.items():
|
||||
data["header"].pop(k, None)
|
||||
self._log("signed request", data)
|
||||
data = self.module.jsonify(data)
|
||||
|
||||
@@ -440,7 +428,7 @@ class ACMEClient(object):
|
||||
Perform a GET-like request. Will try POST-as-GET for ACMEv2, with fallback
|
||||
to GET if server replies with a status code of 405.
|
||||
"""
|
||||
if not get_only and self.version != 1:
|
||||
if not get_only:
|
||||
# Try POST-as-GET
|
||||
content, info = self.send_signed_request(
|
||||
uri, None, parse_json_result=False, fail_on_error=False
|
||||
@@ -551,27 +539,6 @@ class ACMEClient(object):
|
||||
return data
|
||||
|
||||
|
||||
def get_default_argspec():
|
||||
"""
|
||||
Provides default argument spec for the options documented in the acme doc fragment.
|
||||
|
||||
DEPRECATED: will be removed in community.crypto 3.0.0
|
||||
"""
|
||||
return dict(
|
||||
acme_directory=dict(type="str", required=True),
|
||||
acme_version=dict(type="int", required=True, choices=[1, 2]),
|
||||
validate_certs=dict(type="bool", default=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "openssl", "cryptography"]
|
||||
),
|
||||
request_timeout=dict(type="int", default=10),
|
||||
account_key_src=dict(type="path", aliases=["account_key"]),
|
||||
account_key_content=dict(type="str", no_log=True),
|
||||
account_key_passphrase=dict(type="str", no_log=True),
|
||||
account_uri=dict(type="str"),
|
||||
)
|
||||
|
||||
|
||||
def create_default_argspec(
|
||||
with_account=True,
|
||||
require_account_key=True,
|
||||
@@ -583,7 +550,7 @@ def create_default_argspec(
|
||||
result = ArgumentSpec(
|
||||
argument_spec=dict(
|
||||
acme_directory=dict(type="str", required=True),
|
||||
acme_version=dict(type="int", required=True, choices=[1, 2]),
|
||||
acme_version=dict(type="int", choices=[2], default=2),
|
||||
validate_certs=dict(type="bool", default=True),
|
||||
select_crypto_backend=dict(
|
||||
type="str", default="auto", choices=["auto", "openssl", "cryptography"]
|
||||
@@ -613,7 +580,7 @@ def create_default_argspec(
|
||||
return result
|
||||
|
||||
|
||||
def create_backend(module, needs_acme_v2):
|
||||
def create_backend(module, needs_acme_v2=True):
|
||||
if not HAS_IPADDRESS:
|
||||
module.fail_json(
|
||||
msg=missing_required_lib("ipaddress"), exception=IPADDRESS_IMPORT_ERROR
|
||||
@@ -666,18 +633,6 @@ def create_backend(module, needs_acme_v2):
|
||||
"development purposes, but *never* for production purposes."
|
||||
)
|
||||
|
||||
if needs_acme_v2 and module.params["acme_version"] < 2:
|
||||
module.fail_json(
|
||||
msg="The {0} module requires the ACME v2 protocol!".format(module._name)
|
||||
)
|
||||
|
||||
if module.params["acme_version"] == 1:
|
||||
module.deprecate(
|
||||
"The value 1 for 'acme_version' is deprecated. Please switch to ACME v2",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
|
||||
# AnsibleModule() changes the locale, so change it back to C because we rely
|
||||
# on datetime.datetime.strptime() when parsing certificate dates.
|
||||
locale.setlocale(locale.LC_ALL, "C")
|
||||
|
||||
@@ -150,6 +150,7 @@ class CryptoBackend(object):
|
||||
def create_mac_key(self, alg, key):
|
||||
"""Create a MAC key."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_ordered_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||
"""
|
||||
Return a list of requested identifiers (CN and SANs) for the CSR.
|
||||
@@ -159,15 +160,6 @@ class CryptoBackend(object):
|
||||
The list is deduplicated, and if a CNAME is present, it will be returned
|
||||
as the first element in the result.
|
||||
"""
|
||||
self.module.deprecate(
|
||||
"Every backend must override the get_ordered_csr_identifiers() method."
|
||||
" The default implementation will be removed in 3.0.0 and this method will be marked as `abstractmethod` by then.",
|
||||
version="3.0.0",
|
||||
collection_name="community.crypto",
|
||||
)
|
||||
return sorted(
|
||||
self.get_csr_identifiers(csr_filename=csr_filename, csr_content=csr_content)
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_csr_identifiers(self, csr_filename=None, csr_content=None):
|
||||
@@ -193,11 +185,8 @@ class CryptoBackend(object):
|
||||
Given a Criterium object, creates a ChainMatcher object.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
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()")
|
||||
|
||||
@@ -19,7 +19,6 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.errors impor
|
||||
)
|
||||
from ansible_collections.community.crypto.plugins.module_utils.acme.utils import (
|
||||
der_to_pem,
|
||||
nopad_b64,
|
||||
process_links,
|
||||
)
|
||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||
@@ -116,33 +115,3 @@ class ChainMatcher(object):
|
||||
"""
|
||||
Check whether a certificate chain (CertificateChain instance) matches.
|
||||
"""
|
||||
|
||||
|
||||
def retrieve_acme_v1_certificate(client, csr_der):
|
||||
"""
|
||||
Create a new certificate based on the CSR (ACME v1 protocol).
|
||||
Return the certificate object as dict
|
||||
https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.5
|
||||
"""
|
||||
new_cert = {
|
||||
"resource": "new-cert",
|
||||
"csr": nopad_b64(csr_der),
|
||||
}
|
||||
result, info = client.send_signed_request(
|
||||
client.directory["new-cert"],
|
||||
new_cert,
|
||||
error_msg="Failed to receive certificate",
|
||||
expected_status_codes=[200, 201],
|
||||
)
|
||||
cert = CertificateChain(info["location"])
|
||||
cert.cert = der_to_pem(result)
|
||||
|
||||
def f(link, relation):
|
||||
if relation == "up":
|
||||
chain_result, chain_info = client.get_request(link, parse_json_result=False)
|
||||
if chain_info["status"] in [200, 201]:
|
||||
del cert.chain[:]
|
||||
cert.chain.append(der_to_pem(chain_result))
|
||||
|
||||
process_links(info, f)
|
||||
return cert
|
||||
|
||||
@@ -79,16 +79,10 @@ class Challenge(object):
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, client, data, url=None):
|
||||
return cls(data, url or (data["uri"] if client.version == 1 else data["url"]))
|
||||
return cls(data, url or data["url"])
|
||||
|
||||
def call_validate(self, client):
|
||||
challenge_response = {}
|
||||
if client.version == 1:
|
||||
token = re.sub(r"[^A-Za-z0-9_\-]", "_", self.token)
|
||||
key_authorization = create_key_authorization(client, token)
|
||||
challenge_response["resource"] = "challenge"
|
||||
challenge_response["keyAuthorization"] = key_authorization
|
||||
challenge_response["type"] = self.type
|
||||
client.send_signed_request(
|
||||
self.url,
|
||||
challenge_response,
|
||||
@@ -160,13 +154,7 @@ class Authorization(object):
|
||||
]
|
||||
else:
|
||||
self.challenges = []
|
||||
if client.version == 1 and "status" not in data:
|
||||
# https://tools.ietf.org/html/draft-ietf-acme-acme-02#section-6.1.2
|
||||
# "status (required, string): ...
|
||||
# If this field is missing, then the default value is "pending"."
|
||||
self.status = "pending"
|
||||
else:
|
||||
self.status = data["status"]
|
||||
self.status = data["status"]
|
||||
self.identifier = data["identifier"]["value"]
|
||||
self.identifier_type = data["identifier"]["type"]
|
||||
if data.get("wildcard", False):
|
||||
@@ -206,15 +194,11 @@ class Authorization(object):
|
||||
"value": identifier,
|
||||
},
|
||||
}
|
||||
if client.version == 1:
|
||||
url = client.directory["new-authz"]
|
||||
new_authz["resource"] = "new-authz"
|
||||
else:
|
||||
if "newAuthz" not in client.directory.directory:
|
||||
raise ACMEProtocolException(
|
||||
client.module, "ACME endpoint does not support pre-authorization"
|
||||
)
|
||||
url = client.directory["newAuthz"]
|
||||
if "newAuthz" not in client.directory.directory:
|
||||
raise ACMEProtocolException(
|
||||
client.module, "ACME endpoint does not support pre-authorization"
|
||||
)
|
||||
url = client.directory["newAuthz"]
|
||||
|
||||
result, info = client.send_signed_request(
|
||||
url,
|
||||
@@ -338,8 +322,6 @@ class Authorization(object):
|
||||
if not self.can_deactivate():
|
||||
return
|
||||
authz_deactivate = {"status": "deactivated"}
|
||||
if client.version == 1:
|
||||
authz_deactivate["resource"] = "authz"
|
||||
result, info = client.send_signed_request(
|
||||
self.url, authz_deactivate, fail_on_error=False
|
||||
)
|
||||
@@ -357,8 +339,6 @@ class Authorization(object):
|
||||
"""
|
||||
authz = cls(url)
|
||||
authz_deactivate = {"status": "deactivated"}
|
||||
if client.version == 1:
|
||||
authz_deactivate["resource"] = "authz"
|
||||
result, info = client.send_signed_request(
|
||||
url, authz_deactivate, fail_on_error=True
|
||||
)
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
# -*- 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
|
||||
from ansible_collections.community.crypto.plugins.module_utils.argspec import (
|
||||
ArgumentSpec as _ArgumentSpec,
|
||||
)
|
||||
|
||||
|
||||
class ArgumentSpec(_ArgumentSpec):
|
||||
def create_ansible_module_helper(self, clazz, args, **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")
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 }}"
|
||||
|
||||
@@ -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). "
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"]
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -5,6 +5,5 @@
|
||||
|
||||
dependencies:
|
||||
- setup_acme
|
||||
- setup_pyopenssl # needed for Ubuntu 16.04
|
||||
- setup_remote_tmp_dir
|
||||
- prepare_jinja2_compat
|
||||
|
||||
@@ -5,5 +5,4 @@
|
||||
|
||||
dependencies:
|
||||
- setup_openssl
|
||||
- setup_pyopenssl
|
||||
- setup_remote_tmp_dir
|
||||
|
||||
@@ -62,17 +62,6 @@
|
||||
path: '{{ remote_tmp_dir }}/ansible.p12'
|
||||
state: absent
|
||||
|
||||
- block:
|
||||
- name: Running tests with pyOpenSSL backend
|
||||
include_tasks: impl.yml
|
||||
vars:
|
||||
select_crypto_backend: pyopenssl
|
||||
|
||||
when: >-
|
||||
(pyopenssl_version.stdout | default('0.0')) is version('0.15', '>=')
|
||||
and
|
||||
(pyopenssl_version.stdout | default('0.0')) is version('23.3.0', '<')
|
||||
|
||||
- block:
|
||||
- name: Running tests with cryptography backend
|
||||
include_tasks: impl.yml
|
||||
@@ -82,10 +71,4 @@
|
||||
when: cryptography_version.stdout is version('3.0', '>=')
|
||||
|
||||
when: >-
|
||||
(
|
||||
(pyopenssl_version.stdout | default('0.0')) is version('0.15', '>=')
|
||||
and
|
||||
(pyopenssl_version.stdout | default('0.0')) is version('23.3.0', '<')
|
||||
)
|
||||
or
|
||||
cryptography_version.stdout is version('3.0', '>=')
|
||||
|
||||
@@ -81,8 +81,7 @@
|
||||
- name: '({{ select_crypto_backend }}) Load "empty" file'
|
||||
set_fact:
|
||||
empty_contents: "{{ slurp.results[0].content | b64decode }}"
|
||||
empty_expected_pyopenssl: "{{ (slurp.results[2].content | b64decode) ~ (slurp.results[1].content | b64decode) }}"
|
||||
empty_expected_cryptography: "{{ (slurp.results[1].content | b64decode) ~ (slurp.results[2].content | b64decode) }}"
|
||||
empty_expected: "{{ (slurp.results[1].content | b64decode) ~ (slurp.results[2].content | b64decode) }}"
|
||||
|
||||
- name: '({{ select_crypto_backend }}) Check "empty" file'
|
||||
assert:
|
||||
@@ -91,7 +90,7 @@
|
||||
- p12_empty_idem is not changed
|
||||
- p12_empty_concat_idem is not changed
|
||||
- p12_empty_concat_content_idem is not changed
|
||||
- (empty_contents == empty_expected_cryptography) or (empty_contents == empty_expected_pyopenssl and select_crypto_backend == 'pyopenssl')
|
||||
- empty_contents == empty_expected
|
||||
|
||||
- name: '({{ select_crypto_backend }}) PKCS#12 with compatibility2022 settings'
|
||||
when:
|
||||
|
||||
@@ -91,9 +91,11 @@
|
||||
- assert:
|
||||
that:
|
||||
- update_check is changed
|
||||
- update_check.privatekey == 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||
- update_check.privatekey != 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||
- update_check.privatekey != result.privatekey
|
||||
- update_check_return is changed
|
||||
- update_check_return.privatekey == result.privatekey
|
||||
- update_check_return.privatekey != 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
|
||||
- update_check_return.privatekey != result.privatekey
|
||||
- update is changed
|
||||
- update.privatekey != result.privatekey
|
||||
- update_info.public_data.size == default_rsa_key_size
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
has_pyopenssl: true
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
# 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_python_info
|
||||
- setup_remote_constraints
|
||||
- setup_pkg_mgr
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
# 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 #
|
||||
####################################################################
|
||||
|
||||
- name: Install from system packages
|
||||
when: ansible_os_family != "Darwin" and target_system_python
|
||||
block:
|
||||
|
||||
- name: Include OS-specific variables
|
||||
include_vars: '{{ lookup("first_found", search) }}'
|
||||
vars:
|
||||
search:
|
||||
files:
|
||||
- '{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml'
|
||||
- '{{ ansible_distribution }}-{{ ansible_distribution_version }}.yml'
|
||||
- '{{ ansible_distribution }}.yml'
|
||||
- '{{ ansible_os_family }}.yml'
|
||||
paths:
|
||||
- vars
|
||||
|
||||
- when: has_pyopenssl
|
||||
block:
|
||||
|
||||
- name: Install pyOpenSSL (Python 3 from system packages)
|
||||
become: true
|
||||
package:
|
||||
name: '{{ pyopenssl_package_name_python3 }}'
|
||||
when: ansible_python_version is version('3.0', '>=')
|
||||
|
||||
- name: Install pyOpenSSL (Python 2 from system packages)
|
||||
become: true
|
||||
package:
|
||||
name: '{{ pyopenssl_package_name }}'
|
||||
when: ansible_python_version is version('3.0', '<')
|
||||
|
||||
- name: Install from PyPi
|
||||
when: ansible_os_family == "Darwin" or not target_system_python
|
||||
block:
|
||||
|
||||
- name: Install pyOpenSSL (PyPi)
|
||||
become: true
|
||||
pip:
|
||||
name: pyOpenSSL
|
||||
state: "{{ 'latest' if not target_system_python_cannot_upgrade_cryptography else omit }}"
|
||||
extra_args: "-c {{ remote_constraints }}"
|
||||
|
||||
- when: has_pyopenssl
|
||||
block:
|
||||
|
||||
- name: Register pyOpenSSL version
|
||||
command: "{{ ansible_python.executable }} -c 'import OpenSSL; print(OpenSSL.__version__)'"
|
||||
register: pyopenssl_version
|
||||
|
||||
- name: Register pyOpenSSL debug details
|
||||
command: "{{ ansible_python.executable }} -m OpenSSL.debug"
|
||||
register: pyopenssl_debug_version
|
||||
ignore_errors: true
|
||||
|
||||
# Depending on which pyOpenSSL version has been installed, it could be that cryptography has
|
||||
# been upgraded to a newer version. Make sure to register cryptography_version another time here
|
||||
# to avoid strange testing behavior due to wrong values of cryptography_version.
|
||||
- name: Register cryptography version
|
||||
command: "{{ ansible_python.executable }} -c 'import cryptography; print(cryptography.__version__)'"
|
||||
register: cryptography_version
|
||||
ignore_errors: true # in case cryptography was not installed, and setup_openssl hasn't been run before, ignore errors
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
pyopenssl_package_name: py-openssl
|
||||
pyopenssl_package_name_python3: py3-openssl
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
pyopenssl_package_name: python-pyopenssl
|
||||
pyopenssl_package_name_python3: python-pyopenssl
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
pyopenssl_package_name: python-openssl
|
||||
pyopenssl_package_name_python3: python3-openssl
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
pyopenssl_package_name: py27-openssl
|
||||
pyopenssl_package_name_python3: "py{{ ansible_python.version.major }}{{ ansible_python.version.minor }}-openssl"
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
has_pyopenssl: false
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
pyopenssl_package_name: pyOpenSSL
|
||||
pyopenssl_package_name_python3: python3-pyOpenSSL
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
|
||||
pyopenssl_package_name: python-pyOpenSSL
|
||||
pyopenssl_package_name_python3: python3-pyOpenSSL
|
||||
@@ -5,6 +5,5 @@
|
||||
|
||||
dependencies:
|
||||
- setup_acme
|
||||
- setup_pyopenssl # needed for Ubuntu 16.04
|
||||
- setup_remote_tmp_dir
|
||||
- prepare_jinja2_compat
|
||||
|
||||
@@ -68,20 +68,6 @@ def test_challenge_from_to_json():
|
||||
assert challenge.token == "foo"
|
||||
assert challenge.to_json() == data
|
||||
|
||||
data = {
|
||||
"uri": "xxx",
|
||||
"type": "type",
|
||||
"status": "valid",
|
||||
}
|
||||
client.version = 1
|
||||
challenge = Challenge.from_json(client, data)
|
||||
assert challenge.data == data
|
||||
assert challenge.type == "type"
|
||||
assert challenge.url == "xxx"
|
||||
assert challenge.status == "valid"
|
||||
assert challenge.token is None
|
||||
assert challenge.to_json() == data
|
||||
|
||||
|
||||
def test_authorization_from_to_json():
|
||||
client = MagicMock()
|
||||
@@ -154,30 +140,6 @@ def test_authorization_from_to_json():
|
||||
"wildcard": True,
|
||||
}
|
||||
|
||||
client.version = 1
|
||||
|
||||
data = {
|
||||
"challenges": [],
|
||||
"identifier": {
|
||||
"type": "dns",
|
||||
"value": "example.com",
|
||||
},
|
||||
}
|
||||
authz = Authorization.from_json(client, data, "xxx")
|
||||
assert authz.url == "xxx"
|
||||
assert authz.status == "pending"
|
||||
assert authz.identifier == "example.com"
|
||||
assert authz.identifier_type == "dns"
|
||||
assert authz.challenges == []
|
||||
assert authz.to_json() == {
|
||||
"uri": "xxx",
|
||||
"challenges": [],
|
||||
"identifier": {
|
||||
"type": "dns",
|
||||
"value": "example.com",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_authorization_create_error():
|
||||
client = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user