mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-03-26 21:33:25 +00:00
Work on issues found by pylint (#896)
* Look at possibly-used-before-assignment. * Use latest beta releases of ansible-core 2.19 for mypy and pylint. * Look at unsupported-*. * Look at unknown-option-value. * Look at redefined-builtin. * Look at superfluous-parens. * Look at unspecified-encoding. * Adjust to new cryptography version and to ansible-core 2.17's pylint. * Look at super-with-arguments. * Look at no-else-*. * Look at try-except-raise. * Look at inconsistent-return-statements. * Look at redefined-outer-name. * Look at redefined-argument-from-local. * Look at attribute-defined-outside-init. * Look at unused-variable. * Look at protected-access. * Look at raise-missing-from. * Look at arguments-differ. * Look at useless-suppression and use-symbolic-message-instead. * Look at consider-using-dict-items. * Look at consider-using-in. * Look at consider-using-set-comprehension. * Look at consider-using-with. * Look at use-dict-literal.
This commit is contained in:
29
.pylintrc
29
.pylintrc
@@ -356,11 +356,10 @@ disable=raw-checker-failed,
|
|||||||
missing-module-docstring,
|
missing-module-docstring,
|
||||||
locally-disabled,
|
locally-disabled,
|
||||||
suppressed-message,
|
suppressed-message,
|
||||||
useless-suppression,
|
|
||||||
use-symbolic-message-instead,
|
|
||||||
use-implicit-booleaness-not-comparison,
|
use-implicit-booleaness-not-comparison,
|
||||||
use-implicit-booleaness-not-comparison-to-string,
|
use-implicit-booleaness-not-comparison-to-string,
|
||||||
use-implicit-booleaness-not-comparison-to-zero,
|
use-implicit-booleaness-not-comparison-to-zero,
|
||||||
|
superfluous-parens,
|
||||||
too-few-public-methods,
|
too-few-public-methods,
|
||||||
too-many-arguments,
|
too-many-arguments,
|
||||||
too-many-boolean-expressions,
|
too-many-boolean-expressions,
|
||||||
@@ -378,35 +377,13 @@ disable=raw-checker-failed,
|
|||||||
wrong-import-order,
|
wrong-import-order,
|
||||||
wrong-import-position,
|
wrong-import-position,
|
||||||
# To clean up:
|
# To clean up:
|
||||||
arguments-differ,
|
|
||||||
attribute-defined-outside-init,
|
|
||||||
broad-exception-caught,
|
broad-exception-caught,
|
||||||
broad-exception-raised,
|
broad-exception-raised,
|
||||||
consider-using-dict-items,
|
|
||||||
consider-using-in,
|
|
||||||
consider-using-set-comprehension,
|
|
||||||
consider-using-with,
|
|
||||||
fixme,
|
fixme,
|
||||||
inconsistent-return-statements,
|
|
||||||
invalid-name,
|
invalid-name,
|
||||||
no-else-raise,
|
|
||||||
no-else-return,
|
|
||||||
possibly-used-before-assignment,
|
|
||||||
protected-access,
|
|
||||||
raise-missing-from,
|
|
||||||
redefined-argument-from-local,
|
|
||||||
redefined-builtin,
|
|
||||||
redefined-outer-name,
|
|
||||||
superfluous-parens,
|
|
||||||
super-with-arguments,
|
|
||||||
try-except-raise,
|
|
||||||
unknown-option-value,
|
|
||||||
unspecified-encoding,
|
|
||||||
unsupported-assignment-operation,
|
|
||||||
unsupported-binary-operation,
|
|
||||||
unused-argument,
|
unused-argument,
|
||||||
unused-variable,
|
# Cannot remove yet due to inadequacy of rules
|
||||||
use-dict-literal,
|
inconsistent-return-statements, # doesn't notice that fail_json() does not return
|
||||||
|
|
||||||
# Enable the message, report, category or checker with the given id(s). You can
|
# Enable the message, report, category or checker with the given id(s). You can
|
||||||
# either give multiple identifier separated by comma (,) or put this option
|
# either give multiple identifier separated by comma (,) or put this option
|
||||||
|
|||||||
@@ -15,12 +15,13 @@ run_flake8 = true
|
|||||||
flake8_config = ".flake8"
|
flake8_config = ".flake8"
|
||||||
run_pylint = true
|
run_pylint = true
|
||||||
pylint_rcfile = ".pylintrc"
|
pylint_rcfile = ".pylintrc"
|
||||||
|
pylint_ansible_core_package = "ansible-core>=2.19.0b4"
|
||||||
run_yamllint = true
|
run_yamllint = true
|
||||||
yamllint_config = ".yamllint"
|
yamllint_config = ".yamllint"
|
||||||
yamllint_config_plugins = ".yamllint-docs"
|
yamllint_config_plugins = ".yamllint-docs"
|
||||||
yamllint_config_plugins_examples = ".yamllint-examples"
|
yamllint_config_plugins_examples = ".yamllint-examples"
|
||||||
run_mypy = true
|
run_mypy = true
|
||||||
mypy_ansible_core_package = "ansible-core>=2.19.0b3"
|
mypy_ansible_core_package = "ansible-core>=2.19.0b4"
|
||||||
mypy_config = ".mypy.ini"
|
mypy_config = ".mypy.ini"
|
||||||
mypy_extra_deps = [
|
mypy_extra_deps = [
|
||||||
"cryptography",
|
"cryptography",
|
||||||
|
|||||||
@@ -60,14 +60,14 @@ class PrivateKeyModule:
|
|||||||
if self.module_backend.needs_regeneration():
|
if self.module_backend.needs_regeneration():
|
||||||
# Regenerate
|
# Regenerate
|
||||||
self.module_backend.generate_private_key()
|
self.module_backend.generate_private_key()
|
||||||
privatekey_data = self.module_backend.get_private_key_data()
|
# Call get_private_key_data() to make sure that exceptions are raised now:
|
||||||
self.privatekey_bytes = privatekey_data
|
self.module_backend.get_private_key_data()
|
||||||
self.changed = True
|
self.changed = True
|
||||||
elif self.module_backend.needs_conversion():
|
elif self.module_backend.needs_conversion():
|
||||||
# Convert
|
# Convert
|
||||||
self.module_backend.convert_private_key()
|
self.module_backend.convert_private_key()
|
||||||
privatekey_data = self.module_backend.get_private_key_data()
|
# Call get_private_key_data() to make sure that exceptions are raised now:
|
||||||
self.privatekey_bytes = privatekey_data
|
self.module_backend.get_private_key_data()
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
def dump(self) -> dict[str, t.Any]:
|
def dump(self) -> dict[str, t.Any]:
|
||||||
@@ -80,22 +80,20 @@ class PrivateKeyModule:
|
|||||||
|
|
||||||
|
|
||||||
class ActionModule(ActionModuleBase):
|
class ActionModule(ActionModuleBase):
|
||||||
@staticmethod
|
def setup_module(self) -> tuple[ArgumentSpec, dict[str, t.Any]]:
|
||||||
def setup_module() -> tuple[ArgumentSpec, dict[str, t.Any]]:
|
|
||||||
argument_spec = get_privatekey_argument_spec()
|
argument_spec = get_privatekey_argument_spec()
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
content=dict(type="str", no_log=True),
|
"content": {"type": "str", "no_log": True},
|
||||||
content_base64=dict(type="bool", default=False),
|
"content_base64": {"type": "bool", "default": False},
|
||||||
return_current_key=dict(type="bool", default=False),
|
"return_current_key": {"type": "bool", "default": False},
|
||||||
)
|
}
|
||||||
)
|
|
||||||
return argument_spec, dict(
|
|
||||||
supports_check_mode=True,
|
|
||||||
)
|
)
|
||||||
|
return argument_spec, {
|
||||||
|
"supports_check_mode": True,
|
||||||
|
}
|
||||||
|
|
||||||
@staticmethod
|
def run_module(self, module: AnsibleActionModule) -> None:
|
||||||
def run_module(module: AnsibleActionModule) -> None:
|
|
||||||
module_backend = select_backend(module=module)
|
module_backend = select_backend(module=module)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -52,16 +52,18 @@ from ansible_collections.community.crypto.plugins.plugin_utils._gnupg import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def gpg_fingerprint(input: str | bytes) -> str:
|
def gpg_fingerprint(gpg_key_content: str | bytes) -> str:
|
||||||
if not isinstance(input, (str, bytes)):
|
if not isinstance(gpg_key_content, (str, bytes)):
|
||||||
raise AnsibleFilterError(
|
raise AnsibleFilterError(
|
||||||
f"The input for the community.crypto.gpg_fingerprint filter must be a string; got {type(input)} instead"
|
f"The input for the community.crypto.gpg_fingerprint filter must be a string; got {type(gpg_key_content)} instead"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
gpg = PluginGPGRunner()
|
gpg = PluginGPGRunner()
|
||||||
return get_fingerprint_from_bytes(gpg_runner=gpg, content=to_bytes(input))
|
return get_fingerprint_from_bytes(
|
||||||
|
gpg_runner=gpg, content=to_bytes(gpg_key_content)
|
||||||
|
)
|
||||||
except GPGError as exc:
|
except GPGError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ def openssl_csr_info_filter(
|
|||||||
module=module, content=to_bytes(data), validate_signature=True
|
module=module, content=to_bytes(data), validate_signature=True
|
||||||
)
|
)
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -193,9 +193,9 @@ def openssl_privatekey_info_filter(
|
|||||||
result.pop("key_is_consistent", None)
|
result.pop("key_is_consistent", None)
|
||||||
return result
|
return result
|
||||||
except PrivateKeyParseError as exc:
|
except PrivateKeyParseError as exc:
|
||||||
raise AnsibleFilterError(exc.error_message)
|
raise AnsibleFilterError(exc.error_message) from exc
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -150,9 +150,9 @@ def openssl_publickey_info_filter(data: str | bytes) -> dict[str, t.Any]:
|
|||||||
try:
|
try:
|
||||||
return get_publickey_info(module=module, content=to_bytes(data))
|
return get_publickey_info(module=module, content=to_bytes(data))
|
||||||
except PublicKeyParseError as exc:
|
except PublicKeyParseError as exc:
|
||||||
raise AnsibleFilterError(exc.error_message)
|
raise AnsibleFilterError(exc.error_message) from exc
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -48,15 +48,15 @@ from ansible_collections.community.crypto.plugins.module_utils._serial import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_serial_filter(input: str | bytes) -> int:
|
def parse_serial_filter(serial_str: str | bytes) -> int:
|
||||||
if not isinstance(input, (str, bytes)):
|
if not isinstance(serial_str, (str, bytes)):
|
||||||
raise AnsibleFilterError(
|
raise AnsibleFilterError(
|
||||||
f"The input for the community.crypto.parse_serial filter must be a string; got {type(input)} instead"
|
f"The input for the community.crypto.parse_serial filter must be a string; got {type(serial_str)} instead"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return parse_serial(to_native(input))
|
return parse_serial(to_native(serial_str))
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -45,19 +45,19 @@ from ansible.errors import AnsibleFilterError
|
|||||||
from ansible_collections.community.crypto.plugins.module_utils._serial import to_serial
|
from ansible_collections.community.crypto.plugins.module_utils._serial import to_serial
|
||||||
|
|
||||||
|
|
||||||
def to_serial_filter(input: int) -> str:
|
def to_serial_filter(serial_int: int) -> str:
|
||||||
if not isinstance(input, int):
|
if not isinstance(serial_int, int):
|
||||||
raise AnsibleFilterError(
|
raise AnsibleFilterError(
|
||||||
f"The input for the community.crypto.to_serial filter must be an integer; got {type(input)} instead"
|
f"The input for the community.crypto.to_serial filter must be an integer; got {type(serial_int)} instead"
|
||||||
)
|
)
|
||||||
if input < 0:
|
if serial_int < 0:
|
||||||
raise AnsibleFilterError(
|
raise AnsibleFilterError(
|
||||||
"The input for the community.crypto.to_serial filter must not be negative"
|
"The input for the community.crypto.to_serial filter must not be negative"
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
return to_serial(input)
|
return to_serial(serial_int)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ def x509_certificate_info_filter(
|
|||||||
try:
|
try:
|
||||||
return get_certificate_info(module=module, content=to_bytes(data))
|
return get_certificate_info(module=module, content=to_bytes(data))
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ def x509_crl_info_filter(
|
|||||||
list_revoked_certificates=list_revoked_certificates,
|
list_revoked_certificates=list_revoked_certificates,
|
||||||
)
|
)
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise AnsibleFilterError(str(exc))
|
raise AnsibleFilterError(str(exc)) from exc
|
||||||
|
|
||||||
|
|
||||||
class FilterModule:
|
class FilterModule:
|
||||||
|
|||||||
@@ -76,4 +76,4 @@ class LookupModule(LookupBase):
|
|||||||
)
|
)
|
||||||
return result
|
return result
|
||||||
except GPGError as exc:
|
except GPGError as exc:
|
||||||
raise AnsibleLookupError(str(exc))
|
raise AnsibleLookupError(str(exc)) from exc
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class ACMEAccount:
|
|||||||
if "location" in info:
|
if "location" in info:
|
||||||
self.client.set_account_uri(info["location"])
|
self.client.set_account_uri(info["location"])
|
||||||
return True, result
|
return True, result
|
||||||
elif info["status"] == 200:
|
if info["status"] == 200:
|
||||||
# Account did exist
|
# Account did exist
|
||||||
if result.get("status") == "deactivated":
|
if result.get("status") == "deactivated":
|
||||||
# A bug in Pebble (https://github.com/letsencrypt/pebble/issues/179) and
|
# A bug in Pebble (https://github.com/letsencrypt/pebble/issues/179) and
|
||||||
@@ -130,12 +130,11 @@ class ACMEAccount:
|
|||||||
# requests authorized by that account's key."
|
# requests authorized by that account's key."
|
||||||
if not allow_creation:
|
if not allow_creation:
|
||||||
return False, None
|
return False, None
|
||||||
else:
|
raise ModuleFailException("Account is deactivated")
|
||||||
raise ModuleFailException("Account is deactivated")
|
|
||||||
if "location" in info:
|
if "location" in info:
|
||||||
self.client.set_account_uri(info["location"])
|
self.client.set_account_uri(info["location"])
|
||||||
return False, result
|
return False, result
|
||||||
elif (
|
if (
|
||||||
info["status"] in (400, 404)
|
info["status"] in (400, 404)
|
||||||
and result["type"] == "urn:ietf:params:acme:error:accountDoesNotExist"
|
and result["type"] == "urn:ietf:params:acme:error:accountDoesNotExist"
|
||||||
and not allow_creation
|
and not allow_creation
|
||||||
@@ -144,7 +143,7 @@ class ACMEAccount:
|
|||||||
# (According to RFC 8555, Section 7.3.1, the HTTP status code MUST be 400.
|
# (According to RFC 8555, Section 7.3.1, the HTTP status code MUST be 400.
|
||||||
# Unfortunately Digicert does not care and sends 404 instead.)
|
# Unfortunately Digicert does not care and sends 404 instead.)
|
||||||
return False, None
|
return False, None
|
||||||
elif (
|
if (
|
||||||
info["status"] == 403
|
info["status"] == 403
|
||||||
and result["type"] == "urn:ietf:params:acme:error:unauthorized"
|
and result["type"] == "urn:ietf:params:acme:error:unauthorized"
|
||||||
and "deactivated" in (result.get("detail") or "")
|
and "deactivated" in (result.get("detail") or "")
|
||||||
@@ -154,15 +153,13 @@ class ACMEAccount:
|
|||||||
# might need adjustment in error detection.
|
# might need adjustment in error detection.
|
||||||
if not allow_creation:
|
if not allow_creation:
|
||||||
return False, None
|
return False, None
|
||||||
else:
|
raise ModuleFailException("Account is deactivated")
|
||||||
raise ModuleFailException("Account is deactivated")
|
raise ACMEProtocolException(
|
||||||
else:
|
module=self.client.module,
|
||||||
raise ACMEProtocolException(
|
msg="Registering ACME account failed",
|
||||||
module=self.client.module,
|
info=info,
|
||||||
msg="Registering ACME account failed",
|
content_json=result,
|
||||||
info=info,
|
)
|
||||||
content_json=result,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_account_data(self) -> dict[str, t.Any] | None:
|
def get_account_data(self) -> dict[str, t.Any] | None:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -244,7 +244,9 @@ class ACMEClient:
|
|||||||
passphrase=self.account_key_passphrase,
|
passphrase=self.account_key_passphrase,
|
||||||
)
|
)
|
||||||
except KeyParsingError as e:
|
except KeyParsingError as e:
|
||||||
raise ModuleFailException(f"Error while parsing account key: {e.msg}")
|
raise ModuleFailException(
|
||||||
|
f"Error while parsing account key: {e.msg}"
|
||||||
|
) from e
|
||||||
self.account_jwk = self.account_key_data["jwk"]
|
self.account_jwk = self.account_key_data["jwk"]
|
||||||
self.account_jws_header = {
|
self.account_jws_header = {
|
||||||
"alg": self.account_key_data["alg"],
|
"alg": self.account_key_data["alg"],
|
||||||
@@ -307,7 +309,7 @@ class ACMEClient:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ModuleFailException(
|
raise ModuleFailException(
|
||||||
f"Failed to encode payload / headers as JSON: {e}"
|
f"Failed to encode payload / headers as JSON: {e}"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
return self.backend.sign(
|
return self.backend.sign(
|
||||||
payload64=payload64, protected64=protected64, key_data=key_data
|
payload64=payload64, protected64=protected64, key_data=key_data
|
||||||
@@ -456,10 +458,10 @@ class ACMEClient:
|
|||||||
result = decoded_result
|
result = decoded_result
|
||||||
else:
|
else:
|
||||||
result = content
|
result = content
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise NetworkException(
|
raise NetworkException(
|
||||||
f"Failed to parse the ACME response: {url} {content}"
|
f"Failed to parse the ACME response: {url} {content}"
|
||||||
)
|
) from exc
|
||||||
else:
|
else:
|
||||||
result = content
|
result = content
|
||||||
|
|
||||||
@@ -569,10 +571,10 @@ class ACMEClient:
|
|||||||
try:
|
try:
|
||||||
result = self.module.from_json(content.decode("utf8"))
|
result = self.module.from_json(content.decode("utf8"))
|
||||||
parsed_json_result = True
|
parsed_json_result = True
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise NetworkException(
|
raise NetworkException(
|
||||||
f"Failed to parse the ACME response: {uri} {content!r}"
|
f"Failed to parse the ACME response: {uri} {content!r}"
|
||||||
)
|
) from exc
|
||||||
else:
|
else:
|
||||||
result = content
|
result = content
|
||||||
else:
|
else:
|
||||||
@@ -642,30 +644,32 @@ def create_default_argspec(
|
|||||||
Provides default argument spec for the options documented in the acme doc fragment.
|
Provides default argument spec for the options documented in the acme doc fragment.
|
||||||
"""
|
"""
|
||||||
result = ArgumentSpec(
|
result = ArgumentSpec(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
acme_directory=dict(type="str", required=True),
|
"acme_directory": {"type": "str", "required": True},
|
||||||
acme_version=dict(type="int", choices=[2], default=2),
|
"acme_version": {"type": "int", "choices": [2], "default": 2},
|
||||||
validate_certs=dict(type="bool", default=True),
|
"validate_certs": {"type": "bool", "default": True},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", default="auto", choices=["auto", "openssl", "cryptography"]
|
"type": "str",
|
||||||
),
|
"default": "auto",
|
||||||
request_timeout=dict(type="int", default=10),
|
"choices": ["auto", "openssl", "cryptography"],
|
||||||
),
|
},
|
||||||
|
"request_timeout": {"type": "int", "default": 10},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
if with_account:
|
if with_account:
|
||||||
result.update_argspec(
|
result.update_argspec(
|
||||||
account_key_src=dict(type="path", aliases=["account_key"]),
|
account_key_src={"type": "path", "aliases": ["account_key"]},
|
||||||
account_key_content=dict(type="str", no_log=True),
|
account_key_content={"type": "str", "no_log": True},
|
||||||
account_key_passphrase=dict(type="str", no_log=True),
|
account_key_passphrase={"type": "str", "no_log": True},
|
||||||
account_uri=dict(type="str"),
|
account_uri={"type": "str"},
|
||||||
)
|
)
|
||||||
if require_account_key:
|
if require_account_key:
|
||||||
result.update(required_one_of=[["account_key_src", "account_key_content"]])
|
result.update(required_one_of=[["account_key_src", "account_key_content"]])
|
||||||
result.update(mutually_exclusive=[["account_key_src", "account_key_content"]])
|
result.update(mutually_exclusive=[["account_key_src", "account_key_content"]])
|
||||||
if with_certificate:
|
if with_certificate:
|
||||||
result.update_argspec(
|
result.update_argspec(
|
||||||
csr=dict(type="path"),
|
csr={"type": "path"},
|
||||||
csr_content=dict(type="str"),
|
csr_content={"type": "str"},
|
||||||
)
|
)
|
||||||
result.update(
|
result.update(
|
||||||
required_one_of=[["csr", "csr_content"]],
|
required_one_of=[["csr", "csr_content"]],
|
||||||
|
|||||||
@@ -211,9 +211,7 @@ class CryptographyChainMatcher(ChainMatcher):
|
|||||||
|
|
||||||
class CryptographyBackend(CryptoBackend):
|
class CryptographyBackend(CryptoBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(CryptographyBackend, self).__init__(
|
super().__init__(module=module, with_timezone=CRYPTOGRAPHY_TIMEZONE)
|
||||||
module=module, with_timezone=CRYPTOGRAPHY_TIMEZONE
|
|
||||||
)
|
|
||||||
|
|
||||||
def parse_key(
|
def parse_key(
|
||||||
self,
|
self,
|
||||||
@@ -242,7 +240,7 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
password=to_bytes(passphrase) if passphrase is not None else None,
|
password=to_bytes(passphrase) if passphrase is not None else None,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise KeyParsingError(f"error while loading key: {e}")
|
raise KeyParsingError(f"error while loading key: {e}") from e
|
||||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
if isinstance(key, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||||
rsa_pk = key.public_key().public_numbers()
|
rsa_pk = key.public_key().public_numbers()
|
||||||
return {
|
return {
|
||||||
@@ -256,7 +254,7 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
},
|
},
|
||||||
"hash": "sha256",
|
"hash": "sha256",
|
||||||
}
|
}
|
||||||
elif isinstance(
|
if isinstance(
|
||||||
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey
|
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey
|
||||||
):
|
):
|
||||||
ec_pk = key.public_key().public_numbers()
|
ec_pk = key.public_key().public_numbers()
|
||||||
@@ -296,8 +294,7 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
"hash": hashalg,
|
"hash": hashalg,
|
||||||
"point_size": point_size,
|
"point_size": point_size,
|
||||||
}
|
}
|
||||||
else:
|
raise KeyParsingError(f'unknown key type "{type(key)}"')
|
||||||
raise KeyParsingError(f'unknown key type "{type(key)}"')
|
|
||||||
|
|
||||||
def sign(
|
def sign(
|
||||||
self, *, payload64: str, protected64: str, key_data: dict[str, t.Any]
|
self, *, payload64: str, protected64: str, key_data: dict[str, t.Any]
|
||||||
@@ -332,6 +329,8 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
rr = convert_int_to_hex(r, digits=2 * key_data["point_size"])
|
rr = convert_int_to_hex(r, digits=2 * key_data["point_size"])
|
||||||
ss = convert_int_to_hex(s, digits=2 * key_data["point_size"])
|
ss = convert_int_to_hex(s, digits=2 * key_data["point_size"])
|
||||||
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
|
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
|
||||||
|
else:
|
||||||
|
raise AssertionError("Can never be reached") # pragma: no cover
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"protected": protected64,
|
"protected": protected64,
|
||||||
@@ -472,8 +471,10 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
|
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if cert_filename is None:
|
if cert_filename is None:
|
||||||
raise BackendException(f"Cannot parse certificate: {e}")
|
raise BackendException(f"Cannot parse certificate: {e}") from e
|
||||||
raise BackendException(f"Cannot parse certificate {cert_filename}: {e}")
|
raise BackendException(
|
||||||
|
f"Cannot parse certificate {cert_filename}: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
if now is None:
|
if now is None:
|
||||||
now = self.get_now()
|
now = self.get_now()
|
||||||
@@ -508,8 +509,10 @@ class CryptographyBackend(CryptoBackend):
|
|||||||
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
|
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if cert_filename is None:
|
if cert_filename is None:
|
||||||
raise BackendException(f"Cannot parse certificate: {e}")
|
raise BackendException(f"Cannot parse certificate: {e}") from e
|
||||||
raise BackendException(f"Cannot parse certificate {cert_filename}: {e}")
|
raise BackendException(
|
||||||
|
f"Cannot parse certificate {cert_filename}: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
ski = None
|
ski = None
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -45,7 +45,12 @@ if t.TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
_OPENSSL_ENVIRONMENT_UPDATE = dict(LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C")
|
_OPENSSL_ENVIRONMENT_UPDATE = {
|
||||||
|
"LANG": "C",
|
||||||
|
"LC_ALL": "C",
|
||||||
|
"LC_MESSAGES": "C",
|
||||||
|
"LC_CTYPE": "C",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def _extract_date(
|
def _extract_date(
|
||||||
@@ -66,7 +71,7 @@ def _extract_date(
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise BackendException(
|
raise BackendException(
|
||||||
f"Failed to parse '{name}' date{cert_filename_suffix}: {exc}"
|
f"Failed to parse '{name}' date{cert_filename_suffix}: {exc}"
|
||||||
)
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def _decode_octets(octets_text: str) -> bytes:
|
def _decode_octets(octets_text: str) -> bytes:
|
||||||
@@ -118,7 +123,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, *, module: AnsibleModule, openssl_binary: str | None = None
|
self, *, module: AnsibleModule, openssl_binary: str | None = None
|
||||||
) -> None:
|
) -> None:
|
||||||
super(OpenSSLCLIBackend, self).__init__(module=module, with_timezone=True)
|
super().__init__(module=module, with_timezone=True)
|
||||||
if openssl_binary is None:
|
if openssl_binary is None:
|
||||||
openssl_binary = module.get_bin_path("openssl", True)
|
openssl_binary = module.get_bin_path("openssl", True)
|
||||||
self.openssl_binary = openssl_binary
|
self.openssl_binary = openssl_binary
|
||||||
@@ -156,11 +161,11 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
raise KeyParsingError(
|
raise KeyParsingError(
|
||||||
f"failed to create temporary content file: {err}",
|
f"failed to create temporary content file: {err}",
|
||||||
exception=traceback.format_exc(),
|
exception=traceback.format_exc(),
|
||||||
)
|
) from err
|
||||||
f.close()
|
f.close()
|
||||||
# Parse key
|
# Parse key
|
||||||
account_key_type = None
|
account_key_type = None
|
||||||
with open(key_file, "rt") as fi:
|
with open(key_file, "r", encoding="utf-8") as fi:
|
||||||
for line in fi:
|
for line in fi:
|
||||||
m = re.match(
|
m = re.match(
|
||||||
r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line
|
r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line
|
||||||
@@ -228,7 +233,7 @@ class OpenSSLCLIBackend(CryptoBackend):
|
|||||||
},
|
},
|
||||||
"hash": "sha256",
|
"hash": "sha256",
|
||||||
}
|
}
|
||||||
elif account_key_type == "ec":
|
if account_key_type == "ec":
|
||||||
pub_data = re.search(
|
pub_data = re.search(
|
||||||
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
|
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
|
||||||
out_text,
|
out_text,
|
||||||
|
|||||||
@@ -77,14 +77,14 @@ def _parse_acme_timestamp(
|
|||||||
"""
|
"""
|
||||||
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||||
timestamp_str = _reduce_fractional_digits(timestamp_str)
|
timestamp_str = _reduce_fractional_digits(timestamp_str)
|
||||||
for format in (
|
for time_format in (
|
||||||
"%Y-%m-%dT%H:%M:%SZ",
|
"%Y-%m-%dT%H:%M:%SZ",
|
||||||
"%Y-%m-%dT%H:%M:%S.%fZ",
|
"%Y-%m-%dT%H:%M:%S.%fZ",
|
||||||
"%Y-%m-%dT%H:%M:%S%z",
|
"%Y-%m-%dT%H:%M:%S%z",
|
||||||
"%Y-%m-%dT%H:%M:%S.%f%z",
|
"%Y-%m-%dT%H:%M:%S.%f%z",
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
result = datetime.datetime.strptime(timestamp_str, format)
|
result = datetime.datetime.strptime(timestamp_str, time_format)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
@@ -117,7 +117,7 @@ class CryptoBackend(metaclass=abc.ABCMeta):
|
|||||||
raise BackendException(f"Invalid value for {name}: {value!r}")
|
raise BackendException(f"Invalid value for {name}: {value!r}")
|
||||||
return result
|
return result
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise BackendException(str(exc))
|
raise BackendException(str(exc)) from exc
|
||||||
|
|
||||||
def interpolate_timestamp(
|
def interpolate_timestamp(
|
||||||
self,
|
self,
|
||||||
|
|||||||
@@ -166,11 +166,11 @@ class ACMECertificateClient:
|
|||||||
continue
|
continue
|
||||||
challenge_data = authz.get_challenge_data(client=self.client)
|
challenge_data = authz.get_challenge_data(client=self.client)
|
||||||
data.append(
|
data.append(
|
||||||
dict(
|
{
|
||||||
identifier=authz.identifier,
|
"identifier": authz.identifier,
|
||||||
identifier_type=authz.identifier_type,
|
"identifier_type": authz.identifier_type,
|
||||||
challenges=challenge_data,
|
"challenges": challenge_data,
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
dns_challenge = challenge_data.get(dns_challenge_type)
|
dns_challenge = challenge_data.get(dns_challenge_type)
|
||||||
if dns_challenge:
|
if dns_challenge:
|
||||||
@@ -321,7 +321,7 @@ class ACMECertificateClient:
|
|||||||
"""
|
"""
|
||||||
if self.csr is None and self.csr_content is None:
|
if self.csr is None and self.csr_content is None:
|
||||||
raise ModuleFailException("No CSR has been provided")
|
raise ModuleFailException("No CSR has been provided")
|
||||||
for identifier, authz in order.authorizations.items():
|
for authz in order.authorizations.values():
|
||||||
if authz.status != "valid":
|
if authz.status != "valid":
|
||||||
authz.raise_error(
|
authz.raise_error(
|
||||||
error_msg=f'Status is {authz.status!r} and not "valid"',
|
error_msg=f'Status is {authz.status!r} and not "valid"',
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class CertificateChain:
|
|||||||
|
|
||||||
process_links(
|
process_links(
|
||||||
info=info,
|
info=info,
|
||||||
callback=lambda link, relation: result._process_links(
|
callback=lambda link, relation: result._process_links( # pylint: disable=protected-access
|
||||||
client=client, link=link, relation=relation
|
client=client, link=link, relation=relation
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -295,10 +295,10 @@ class Authorization:
|
|||||||
raise ACMEProtocolException(
|
raise ACMEProtocolException(
|
||||||
module=module,
|
module=module,
|
||||||
msg=f"Failed to validate challenge for {self.combined_identifier}: {error_msg}. {'; '.join(error_details)}",
|
msg=f"Failed to validate challenge for {self.combined_identifier}: {error_msg}. {'; '.join(error_details)}",
|
||||||
extras=dict(
|
extras={
|
||||||
identifier=self.combined_identifier,
|
"identifier": self.combined_identifier,
|
||||||
authorization=self.data,
|
"authorization": self.data,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def find_challenge(self, *, challenge_type: str) -> Challenge | None:
|
def find_challenge(self, *, challenge_type: str) -> Challenge | None:
|
||||||
@@ -374,7 +374,7 @@ class Authorization:
|
|||||||
"""
|
"""
|
||||||
authz = cls(url=url)
|
authz = cls(url=url)
|
||||||
authz_deactivate = {"status": "deactivated"}
|
authz_deactivate = {"status": "deactivated"}
|
||||||
result, info = client.send_signed_request(
|
result, _info = client.send_signed_request(
|
||||||
url, authz_deactivate, fail_on_error=True
|
url, authz_deactivate, fail_on_error=True
|
||||||
)
|
)
|
||||||
authz._setup(client=client, data=result)
|
authz._setup(client=client, data=result)
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ def format_error_problem(
|
|||||||
subproblems = problem.get("subproblems")
|
subproblems = problem.get("subproblems")
|
||||||
if subproblems is not None:
|
if subproblems is not None:
|
||||||
msg = f"{msg} Subproblems:"
|
msg = f"{msg} Subproblems:"
|
||||||
for index, problem in enumerate(subproblems):
|
for index, subproblem in enumerate(subproblems):
|
||||||
index_str = f"{subproblem_prefix}{index}"
|
index_str = f"{subproblem_prefix}{index}"
|
||||||
problem_str = format_error_problem(
|
subproblem_str = format_error_problem(
|
||||||
problem, subproblem_prefix=f"{index_str}."
|
subproblem, subproblem_prefix=f"{index_str}."
|
||||||
)
|
)
|
||||||
msg = f"{msg}\n({index_str}) {problem_str}"
|
msg = f"{msg}\n({index_str}) {subproblem_str}"
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ class ModuleFailException(Exception):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, msg: str, **args: t.Any) -> None:
|
def __init__(self, msg: str, **args: t.Any) -> None:
|
||||||
super(ModuleFailException, self).__init__(self, msg)
|
super().__init__(self, msg)
|
||||||
self.msg = msg
|
self.msg = msg
|
||||||
self.module_fail_args = args
|
self.module_fail_args = args
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ class ACMEProtocolException(ModuleFailException):
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
extras = extras or dict()
|
extras = extras or {}
|
||||||
error_code = None
|
error_code = None
|
||||||
error_type = None
|
error_type = None
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ class ACMEProtocolException(ModuleFailException):
|
|||||||
elif content is not None:
|
elif content is not None:
|
||||||
add_msg = f" The raw result: {to_text(content)}"
|
add_msg = f" The raw result: {to_text(content)}"
|
||||||
|
|
||||||
super(ACMEProtocolException, self).__init__(f"{msg}.{add_msg}", **extras)
|
super().__init__(f"{msg}.{add_msg}", **extras)
|
||||||
self.problem: dict[str, t.Any] = {}
|
self.problem: dict[str, t.Any] = {}
|
||||||
self.subproblems: list[dict[str, t.Any]] = []
|
self.subproblems: list[dict[str, t.Any]] = []
|
||||||
self.error_code = error_code
|
self.error_code = error_code
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ def read_file(fn: str | os.PathLike) -> bytes:
|
|||||||
with open(fn, "rb") as f:
|
with open(fn, "rb") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ModuleFailException(f'Error while reading file "{fn}": {e}')
|
raise ModuleFailException(f'Error while reading file "{fn}": {e}') from e
|
||||||
|
|
||||||
|
|
||||||
# This function was adapted from an earlier version of https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/uri.py
|
# This function was adapted from an earlier version of https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/uri.py
|
||||||
@@ -55,7 +55,7 @@ def write_file(
|
|||||||
raise ModuleFailException(
|
raise ModuleFailException(
|
||||||
f"failed to create temporary content file: {err}",
|
f"failed to create temporary content file: {err}",
|
||||||
exception=traceback.format_exc(),
|
exception=traceback.format_exc(),
|
||||||
)
|
) from err
|
||||||
f.close()
|
f.close()
|
||||||
checksum_src = None
|
checksum_src = None
|
||||||
checksum_dest = None
|
checksum_dest = None
|
||||||
@@ -94,7 +94,7 @@ def write_file(
|
|||||||
raise ModuleFailException(
|
raise ModuleFailException(
|
||||||
f"failed to copy {tmpsrc} to {dest}: {err}",
|
f"failed to copy {tmpsrc} to {dest}: {err}",
|
||||||
exception=traceback.format_exc(),
|
exception=traceback.format_exc(),
|
||||||
)
|
) from err
|
||||||
os.remove(tmpsrc)
|
os.remove(tmpsrc)
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ def pem_to_der(
|
|||||||
lines = pem_content.splitlines()
|
lines = pem_content.splitlines()
|
||||||
elif pem_filename is not None:
|
elif pem_filename is not None:
|
||||||
try:
|
try:
|
||||||
with open(pem_filename, "rt") as f:
|
with open(pem_filename, "r", encoding="utf-8") as f:
|
||||||
lines = list(f)
|
lines = list(f)
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
raise ModuleFailException(
|
raise ModuleFailException(
|
||||||
f"cannot load PEM file {pem_filename}: {err}",
|
f"cannot load PEM file {pem_filename}: {err}",
|
||||||
exception=traceback.format_exc(),
|
exception=traceback.format_exc(),
|
||||||
)
|
) from err
|
||||||
else:
|
else:
|
||||||
raise ModuleFailException(
|
raise ModuleFailException(
|
||||||
"One of pem_filename and pem_content must be provided"
|
"One of pem_filename and pem_content must be provided"
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto._objects_
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
OID_LOOKUP: dict[str, str] = dict()
|
OID_LOOKUP: dict[str, str] = {}
|
||||||
NORMALIZE_NAMES: dict[str, str] = dict()
|
NORMALIZE_NAMES: dict[str, str] = {}
|
||||||
NORMALIZE_NAMES_SHORT: dict[str, str] = dict()
|
NORMALIZE_NAMES_SHORT: dict[str, str] = {}
|
||||||
|
|
||||||
for dotted, names in OID_MAP.items():
|
for dotted, names in OID_MAP.items():
|
||||||
for name in names:
|
for name in names:
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ if HAS_CRYPTOGRAPHY:
|
|||||||
"aa_compromise": x509.ReasonFlags.aa_compromise,
|
"aa_compromise": x509.ReasonFlags.aa_compromise,
|
||||||
"remove_from_crl": x509.ReasonFlags.remove_from_crl,
|
"remove_from_crl": x509.ReasonFlags.remove_from_crl,
|
||||||
}
|
}
|
||||||
REVOCATION_REASON_MAP_INVERSE = dict()
|
REVOCATION_REASON_MAP_INVERSE = {}
|
||||||
for k, v in REVOCATION_REASON_MAP.items():
|
for k, v in REVOCATION_REASON_MAP.items():
|
||||||
REVOCATION_REASON_MAP_INVERSE[v] = k
|
REVOCATION_REASON_MAP_INVERSE[v] = k
|
||||||
|
|
||||||
else:
|
else:
|
||||||
REVOCATION_REASON_MAP = dict()
|
REVOCATION_REASON_MAP = {}
|
||||||
REVOCATION_REASON_MAP_INVERSE = dict()
|
REVOCATION_REASON_MAP_INVERSE = {}
|
||||||
|
|
||||||
|
|
||||||
def cryptography_decode_revoked_certificate(
|
def cryptography_decode_revoked_certificate(
|
||||||
@@ -145,7 +145,9 @@ def cryptography_get_signature_algorithm_oid_from_crl(
|
|||||||
except AttributeError:
|
except AttributeError:
|
||||||
# Older cryptography versions do not have signature_algorithm_oid yet
|
# Older cryptography versions do not have signature_algorithm_oid yet
|
||||||
dotted = obj2txt(
|
dotted = obj2txt(
|
||||||
crl._backend._lib, crl._backend._ffi, crl._x509_crl.sig_alg.algorithm # type: ignore
|
crl._backend._lib, # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||||
|
crl._backend._ffi, # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||||
|
crl._x509_crl.sig_alg.algorithm, # type: ignore[attr-defined] # pylint: disable=protected-access
|
||||||
)
|
)
|
||||||
return x509.oid.ObjectIdentifier(dotted)
|
return x509.oid.ObjectIdentifier(dotted)
|
||||||
|
|
||||||
|
|||||||
@@ -113,17 +113,17 @@ if t.TYPE_CHECKING:
|
|||||||
PKCS12KeyAndCertificates,
|
PKCS12KeyAndCertificates,
|
||||||
)
|
)
|
||||||
|
|
||||||
CertificatePrivateKeyTypes = (
|
CertificatePrivateKeyTypes = t.Union[
|
||||||
CertificateIssuerPrivateKeyTypes
|
CertificateIssuerPrivateKeyTypes,
|
||||||
| cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey
|
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey,
|
||||||
| cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey
|
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey,
|
||||||
)
|
]
|
||||||
PublicKeyTypesWOEdwards = (
|
PublicKeyTypesWOEdwards = t.Union[
|
||||||
DHPublicKey | DSAPublicKey | EllipticCurvePublicKey | RSAPublicKey
|
DHPublicKey, DSAPublicKey, EllipticCurvePublicKey, RSAPublicKey
|
||||||
)
|
]
|
||||||
PrivateKeyTypesWOEdwards = (
|
PrivateKeyTypesWOEdwards = t.Union[
|
||||||
DHPrivateKey | DSAPrivateKey | EllipticCurvePrivateKey | RSAPrivateKey
|
DHPrivateKey, DSAPrivateKey, EllipticCurvePrivateKey, RSAPrivateKey
|
||||||
)
|
]
|
||||||
else:
|
else:
|
||||||
PublicKeyTypesWOEdwards = None
|
PublicKeyTypesWOEdwards = None
|
||||||
PrivateKeyTypesWOEdwards = None
|
PrivateKeyTypesWOEdwards = None
|
||||||
@@ -146,14 +146,14 @@ DOTTED_OID = re.compile(r"^\d+(?:\.\d+)+$")
|
|||||||
def cryptography_get_extensions_from_cert(
|
def cryptography_get_extensions_from_cert(
|
||||||
cert: x509.Certificate,
|
cert: x509.Certificate,
|
||||||
) -> dict[str, dict[str, bool | str]]:
|
) -> dict[str, dict[str, bool | str]]:
|
||||||
result = dict()
|
result = {}
|
||||||
|
|
||||||
if _CRYPTOGRAPHY_36_0_OR_NEWER:
|
if _CRYPTOGRAPHY_36_0_OR_NEWER:
|
||||||
for ext in cert.extensions:
|
for ext in cert.extensions:
|
||||||
result[ext.oid.dotted_string] = dict(
|
result[ext.oid.dotted_string] = {
|
||||||
critical=ext.critical,
|
"critical": ext.critical,
|
||||||
value=base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
"value": base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
||||||
)
|
}
|
||||||
else:
|
else:
|
||||||
# Since cryptography will not give us the DER value for an extension
|
# Since cryptography will not give us the DER value for an extension
|
||||||
# (that is only stored for unrecognized extensions), we have to re-do
|
# (that is only stored for unrecognized extensions), we have to re-do
|
||||||
@@ -162,6 +162,9 @@ def cryptography_get_extensions_from_cert(
|
|||||||
|
|
||||||
backend = default_backend()
|
backend = default_backend()
|
||||||
|
|
||||||
|
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
x509_obj = cert._x509 # type: ignore
|
x509_obj = cert._x509 # type: ignore
|
||||||
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
# With cryptography 35.0.0, we can no longer use obj2txt. Unfortunately it still does
|
||||||
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
# not allow to get the raw value of an extension, so we have to use this ugly hack:
|
||||||
@@ -175,10 +178,10 @@ def cryptography_get_extensions_from_cert(
|
|||||||
data = backend._lib.X509_EXTENSION_get_data(ext)
|
data = backend._lib.X509_EXTENSION_get_data(ext)
|
||||||
backend.openssl_assert(data != backend._ffi.NULL)
|
backend.openssl_assert(data != backend._ffi.NULL)
|
||||||
der = backend._ffi.buffer(data.data, data.length)[:]
|
der = backend._ffi.buffer(data.data, data.length)[:]
|
||||||
entry = dict(
|
entry = {
|
||||||
critical=(crit == 1),
|
"critical": (crit == 1),
|
||||||
value=base64.b64encode(der).decode("ascii"),
|
"value": base64.b64encode(der).decode("ascii"),
|
||||||
)
|
}
|
||||||
try:
|
try:
|
||||||
oid = obj2txt(
|
oid = obj2txt(
|
||||||
backend._lib,
|
backend._lib,
|
||||||
@@ -195,14 +198,14 @@ def cryptography_get_extensions_from_cert(
|
|||||||
def cryptography_get_extensions_from_csr(
|
def cryptography_get_extensions_from_csr(
|
||||||
csr: x509.CertificateSigningRequest,
|
csr: x509.CertificateSigningRequest,
|
||||||
) -> dict[str, dict[str, bool | str]]:
|
) -> dict[str, dict[str, bool | str]]:
|
||||||
result = dict()
|
result = {}
|
||||||
|
|
||||||
if _CRYPTOGRAPHY_36_0_OR_NEWER:
|
if _CRYPTOGRAPHY_36_0_OR_NEWER:
|
||||||
for ext in csr.extensions:
|
for ext in csr.extensions:
|
||||||
result[ext.oid.dotted_string] = dict(
|
result[ext.oid.dotted_string] = {
|
||||||
critical=ext.critical,
|
"critical": ext.critical,
|
||||||
value=base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
"value": base64.b64encode(ext.value.public_bytes()).decode("ascii"),
|
||||||
)
|
}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Since cryptography will not give us the DER value for an extension
|
# Since cryptography will not give us the DER value for an extension
|
||||||
@@ -212,6 +215,9 @@ def cryptography_get_extensions_from_csr(
|
|||||||
|
|
||||||
backend = default_backend()
|
backend = default_backend()
|
||||||
|
|
||||||
|
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req) # type: ignore
|
extensions = backend._lib.X509_REQ_get_extensions(csr._x509_req) # type: ignore
|
||||||
extensions = backend._ffi.gc(
|
extensions = backend._ffi.gc(
|
||||||
extensions,
|
extensions,
|
||||||
@@ -235,10 +241,10 @@ def cryptography_get_extensions_from_csr(
|
|||||||
data = backend._lib.X509_EXTENSION_get_data(ext)
|
data = backend._lib.X509_EXTENSION_get_data(ext)
|
||||||
backend.openssl_assert(data != backend._ffi.NULL)
|
backend.openssl_assert(data != backend._ffi.NULL)
|
||||||
der: bytes = backend._ffi.buffer(data.data, data.length)[:] # type: ignore
|
der: bytes = backend._ffi.buffer(data.data, data.length)[:] # type: ignore
|
||||||
entry = dict(
|
entry = {
|
||||||
critical=(crit == 1),
|
"critical": (crit == 1),
|
||||||
value=base64.b64encode(der).decode("ascii"),
|
"value": base64.b64encode(der).decode("ascii"),
|
||||||
)
|
}
|
||||||
try:
|
try:
|
||||||
oid = obj2txt(
|
oid = obj2txt(
|
||||||
backend._lib,
|
backend._lib,
|
||||||
@@ -269,13 +275,15 @@ def cryptography_oid_to_name(
|
|||||||
if names:
|
if names:
|
||||||
name = names[0]
|
name = names[0]
|
||||||
else:
|
else:
|
||||||
name = oid._name
|
try:
|
||||||
if name == "Unknown OID":
|
name = oid._name # pylint: disable=protected-access
|
||||||
|
if name == "Unknown OID":
|
||||||
|
name = dotted_string
|
||||||
|
except AttributeError:
|
||||||
name = dotted_string
|
name = dotted_string
|
||||||
if short:
|
if short:
|
||||||
return NORMALIZE_NAMES_SHORT.get(name, name)
|
return NORMALIZE_NAMES_SHORT.get(name, name)
|
||||||
else:
|
return NORMALIZE_NAMES.get(name, name)
|
||||||
return NORMALIZE_NAMES.get(name, name)
|
|
||||||
|
|
||||||
|
|
||||||
def _get_hex(bytesstr: bytes) -> str:
|
def _get_hex(bytesstr: bytes) -> str:
|
||||||
@@ -393,7 +401,7 @@ def _parse_dn(name: bytes) -> list[x509.NameAttribute]:
|
|||||||
except OpenSSLObjectError as e:
|
except OpenSSLObjectError as e:
|
||||||
raise OpenSSLObjectError(
|
raise OpenSSLObjectError(
|
||||||
f"Error while parsing distinguished name {to_text(original_name)!r}: {e}"
|
f"Error while parsing distinguished name {to_text(original_name)!r}: {e}"
|
||||||
)
|
) from e
|
||||||
result.append(attribute)
|
result.append(attribute)
|
||||||
if name:
|
if name:
|
||||||
if name[0:1] != sep or len(name) < 2:
|
if name[0:1] != sep or len(name) < 2:
|
||||||
@@ -414,7 +422,7 @@ def cryptography_parse_relative_distinguished_name(
|
|||||||
except OpenSSLObjectError as e:
|
except OpenSSLObjectError as e:
|
||||||
raise OpenSSLObjectError(
|
raise OpenSSLObjectError(
|
||||||
f"Error while parsing relative distinguished name {to_text(part)!r}: {e}"
|
f"Error while parsing relative distinguished name {to_text(part)!r}: {e}"
|
||||||
)
|
) from e
|
||||||
return cryptography.x509.RelativeDistinguishedName(names)
|
return cryptography.x509.RelativeDistinguishedName(names)
|
||||||
|
|
||||||
|
|
||||||
@@ -468,7 +476,7 @@ def _adjust_idn(
|
|||||||
raise OpenSSLObjectError(
|
raise OpenSSLObjectError(
|
||||||
f'Error while transforming part "{part}" of {what} DNS name "{value}" to {dest}.'
|
f'Error while transforming part "{part}" of {what} DNS name "{value}" to {dest}.'
|
||||||
f' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'
|
f' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'
|
||||||
)
|
) from exc2003
|
||||||
return ".".join(parts)
|
return ".".join(parts)
|
||||||
|
|
||||||
|
|
||||||
@@ -561,7 +569,7 @@ def cryptography_get_name(
|
|||||||
x509.Name(reversed(_parse_dn(to_bytes(name[8:]))))
|
x509.Name(reversed(_parse_dn(to_bytes(name[8:]))))
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise OpenSSLObjectError(f'Cannot parse {what} "{name}": {e}')
|
raise OpenSSLObjectError(f'Cannot parse {what} "{name}": {e}') from e
|
||||||
if ":" not in name:
|
if ":" not in name:
|
||||||
raise OpenSSLObjectError(
|
raise OpenSSLObjectError(
|
||||||
f'Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'
|
f'Cannot parse {what} "{name}" (forgot "DNS:" prefix?)'
|
||||||
@@ -656,17 +664,17 @@ def cryptography_parse_key_usage_params(usages: t.Iterable[str]) -> dict[str, bo
|
|||||||
Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage().
|
Given a list of key usage identifier strings, returns the parameters for cryptography's x509.KeyUsage().
|
||||||
Raises an OpenSSLObjectError if an identifier is unknown.
|
Raises an OpenSSLObjectError if an identifier is unknown.
|
||||||
"""
|
"""
|
||||||
params = dict(
|
params = {
|
||||||
digital_signature=False,
|
"digital_signature": False,
|
||||||
content_commitment=False,
|
"content_commitment": False,
|
||||||
key_encipherment=False,
|
"key_encipherment": False,
|
||||||
data_encipherment=False,
|
"data_encipherment": False,
|
||||||
key_agreement=False,
|
"key_agreement": False,
|
||||||
key_cert_sign=False,
|
"key_cert_sign": False,
|
||||||
crl_sign=False,
|
"crl_sign": False,
|
||||||
encipher_only=False,
|
"encipher_only": False,
|
||||||
decipher_only=False,
|
"decipher_only": False,
|
||||||
)
|
}
|
||||||
for usage in usages:
|
for usage in usages:
|
||||||
params[_cryptography_get_keyusage(usage)] = True
|
params[_cryptography_get_keyusage(usage)] = True
|
||||||
return params
|
return params
|
||||||
@@ -699,7 +707,7 @@ def cryptography_get_basic_constraints(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise OpenSSLObjectError(
|
raise OpenSSLObjectError(
|
||||||
f'Cannot parse path length constraint "{v}" ({e})'
|
f'Cannot parse path length constraint "{v}" ({e})'
|
||||||
)
|
) from e
|
||||||
else:
|
else:
|
||||||
raise OpenSSLObjectError(f'Unknown basic constraint "{constraint}"')
|
raise OpenSSLObjectError(f'Unknown basic constraint "{constraint}"')
|
||||||
return ca, path_length
|
return ca, path_length
|
||||||
@@ -901,6 +909,9 @@ def _parse_pkcs12_35_0_0(
|
|||||||
|
|
||||||
backend = default_backend()
|
backend = default_backend()
|
||||||
|
|
||||||
|
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
# This code basically does what load_key_and_certificates() does, but without error-checking.
|
# This code basically does what load_key_and_certificates() does, but without error-checking.
|
||||||
# Since load_key_and_certificates succeeded, it should not fail.
|
# Since load_key_and_certificates succeeded, it should not fail.
|
||||||
pkcs12 = backend._ffi.gc(
|
pkcs12 = backend._ffi.gc(
|
||||||
@@ -944,6 +955,9 @@ def _parse_pkcs12_legacy(
|
|||||||
pkcs12_bytes, passphrase
|
pkcs12_bytes, passphrase
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# We access a *lot* of internal APIs here, so let's disable that message...
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
|
||||||
friendly_name = None
|
friendly_name = None
|
||||||
if certificate:
|
if certificate:
|
||||||
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
# See https://github.com/pyca/cryptography/issues/5760#issuecomment-842687238
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ class CertificateBackend(metaclass=abc.ABCMeta):
|
|||||||
result["can_parse_certificate"] = True
|
result["can_parse_certificate"] = True
|
||||||
return result
|
return result
|
||||||
except Exception:
|
except Exception:
|
||||||
return dict(can_parse_certificate=False)
|
return {"can_parse_certificate": False}
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def generate_certificate(self) -> None:
|
def generate_certificate(self) -> None:
|
||||||
@@ -143,7 +143,7 @@ class CertificateBackend(metaclass=abc.ABCMeta):
|
|||||||
passphrase=self.privatekey_passphrase,
|
passphrase=self.privatekey_passphrase,
|
||||||
)
|
)
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
raise CertificateError(exc)
|
raise CertificateError(exc) from exc
|
||||||
|
|
||||||
def _ensure_csr_loaded(self) -> None:
|
def _ensure_csr_loaded(self) -> None:
|
||||||
"""Load the CSR into self.csr."""
|
"""Load the CSR into self.csr."""
|
||||||
@@ -380,25 +380,28 @@ def select_backend(
|
|||||||
|
|
||||||
def get_certificate_argument_spec() -> ArgumentSpec:
|
def get_certificate_argument_spec() -> ArgumentSpec:
|
||||||
return ArgumentSpec(
|
return ArgumentSpec(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
provider=dict(
|
"provider": {
|
||||||
type="str", choices=[]
|
"type": "str",
|
||||||
), # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py
|
"choices": [],
|
||||||
force=dict(
|
}, # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py
|
||||||
type="bool",
|
"force": {
|
||||||
default=False,
|
"type": "bool",
|
||||||
),
|
"default": False,
|
||||||
csr_path=dict(type="path"),
|
},
|
||||||
csr_content=dict(type="str"),
|
"csr_path": {"type": "path"},
|
||||||
ignore_timestamps=dict(type="bool", default=True),
|
"csr_content": {"type": "str"},
|
||||||
select_crypto_backend=dict(
|
"ignore_timestamps": {"type": "bool", "default": True},
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
"select_crypto_backend": {
|
||||||
),
|
"type": "str",
|
||||||
|
"default": "auto",
|
||||||
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
# General properties of a certificate
|
# General properties of a certificate
|
||||||
privatekey_path=dict(type="path"),
|
"privatekey_path": {"type": "path"},
|
||||||
privatekey_content=dict(type="str", no_log=True),
|
"privatekey_content": {"type": "str", "no_log": True},
|
||||||
privatekey_passphrase=dict(type="str", no_log=True),
|
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
),
|
},
|
||||||
mutually_exclusive=[
|
mutually_exclusive=[
|
||||||
["csr_path", "csr_content"],
|
["csr_path", "csr_content"],
|
||||||
["privatekey_path", "privatekey_content"],
|
["privatekey_path", "privatekey_content"],
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ if t.TYPE_CHECKING:
|
|||||||
|
|
||||||
class AcmeCertificateBackend(CertificateBackend):
|
class AcmeCertificateBackend(CertificateBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(AcmeCertificateBackend, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
self.accountkey_path: str = module.params["acme_accountkey_path"]
|
self.accountkey_path: str = module.params["acme_accountkey_path"]
|
||||||
self.challenge_path: str = module.params["acme_challenge_path"]
|
self.challenge_path: str = module.params["acme_challenge_path"]
|
||||||
self.use_chain: bool = module.params["acme_chain"]
|
self.use_chain: bool = module.params["acme_chain"]
|
||||||
@@ -94,7 +94,7 @@ class AcmeCertificateBackend(CertificateBackend):
|
|||||||
self.module.run_command(command, check_rc=True)[1]
|
self.module.run_command(command, check_rc=True)[1]
|
||||||
)
|
)
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
raise CertificateError(exc)
|
raise CertificateError(exc) from exc
|
||||||
|
|
||||||
def get_certificate_data(self) -> bytes:
|
def get_certificate_data(self) -> bytes:
|
||||||
"""Return bytes for self.cert."""
|
"""Return bytes for self.cert."""
|
||||||
@@ -103,9 +103,7 @@ class AcmeCertificateBackend(CertificateBackend):
|
|||||||
return self.cert_bytes
|
return self.cert_bytes
|
||||||
|
|
||||||
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
||||||
result = super(AcmeCertificateBackend, self).dump(
|
result = super().dump(include_certificate=include_certificate)
|
||||||
include_certificate=include_certificate
|
|
||||||
)
|
|
||||||
result["accountkey"] = self.accountkey_path
|
result["accountkey"] = self.accountkey_path
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -128,14 +126,15 @@ class AcmeCertificateProvider(CertificateProvider):
|
|||||||
def add_acme_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
def add_acme_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||||
argument_spec.argument_spec["provider"]["choices"].append("acme")
|
argument_spec.argument_spec["provider"]["choices"].append("acme")
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
acme_accountkey_path=dict(type="path"),
|
"acme_accountkey_path": {"type": "path"},
|
||||||
acme_challenge_path=dict(type="path"),
|
"acme_challenge_path": {"type": "path"},
|
||||||
acme_chain=dict(type="bool", default=False),
|
"acme_chain": {"type": "bool", "default": False},
|
||||||
acme_directory=dict(
|
"acme_directory": {
|
||||||
type="str", default="https://acme-v02.api.letsencrypt.org/directory"
|
"type": "str",
|
||||||
),
|
"default": "https://acme-v02.api.letsencrypt.org/directory",
|
||||||
)
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -51,13 +51,14 @@ except ImportError:
|
|||||||
|
|
||||||
class EntrustCertificateBackend(CertificateBackend):
|
class EntrustCertificateBackend(CertificateBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(EntrustCertificateBackend, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
self.trackingId = None
|
self.trackingId = None
|
||||||
self.notAfter = get_relative_time_option(
|
self.notAfter = get_relative_time_option(
|
||||||
module.params["entrust_not_after"],
|
module.params["entrust_not_after"],
|
||||||
input_name="entrust_not_after",
|
input_name="entrust_not_after",
|
||||||
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
with_timezone=CRYPTOGRAPHY_TIMEZONE,
|
||||||
)
|
)
|
||||||
|
self.cert_bytes: bytes | None = None
|
||||||
|
|
||||||
if self.csr_content is None:
|
if self.csr_content is None:
|
||||||
if self.csr_path is None:
|
if self.csr_path is None:
|
||||||
@@ -119,7 +120,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
body["csr"] = to_native(self.csr_content)
|
body["csr"] = to_native(self.csr_content)
|
||||||
else:
|
else:
|
||||||
assert self.csr_path is not None
|
assert self.csr_path is not None
|
||||||
with open(self.csr_path, "r") as csr_file:
|
with open(self.csr_path, "r", encoding="utf-8") as csr_file:
|
||||||
body["csr"] = csr_file.read()
|
body["csr"] = csr_file.read()
|
||||||
|
|
||||||
body["certType"] = self.module.params["entrust_cert_type"]
|
body["certType"] = self.module.params["entrust_cert_type"]
|
||||||
@@ -157,6 +158,8 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
|
|
||||||
def get_certificate_data(self) -> bytes:
|
def get_certificate_data(self) -> bytes:
|
||||||
"""Return bytes for self.cert."""
|
"""Return bytes for self.cert."""
|
||||||
|
if self.cert_bytes is None:
|
||||||
|
raise AssertionError("Contract violation: cert_bytes not set")
|
||||||
return self.cert_bytes
|
return self.cert_bytes
|
||||||
|
|
||||||
def needs_regeneration(
|
def needs_regeneration(
|
||||||
@@ -165,7 +168,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
not_before: datetime.datetime | None = None,
|
not_before: datetime.datetime | None = None,
|
||||||
not_after: datetime.datetime | None = None,
|
not_after: datetime.datetime | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
parent_check = super(EntrustCertificateBackend, self).needs_regeneration()
|
parent_check = super().needs_regeneration()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cert_details = self._get_cert_details()
|
cert_details = self._get_cert_details()
|
||||||
@@ -176,7 +179,7 @@ class EntrustCertificateBackend(CertificateBackend):
|
|||||||
|
|
||||||
# Always issue a new certificate if the certificate is expired, suspended or revoked
|
# Always issue a new certificate if the certificate is expired, suspended or revoked
|
||||||
status = cert_details.get("status", False)
|
status = cert_details.get("status", False)
|
||||||
if status == "EXPIRED" or status == "SUSPENDED" or status == "REVOKED":
|
if status in ("EXPIRED", "SUSPENDED", "REVOKED"):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# If the requested cert type was specified and it is for a different certificate type than the initial certificate, a new one is needed
|
# If the requested cert type was specified and it is for a different certificate type than the initial certificate, a new one is needed
|
||||||
@@ -239,11 +242,11 @@ class EntrustCertificateProvider(CertificateProvider):
|
|||||||
def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||||
argument_spec.argument_spec["provider"]["choices"].append("entrust")
|
argument_spec.argument_spec["provider"]["choices"].append("entrust")
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
entrust_cert_type=dict(
|
"entrust_cert_type": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="STANDARD_SSL",
|
"default": "STANDARD_SSL",
|
||||||
choices=[
|
"choices": [
|
||||||
"STANDARD_SSL",
|
"STANDARD_SSL",
|
||||||
"ADVANTAGE_SSL",
|
"ADVANTAGE_SSL",
|
||||||
"UC_SSL",
|
"UC_SSL",
|
||||||
@@ -255,20 +258,20 @@ def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
|||||||
"CDS_ENT_PRO",
|
"CDS_ENT_PRO",
|
||||||
"SMIME_ENT",
|
"SMIME_ENT",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
entrust_requester_email=dict(type="str"),
|
"entrust_requester_email": {"type": "str"},
|
||||||
entrust_requester_name=dict(type="str"),
|
"entrust_requester_name": {"type": "str"},
|
||||||
entrust_requester_phone=dict(type="str"),
|
"entrust_requester_phone": {"type": "str"},
|
||||||
entrust_api_user=dict(type="str"),
|
"entrust_api_user": {"type": "str"},
|
||||||
entrust_api_key=dict(type="str", no_log=True),
|
"entrust_api_key": {"type": "str", "no_log": True},
|
||||||
entrust_api_client_cert_path=dict(type="path"),
|
"entrust_api_client_cert_path": {"type": "path"},
|
||||||
entrust_api_client_cert_key_path=dict(type="path", no_log=True),
|
"entrust_api_client_cert_key_path": {"type": "path", "no_log": True},
|
||||||
entrust_api_specification_path=dict(
|
"entrust_api_specification_path": {
|
||||||
type="path",
|
"type": "path",
|
||||||
default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
"default": "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
||||||
),
|
},
|
||||||
entrust_not_after=dict(type="str", default="+365d"),
|
"entrust_not_after": {"type": "str", "default": "+365d"},
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
argument_spec.required_if.append(
|
argument_spec.required_if.append(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
|||||||
|
|
||||||
|
|
||||||
class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
|
class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
|
||||||
|
cert: x509.Certificate
|
||||||
|
|
||||||
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
|
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
|
||||||
# content must be a bytes string
|
# content must be a bytes string
|
||||||
self.module = module
|
self.module = module
|
||||||
@@ -169,11 +171,11 @@ class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
|
|||||||
result["signature_algorithm"] = self._get_signature_algorithm()
|
result["signature_algorithm"] = self._get_signature_algorithm()
|
||||||
subject = self._get_subject_ordered()
|
subject = self._get_subject_ordered()
|
||||||
issuer = self._get_issuer_ordered()
|
issuer = self._get_issuer_ordered()
|
||||||
result["subject"] = dict()
|
result["subject"] = {}
|
||||||
for k, v in subject:
|
for k, v in subject:
|
||||||
result["subject"][k] = v
|
result["subject"][k] = v
|
||||||
result["subject_ordered"] = subject
|
result["subject_ordered"] = subject
|
||||||
result["issuer"] = dict()
|
result["issuer"] = {}
|
||||||
for k, v in issuer:
|
for k, v in issuer:
|
||||||
result["issuer"][k] = v
|
result["issuer"][k] = v
|
||||||
result["issuer_ordered"] = issuer
|
result["issuer_ordered"] = issuer
|
||||||
@@ -249,9 +251,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
|||||||
"""Validate the supplied cert, using the cryptography backend"""
|
"""Validate the supplied cert, using the cryptography backend"""
|
||||||
|
|
||||||
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
|
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
|
||||||
super(CertificateInfoRetrievalCryptography, self).__init__(
|
super().__init__(module=module, content=content)
|
||||||
module=module, content=content
|
|
||||||
)
|
|
||||||
self.name_encoding = module.params.get("name_encoding", "ignore")
|
self.name_encoding = module.params.get("name_encoding", "ignore")
|
||||||
|
|
||||||
def _get_der_bytes(self) -> bytes:
|
def _get_der_bytes(self) -> bytes:
|
||||||
@@ -289,36 +289,36 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
|
|||||||
x509.KeyUsage
|
x509.KeyUsage
|
||||||
)
|
)
|
||||||
current_key_usage = current_key_ext.value
|
current_key_usage = current_key_ext.value
|
||||||
key_usage = dict(
|
key_usage = {
|
||||||
digital_signature=current_key_usage.digital_signature,
|
"digital_signature": current_key_usage.digital_signature,
|
||||||
content_commitment=current_key_usage.content_commitment,
|
"content_commitment": current_key_usage.content_commitment,
|
||||||
key_encipherment=current_key_usage.key_encipherment,
|
"key_encipherment": current_key_usage.key_encipherment,
|
||||||
data_encipherment=current_key_usage.data_encipherment,
|
"data_encipherment": current_key_usage.data_encipherment,
|
||||||
key_agreement=current_key_usage.key_agreement,
|
"key_agreement": current_key_usage.key_agreement,
|
||||||
key_cert_sign=current_key_usage.key_cert_sign,
|
"key_cert_sign": current_key_usage.key_cert_sign,
|
||||||
crl_sign=current_key_usage.crl_sign,
|
"crl_sign": current_key_usage.crl_sign,
|
||||||
encipher_only=False,
|
"encipher_only": False,
|
||||||
decipher_only=False,
|
"decipher_only": False,
|
||||||
)
|
}
|
||||||
if key_usage["key_agreement"]:
|
if key_usage["key_agreement"]:
|
||||||
key_usage.update(
|
key_usage.update(
|
||||||
dict(
|
{
|
||||||
encipher_only=current_key_usage.encipher_only,
|
"encipher_only": current_key_usage.encipher_only,
|
||||||
decipher_only=current_key_usage.decipher_only,
|
"decipher_only": current_key_usage.decipher_only,
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
key_usage_names = dict(
|
key_usage_names = {
|
||||||
digital_signature="Digital Signature",
|
"digital_signature": "Digital Signature",
|
||||||
content_commitment="Non Repudiation",
|
"content_commitment": "Non Repudiation",
|
||||||
key_encipherment="Key Encipherment",
|
"key_encipherment": "Key Encipherment",
|
||||||
data_encipherment="Data Encipherment",
|
"data_encipherment": "Data Encipherment",
|
||||||
key_agreement="Key Agreement",
|
"key_agreement": "Key Agreement",
|
||||||
key_cert_sign="Certificate Sign",
|
"key_cert_sign": "Certificate Sign",
|
||||||
crl_sign="CRL Sign",
|
"crl_sign": "CRL Sign",
|
||||||
encipher_only="Encipher Only",
|
"encipher_only": "Encipher Only",
|
||||||
decipher_only="Decipher Only",
|
"decipher_only": "Decipher Only",
|
||||||
)
|
}
|
||||||
return (
|
return (
|
||||||
sorted(
|
sorted(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ except ImportError:
|
|||||||
|
|
||||||
class OwnCACertificateBackendCryptography(CertificateBackend):
|
class OwnCACertificateBackendCryptography(CertificateBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(OwnCACertificateBackendCryptography, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
self.create_subject_key_identifier: t.Literal[
|
self.create_subject_key_identifier: t.Literal[
|
||||||
"create_if_not_provided", "always_create", "never_create"
|
"create_if_not_provided", "always_create", "never_create"
|
||||||
@@ -223,7 +223,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
not_before: datetime.datetime | None = None,
|
not_before: datetime.datetime | None = None,
|
||||||
not_after: datetime.datetime | None = None,
|
not_after: datetime.datetime | None = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(
|
if super().needs_regeneration(
|
||||||
not_before=self.notBefore, not_after=self.notAfter
|
not_before=self.notBefore, not_after=self.notAfter
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
@@ -272,9 +272,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
||||||
result = super(OwnCACertificateBackendCryptography, self).dump(
|
result = super().dump(include_certificate=include_certificate)
|
||||||
include_certificate=include_certificate
|
|
||||||
)
|
|
||||||
result.update(
|
result.update(
|
||||||
{
|
{
|
||||||
"ca_cert": self.ca_cert_path,
|
"ca_cert": self.ca_cert_path,
|
||||||
@@ -343,23 +341,23 @@ class OwnCACertificateProvider(CertificateProvider):
|
|||||||
def add_ownca_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
def add_ownca_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||||
argument_spec.argument_spec["provider"]["choices"].append("ownca")
|
argument_spec.argument_spec["provider"]["choices"].append("ownca")
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
ownca_path=dict(type="path"),
|
"ownca_path": {"type": "path"},
|
||||||
ownca_content=dict(type="str"),
|
"ownca_content": {"type": "str"},
|
||||||
ownca_privatekey_path=dict(type="path"),
|
"ownca_privatekey_path": {"type": "path"},
|
||||||
ownca_privatekey_content=dict(type="str", no_log=True),
|
"ownca_privatekey_content": {"type": "str", "no_log": True},
|
||||||
ownca_privatekey_passphrase=dict(type="str", no_log=True),
|
"ownca_privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
ownca_digest=dict(type="str", default="sha256"),
|
"ownca_digest": {"type": "str", "default": "sha256"},
|
||||||
ownca_version=dict(type="int", default=3, choices=[3]), # not used
|
"ownca_version": {"type": "int", "default": 3, "choices": [3]}, # not used
|
||||||
ownca_not_before=dict(type="str", default="+0s"),
|
"ownca_not_before": {"type": "str", "default": "+0s"},
|
||||||
ownca_not_after=dict(type="str", default="+3650d"),
|
"ownca_not_after": {"type": "str", "default": "+3650d"},
|
||||||
ownca_create_subject_key_identifier=dict(
|
"ownca_create_subject_key_identifier": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="create_if_not_provided",
|
"default": "create_if_not_provided",
|
||||||
choices=["create_if_not_provided", "always_create", "never_create"],
|
"choices": ["create_if_not_provided", "always_create", "never_create"],
|
||||||
),
|
},
|
||||||
ownca_create_authority_key_identifier=dict(type="bool", default=True),
|
"ownca_create_authority_key_identifier": {"type": "bool", "default": True},
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
argument_spec.mutually_exclusive.extend(
|
argument_spec.mutually_exclusive.extend(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
privatekey: CertificateIssuerPrivateKeyTypes
|
privatekey: CertificateIssuerPrivateKeyTypes
|
||||||
|
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(SelfSignedCertificateBackendCryptography, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
self.create_subject_key_identifier: t.Literal[
|
self.create_subject_key_identifier: t.Literal[
|
||||||
"create_if_not_provided", "always_create", "never_create"
|
"create_if_not_provided", "always_create", "never_create"
|
||||||
@@ -144,7 +144,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
critical=False,
|
critical=False,
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise CertificateError(str(e))
|
raise CertificateError(str(e)) from e
|
||||||
|
|
||||||
certificate = cert_builder.sign(
|
certificate = cert_builder.sign(
|
||||||
private_key=self.privatekey,
|
private_key=self.privatekey,
|
||||||
@@ -167,7 +167,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
assert self.privatekey is not None
|
assert self.privatekey is not None
|
||||||
|
|
||||||
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(
|
if super().needs_regeneration(
|
||||||
not_before=self.notBefore, not_after=self.notAfter
|
not_before=self.notBefore, not_after=self.notAfter
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
@@ -185,9 +185,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
|
||||||
result = super(SelfSignedCertificateBackendCryptography, self).dump(
|
result = super().dump(include_certificate=include_certificate)
|
||||||
include_certificate=include_certificate
|
|
||||||
)
|
|
||||||
|
|
||||||
if self.module.check_mode:
|
if self.module.check_mode:
|
||||||
result.update(
|
result.update(
|
||||||
@@ -243,21 +241,29 @@ class SelfSignedCertificateProvider(CertificateProvider):
|
|||||||
def add_selfsigned_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
def add_selfsigned_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
|
||||||
argument_spec.argument_spec["provider"]["choices"].append("selfsigned")
|
argument_spec.argument_spec["provider"]["choices"].append("selfsigned")
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
selfsigned_version=dict(type="int", default=3, choices=[3]), # not used
|
"selfsigned_version": {
|
||||||
selfsigned_digest=dict(type="str", default="sha256"),
|
"type": "int",
|
||||||
selfsigned_not_before=dict(
|
"default": 3,
|
||||||
type="str", default="+0s", aliases=["selfsigned_notBefore"]
|
"choices": [3],
|
||||||
),
|
}, # not used
|
||||||
selfsigned_not_after=dict(
|
"selfsigned_digest": {"type": "str", "default": "sha256"},
|
||||||
type="str", default="+3650d", aliases=["selfsigned_notAfter"]
|
"selfsigned_not_before": {
|
||||||
),
|
"type": "str",
|
||||||
selfsigned_create_subject_key_identifier=dict(
|
"default": "+0s",
|
||||||
type="str",
|
"aliases": ["selfsigned_notBefore"],
|
||||||
default="create_if_not_provided",
|
},
|
||||||
choices=["create_if_not_provided", "always_create", "never_create"],
|
"selfsigned_not_after": {
|
||||||
),
|
"type": "str",
|
||||||
)
|
"default": "+3650d",
|
||||||
|
"aliases": ["selfsigned_notAfter"],
|
||||||
|
},
|
||||||
|
"selfsigned_create_subject_key_identifier": {
|
||||||
|
"type": "str",
|
||||||
|
"default": "create_if_not_provided",
|
||||||
|
"choices": ["create_if_not_provided", "always_create", "never_create"],
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -67,18 +67,18 @@ class CRLInfoRetrieval:
|
|||||||
self.name_encoding = module.params.get("name_encoding", "ignore")
|
self.name_encoding = module.params.get("name_encoding", "ignore")
|
||||||
|
|
||||||
def get_info(self) -> dict[str, t.Any]:
|
def get_info(self) -> dict[str, t.Any]:
|
||||||
self.crl_pem = identify_pem_format(self.content)
|
crl_pem = identify_pem_format(self.content)
|
||||||
try:
|
try:
|
||||||
if self.crl_pem:
|
if crl_pem:
|
||||||
self.crl = x509.load_pem_x509_crl(self.content)
|
crl = x509.load_pem_x509_crl(self.content)
|
||||||
else:
|
else:
|
||||||
self.crl = x509.load_der_x509_crl(self.content)
|
crl = x509.load_der_x509_crl(self.content)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
self.module.fail_json(msg=f"Error while decoding CRL: {e}")
|
self.module.fail_json(msg=f"Error while decoding CRL: {e}")
|
||||||
|
|
||||||
result: dict[str, t.Any] = {
|
result: dict[str, t.Any] = {
|
||||||
"changed": False,
|
"changed": False,
|
||||||
"format": "pem" if self.crl_pem else "der",
|
"format": "pem" if crl_pem else "der",
|
||||||
"last_update": None,
|
"last_update": None,
|
||||||
"next_update": None,
|
"next_update": None,
|
||||||
"digest": None,
|
"digest": None,
|
||||||
@@ -86,25 +86,24 @@ class CRLInfoRetrieval:
|
|||||||
"issuer": None,
|
"issuer": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
result["last_update"] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
|
result["last_update"] = crl.last_update.strftime(TIMESTAMP_FORMAT)
|
||||||
result["next_update"] = (
|
result["next_update"] = (
|
||||||
self.crl.next_update.strftime(TIMESTAMP_FORMAT)
|
crl.next_update.strftime(TIMESTAMP_FORMAT) if crl.next_update else None
|
||||||
if self.crl.next_update
|
|
||||||
else None
|
|
||||||
)
|
)
|
||||||
result["digest"] = cryptography_oid_to_name(
|
result["digest"] = cryptography_oid_to_name(
|
||||||
cryptography_get_signature_algorithm_oid_from_crl(self.crl)
|
cryptography_get_signature_algorithm_oid_from_crl(crl)
|
||||||
)
|
)
|
||||||
issuer = []
|
issuer = []
|
||||||
for attribute in self.crl.issuer:
|
for attribute in crl.issuer:
|
||||||
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
|
||||||
result["issuer_ordered"] = issuer
|
result["issuer_ordered"] = issuer
|
||||||
result["issuer"] = {}
|
issuer_dict = {}
|
||||||
for k, v in issuer:
|
for k, v in issuer:
|
||||||
result["issuer"][k] = v
|
issuer_dict[k] = v
|
||||||
|
result["issuer"] = issuer_dict
|
||||||
if self.list_revoked_certificates:
|
if self.list_revoked_certificates:
|
||||||
result["revoked_certificates"] = []
|
result["revoked_certificates"] = []
|
||||||
for cert in self.crl:
|
for cert in crl:
|
||||||
entry = cryptography_decode_revoked_certificate(cert)
|
entry = cryptography_decode_revoked_certificate(cert)
|
||||||
result["revoked_certificates"].append(
|
result["revoked_certificates"].append(
|
||||||
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)
|
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)
|
||||||
|
|||||||
@@ -170,7 +170,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
|||||||
)
|
)
|
||||||
self.ordered_subject = True
|
self.ordered_subject = True
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise CertificateSigningRequestError(str(exc))
|
raise CertificateSigningRequestError(str(exc)) from exc
|
||||||
|
|
||||||
self.using_common_name_for_san = False
|
self.using_common_name_for_san = False
|
||||||
if not self.subjectAltName and module.params["use_common_name_for_san"]:
|
if not self.subjectAltName and module.params["use_common_name_for_san"]:
|
||||||
@@ -189,7 +189,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CertificateSigningRequestError(
|
raise CertificateSigningRequestError(
|
||||||
f"Cannot parse subject_key_identifier: {e}"
|
f"Cannot parse subject_key_identifier: {e}"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
self.authority_key_identifier: bytes | None = None
|
self.authority_key_identifier: bytes | None = None
|
||||||
if authority_key_identifier is not None:
|
if authority_key_identifier is not None:
|
||||||
@@ -200,7 +200,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise CertificateSigningRequestError(
|
raise CertificateSigningRequestError(
|
||||||
f"Cannot parse authority_key_identifier: {e}"
|
f"Cannot parse authority_key_identifier: {e}"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
self.existing_csr: cryptography.x509.CertificateSigningRequest | None = None
|
self.existing_csr: cryptography.x509.CertificateSigningRequest | None = None
|
||||||
self.existing_csr_bytes: bytes | None = None
|
self.existing_csr_bytes: bytes | None = None
|
||||||
@@ -221,7 +221,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
|||||||
result["can_parse_csr"] = True
|
result["can_parse_csr"] = True
|
||||||
return result
|
return result
|
||||||
except Exception:
|
except Exception:
|
||||||
return dict(can_parse_csr=False)
|
return {"can_parse_csr": False}
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def generate_csr(self) -> None:
|
def generate_csr(self) -> None:
|
||||||
@@ -253,7 +253,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
|||||||
passphrase=self.privatekey_passphrase,
|
passphrase=self.privatekey_passphrase,
|
||||||
)
|
)
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
raise CertificateSigningRequestError(exc)
|
raise CertificateSigningRequestError(exc) from exc
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _check_csr(self) -> bool:
|
def _check_csr(self) -> bool:
|
||||||
@@ -294,10 +294,10 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
|
|||||||
# Store result
|
# Store result
|
||||||
result["csr"] = csr_bytes.decode("utf-8") if csr_bytes else None
|
result["csr"] = csr_bytes.decode("utf-8") if csr_bytes else None
|
||||||
|
|
||||||
result["diff"] = dict(
|
result["diff"] = {
|
||||||
before=self.diff_before,
|
"before": self.diff_before,
|
||||||
after=self.diff_after,
|
"after": self.diff_after,
|
||||||
)
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -347,16 +347,14 @@ def parse_crl_distribution_points(
|
|||||||
except (OpenSSLObjectError, ValueError) as e:
|
except (OpenSSLObjectError, ValueError) as e:
|
||||||
raise OpenSSLObjectError(
|
raise OpenSSLObjectError(
|
||||||
f"Error while parsing CRL distribution point #{index}: {e}"
|
f"Error while parsing CRL distribution point #{index}: {e}"
|
||||||
)
|
) from e
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
# Implementation with using cryptography
|
# Implementation with using cryptography
|
||||||
class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBackend):
|
class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(CertificateSigningRequestCryptographyBackend, self).__init__(
|
super().__init__(module=module)
|
||||||
module=module
|
|
||||||
)
|
|
||||||
if self.version != 1:
|
if self.version != 1:
|
||||||
module.warn(
|
module.warn(
|
||||||
"The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)"
|
"The cryptography backend only supports version 1. (The only valid value according to RFC 2986.)"
|
||||||
@@ -388,7 +386,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise CertificateSigningRequestError(e)
|
raise CertificateSigningRequestError(e) from e
|
||||||
|
|
||||||
if self.subjectAltName:
|
if self.subjectAltName:
|
||||||
csr = csr.add_extension(
|
csr = csr.add_extension(
|
||||||
@@ -451,7 +449,9 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
critical=self.name_constraints_critical,
|
critical=self.name_constraints_critical,
|
||||||
)
|
)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise OpenSSLObjectError(f"Error while parsing name constraint: {e}")
|
raise OpenSSLObjectError(
|
||||||
|
f"Error while parsing name constraint: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
if self.create_subject_key_identifier:
|
if self.create_subject_key_identifier:
|
||||||
if not is_potential_certificate_issuer_public_key(
|
if not is_potential_certificate_issuer_public_key(
|
||||||
@@ -556,8 +556,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
|
current_subject = [(sub.oid, sub.value) for sub in csr.subject]
|
||||||
if self.ordered_subject:
|
if self.ordered_subject:
|
||||||
return subject == current_subject
|
return subject == current_subject
|
||||||
else:
|
return set(subject) == set(current_subject)
|
||||||
return set(subject) == set(current_subject)
|
|
||||||
|
|
||||||
def _find_extension(
|
def _find_extension(
|
||||||
extensions: cryptography.x509.Extensions, exttype: type[_ET]
|
extensions: cryptography.x509.Extensions, exttype: type[_ET]
|
||||||
@@ -596,15 +595,14 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
)
|
)
|
||||||
if not self.keyUsage:
|
if not self.keyUsage:
|
||||||
return current_keyusage_ext is None
|
return current_keyusage_ext is None
|
||||||
elif current_keyusage_ext is None:
|
if current_keyusage_ext is None:
|
||||||
return False
|
return False
|
||||||
params = cryptography_parse_key_usage_params(self.keyUsage)
|
params = cryptography_parse_key_usage_params(self.keyUsage)
|
||||||
for param in params:
|
for param, value in params.items():
|
||||||
if getattr(current_keyusage_ext.value, "_" + param) != params[param]:
|
# TODO: check whether getattr() with '_' prepended is really needed
|
||||||
|
if getattr(current_keyusage_ext.value, "_" + param) != value:
|
||||||
return False
|
return False
|
||||||
if current_keyusage_ext.critical != self.keyUsage_critical:
|
return current_keyusage_ext.critical == self.keyUsage_critical
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _check_extenededKeyUsage(extensions: cryptography.x509.Extensions) -> bool:
|
def _check_extenededKeyUsage(extensions: cryptography.x509.Extensions) -> bool:
|
||||||
current_usages_ext = _find_extension(
|
current_usages_ext = _find_extension(
|
||||||
@@ -647,8 +645,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
bc_ext is not None
|
bc_ext is not None
|
||||||
and bc_ext.critical == self.basicConstraints_critical
|
and bc_ext.critical == self.basicConstraints_critical
|
||||||
)
|
)
|
||||||
else:
|
return bc_ext is None
|
||||||
return bc_ext is None
|
|
||||||
|
|
||||||
def _check_ocspMustStaple(extensions: cryptography.x509.Extensions) -> bool:
|
def _check_ocspMustStaple(extensions: cryptography.x509.Extensions) -> bool:
|
||||||
tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature)
|
tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature)
|
||||||
@@ -662,8 +659,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
cryptography.x509.TLSFeatureType.status_request
|
cryptography.x509.TLSFeatureType.status_request
|
||||||
in tlsfeature_ext.value
|
in tlsfeature_ext.value
|
||||||
)
|
)
|
||||||
else:
|
return tlsfeature_ext is None
|
||||||
return tlsfeature_ext is None
|
|
||||||
|
|
||||||
def _check_nameConstraints(extensions: cryptography.x509.Extensions) -> bool:
|
def _check_nameConstraints(extensions: cryptography.x509.Extensions) -> bool:
|
||||||
current_nc_ext = _find_extension(
|
current_nc_ext = _find_extension(
|
||||||
@@ -722,10 +718,8 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
self.privatekey.public_key()
|
self.privatekey.public_key()
|
||||||
).digest
|
).digest
|
||||||
return ext.value.digest == digest
|
return ext.value.digest == digest
|
||||||
else:
|
return ext.value.digest == self.subject_key_identifier
|
||||||
return ext.value.digest == self.subject_key_identifier
|
return ext is None
|
||||||
else:
|
|
||||||
return ext is None
|
|
||||||
|
|
||||||
def _check_authority_key_identifier(
|
def _check_authority_key_identifier(
|
||||||
extensions: cryptography.x509.Extensions,
|
extensions: cryptography.x509.Extensions,
|
||||||
@@ -753,8 +747,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
|
|||||||
and ext.value.authority_cert_serial_number
|
and ext.value.authority_cert_serial_number
|
||||||
== self.authority_cert_serial_number
|
== self.authority_cert_serial_number
|
||||||
)
|
)
|
||||||
else:
|
return ext is None
|
||||||
return ext is None
|
|
||||||
|
|
||||||
def _check_crl_distribution_points(
|
def _check_crl_distribution_points(
|
||||||
extensions: cryptography.x509.Extensions,
|
extensions: cryptography.x509.Extensions,
|
||||||
@@ -814,77 +807,97 @@ def select_backend(
|
|||||||
|
|
||||||
def get_csr_argument_spec() -> ArgumentSpec:
|
def get_csr_argument_spec() -> ArgumentSpec:
|
||||||
return ArgumentSpec(
|
return ArgumentSpec(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
digest=dict(type="str", default="sha256"),
|
"digest": {"type": "str", "default": "sha256"},
|
||||||
privatekey_path=dict(type="path"),
|
"privatekey_path": {"type": "path"},
|
||||||
privatekey_content=dict(type="str", no_log=True),
|
"privatekey_content": {"type": "str", "no_log": True},
|
||||||
privatekey_passphrase=dict(type="str", no_log=True),
|
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
version=dict(type="int", default=1, choices=[1]),
|
"version": {"type": "int", "default": 1, "choices": [1]},
|
||||||
subject=dict(type="dict"),
|
"subject": {"type": "dict"},
|
||||||
subject_ordered=dict(type="list", elements="dict"),
|
"subject_ordered": {"type": "list", "elements": "dict"},
|
||||||
country_name=dict(type="str", aliases=["C", "countryName"]),
|
"country_name": {"type": "str", "aliases": ["C", "countryName"]},
|
||||||
state_or_province_name=dict(
|
"state_or_province_name": {
|
||||||
type="str", aliases=["ST", "stateOrProvinceName"]
|
"type": "str",
|
||||||
),
|
"aliases": ["ST", "stateOrProvinceName"],
|
||||||
locality_name=dict(type="str", aliases=["L", "localityName"]),
|
},
|
||||||
organization_name=dict(type="str", aliases=["O", "organizationName"]),
|
"locality_name": {"type": "str", "aliases": ["L", "localityName"]},
|
||||||
organizational_unit_name=dict(
|
"organization_name": {"type": "str", "aliases": ["O", "organizationName"]},
|
||||||
type="str", aliases=["OU", "organizationalUnitName"]
|
"organizational_unit_name": {
|
||||||
),
|
"type": "str",
|
||||||
common_name=dict(type="str", aliases=["CN", "commonName"]),
|
"aliases": ["OU", "organizationalUnitName"],
|
||||||
email_address=dict(type="str", aliases=["E", "emailAddress"]),
|
},
|
||||||
subject_alt_name=dict(
|
"common_name": {"type": "str", "aliases": ["CN", "commonName"]},
|
||||||
type="list", elements="str", aliases=["subjectAltName"]
|
"email_address": {"type": "str", "aliases": ["E", "emailAddress"]},
|
||||||
),
|
"subject_alt_name": {
|
||||||
subject_alt_name_critical=dict(
|
"type": "list",
|
||||||
type="bool", default=False, aliases=["subjectAltName_critical"]
|
"elements": "str",
|
||||||
),
|
"aliases": ["subjectAltName"],
|
||||||
use_common_name_for_san=dict(
|
},
|
||||||
type="bool", default=True, aliases=["useCommonNameForSAN"]
|
"subject_alt_name_critical": {
|
||||||
),
|
"type": "bool",
|
||||||
key_usage=dict(type="list", elements="str", aliases=["keyUsage"]),
|
"default": False,
|
||||||
key_usage_critical=dict(
|
"aliases": ["subjectAltName_critical"],
|
||||||
type="bool", default=False, aliases=["keyUsage_critical"]
|
},
|
||||||
),
|
"use_common_name_for_san": {
|
||||||
extended_key_usage=dict(
|
"type": "bool",
|
||||||
type="list", elements="str", aliases=["extKeyUsage", "extendedKeyUsage"]
|
"default": True,
|
||||||
),
|
"aliases": ["useCommonNameForSAN"],
|
||||||
extended_key_usage_critical=dict(
|
},
|
||||||
type="bool",
|
"key_usage": {"type": "list", "elements": "str", "aliases": ["keyUsage"]},
|
||||||
default=False,
|
"key_usage_critical": {
|
||||||
aliases=["extKeyUsage_critical", "extendedKeyUsage_critical"],
|
"type": "bool",
|
||||||
),
|
"default": False,
|
||||||
basic_constraints=dict(
|
"aliases": ["keyUsage_critical"],
|
||||||
type="list", elements="str", aliases=["basicConstraints"]
|
},
|
||||||
),
|
"extended_key_usage": {
|
||||||
basic_constraints_critical=dict(
|
"type": "list",
|
||||||
type="bool", default=False, aliases=["basicConstraints_critical"]
|
"elements": "str",
|
||||||
),
|
"aliases": ["extKeyUsage", "extendedKeyUsage"],
|
||||||
ocsp_must_staple=dict(
|
},
|
||||||
type="bool", default=False, aliases=["ocspMustStaple"]
|
"extended_key_usage_critical": {
|
||||||
),
|
"type": "bool",
|
||||||
ocsp_must_staple_critical=dict(
|
"default": False,
|
||||||
type="bool", default=False, aliases=["ocspMustStaple_critical"]
|
"aliases": ["extKeyUsage_critical", "extendedKeyUsage_critical"],
|
||||||
),
|
},
|
||||||
name_constraints_permitted=dict(type="list", elements="str"),
|
"basic_constraints": {
|
||||||
name_constraints_excluded=dict(type="list", elements="str"),
|
"type": "list",
|
||||||
name_constraints_critical=dict(type="bool", default=False),
|
"elements": "str",
|
||||||
create_subject_key_identifier=dict(type="bool", default=False),
|
"aliases": ["basicConstraints"],
|
||||||
subject_key_identifier=dict(type="str"),
|
},
|
||||||
authority_key_identifier=dict(type="str"),
|
"basic_constraints_critical": {
|
||||||
authority_cert_issuer=dict(type="list", elements="str"),
|
"type": "bool",
|
||||||
authority_cert_serial_number=dict(type="int"),
|
"default": False,
|
||||||
crl_distribution_points=dict(
|
"aliases": ["basicConstraints_critical"],
|
||||||
type="list",
|
},
|
||||||
elements="dict",
|
"ocsp_must_staple": {
|
||||||
options=dict(
|
"type": "bool",
|
||||||
full_name=dict(type="list", elements="str"),
|
"default": False,
|
||||||
relative_name=dict(type="list", elements="str"),
|
"aliases": ["ocspMustStaple"],
|
||||||
crl_issuer=dict(type="list", elements="str"),
|
},
|
||||||
reasons=dict(
|
"ocsp_must_staple_critical": {
|
||||||
type="list",
|
"type": "bool",
|
||||||
elements="str",
|
"default": False,
|
||||||
choices=[
|
"aliases": ["ocspMustStaple_critical"],
|
||||||
|
},
|
||||||
|
"name_constraints_permitted": {"type": "list", "elements": "str"},
|
||||||
|
"name_constraints_excluded": {"type": "list", "elements": "str"},
|
||||||
|
"name_constraints_critical": {"type": "bool", "default": False},
|
||||||
|
"create_subject_key_identifier": {"type": "bool", "default": False},
|
||||||
|
"subject_key_identifier": {"type": "str"},
|
||||||
|
"authority_key_identifier": {"type": "str"},
|
||||||
|
"authority_cert_issuer": {"type": "list", "elements": "str"},
|
||||||
|
"authority_cert_serial_number": {"type": "int"},
|
||||||
|
"crl_distribution_points": {
|
||||||
|
"type": "list",
|
||||||
|
"elements": "dict",
|
||||||
|
"options": {
|
||||||
|
"full_name": {"type": "list", "elements": "str"},
|
||||||
|
"relative_name": {"type": "list", "elements": "str"},
|
||||||
|
"crl_issuer": {"type": "list", "elements": "str"},
|
||||||
|
"reasons": {
|
||||||
|
"type": "list",
|
||||||
|
"elements": "str",
|
||||||
|
"choices": [
|
||||||
"key_compromise",
|
"key_compromise",
|
||||||
"ca_compromise",
|
"ca_compromise",
|
||||||
"affiliation_changed",
|
"affiliation_changed",
|
||||||
@@ -894,15 +907,17 @@ def get_csr_argument_spec() -> ArgumentSpec:
|
|||||||
"privilege_withdrawn",
|
"privilege_withdrawn",
|
||||||
"aa_compromise",
|
"aa_compromise",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
),
|
},
|
||||||
mutually_exclusive=[("full_name", "relative_name")],
|
"mutually_exclusive": [("full_name", "relative_name")],
|
||||||
required_one_of=[("full_name", "relative_name", "crl_issuer")],
|
"required_one_of": [("full_name", "relative_name", "crl_issuer")],
|
||||||
),
|
},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
"type": "str",
|
||||||
),
|
"default": "auto",
|
||||||
),
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_together=[
|
required_together=[
|
||||||
["authority_cert_issuer", "authority_cert_serial_number"],
|
["authority_cert_issuer", "authority_cert_serial_number"],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
|
|||||||
|
|
||||||
|
|
||||||
class CSRInfoRetrieval(metaclass=abc.ABCMeta):
|
class CSRInfoRetrieval(metaclass=abc.ABCMeta):
|
||||||
|
csr: x509.CertificateSigningRequest
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
|
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -129,7 +131,7 @@ class CSRInfoRetrieval(metaclass=abc.ABCMeta):
|
|||||||
)
|
)
|
||||||
|
|
||||||
subject = self._get_subject_ordered()
|
subject = self._get_subject_ordered()
|
||||||
result["subject"] = dict()
|
result["subject"] = {}
|
||||||
for k, v in subject:
|
for k, v in subject:
|
||||||
result["subject"][k] = v
|
result["subject"][k] = v
|
||||||
result["subject_ordered"] = subject
|
result["subject_ordered"] = subject
|
||||||
@@ -197,7 +199,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
|
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
|
||||||
) -> None:
|
) -> None:
|
||||||
super(CSRInfoRetrievalCryptography, self).__init__(
|
super().__init__(
|
||||||
module=module, content=content, validate_signature=validate_signature
|
module=module, content=content, validate_signature=validate_signature
|
||||||
)
|
)
|
||||||
self.name_encoding: t.Literal["ignore", "idna", "unicode"] = module.params.get(
|
self.name_encoding: t.Literal["ignore", "idna", "unicode"] = module.params.get(
|
||||||
@@ -216,36 +218,36 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
|
|||||||
try:
|
try:
|
||||||
current_key_ext = self.csr.extensions.get_extension_for_class(x509.KeyUsage)
|
current_key_ext = self.csr.extensions.get_extension_for_class(x509.KeyUsage)
|
||||||
current_key_usage = current_key_ext.value
|
current_key_usage = current_key_ext.value
|
||||||
key_usage = dict(
|
key_usage = {
|
||||||
digital_signature=current_key_usage.digital_signature,
|
"digital_signature": current_key_usage.digital_signature,
|
||||||
content_commitment=current_key_usage.content_commitment,
|
"content_commitment": current_key_usage.content_commitment,
|
||||||
key_encipherment=current_key_usage.key_encipherment,
|
"key_encipherment": current_key_usage.key_encipherment,
|
||||||
data_encipherment=current_key_usage.data_encipherment,
|
"data_encipherment": current_key_usage.data_encipherment,
|
||||||
key_agreement=current_key_usage.key_agreement,
|
"key_agreement": current_key_usage.key_agreement,
|
||||||
key_cert_sign=current_key_usage.key_cert_sign,
|
"key_cert_sign": current_key_usage.key_cert_sign,
|
||||||
crl_sign=current_key_usage.crl_sign,
|
"crl_sign": current_key_usage.crl_sign,
|
||||||
encipher_only=False,
|
"encipher_only": False,
|
||||||
decipher_only=False,
|
"decipher_only": False,
|
||||||
)
|
}
|
||||||
if key_usage["key_agreement"]:
|
if key_usage["key_agreement"]:
|
||||||
key_usage.update(
|
key_usage.update(
|
||||||
dict(
|
{
|
||||||
encipher_only=current_key_usage.encipher_only,
|
"encipher_only": current_key_usage.encipher_only,
|
||||||
decipher_only=current_key_usage.decipher_only,
|
"decipher_only": current_key_usage.decipher_only,
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
key_usage_names = dict(
|
key_usage_names = {
|
||||||
digital_signature="Digital Signature",
|
"digital_signature": "Digital Signature",
|
||||||
content_commitment="Non Repudiation",
|
"content_commitment": "Non Repudiation",
|
||||||
key_encipherment="Key Encipherment",
|
"key_encipherment": "Key Encipherment",
|
||||||
data_encipherment="Data Encipherment",
|
"data_encipherment": "Data Encipherment",
|
||||||
key_agreement="Key Agreement",
|
"key_agreement": "Key Agreement",
|
||||||
key_cert_sign="Certificate Sign",
|
"key_cert_sign": "Certificate Sign",
|
||||||
crl_sign="CRL Sign",
|
"crl_sign": "CRL Sign",
|
||||||
encipher_only="Encipher Only",
|
"encipher_only": "Encipher Only",
|
||||||
decipher_only="Decipher Only",
|
"decipher_only": "Decipher Only",
|
||||||
)
|
}
|
||||||
return (
|
return (
|
||||||
sorted(
|
sorted(
|
||||||
[
|
[
|
||||||
|
|||||||
@@ -265,10 +265,10 @@ class PrivateKeyBackend(metaclass=abc.ABCMeta):
|
|||||||
else:
|
else:
|
||||||
result["privatekey"] = None
|
result["privatekey"] = None
|
||||||
|
|
||||||
result["diff"] = dict(
|
result["diff"] = {
|
||||||
before=self.diff_before,
|
"before": self.diff_before,
|
||||||
after=self.diff_after,
|
"after": self.diff_after,
|
||||||
)
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -323,7 +323,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
self.curves[name] = _Curve(name=name, ectype=ectype, deprecated=deprecated)
|
self.curves[name] = _Curve(name=name, ectype=ectype, deprecated=deprecated)
|
||||||
|
|
||||||
def __init__(self, module: GeneralAnsibleModule) -> None:
|
def __init__(self, module: GeneralAnsibleModule) -> None:
|
||||||
super(PrivateKeyCryptographyBackend, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
self.curves: dict[str, _Curve] = {}
|
self.curves: dict[str, _Curve] = {}
|
||||||
self._add_curve("secp224r1", "SECP224R1")
|
self._add_curve("secp224r1", "SECP224R1")
|
||||||
@@ -351,8 +351,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
return self.format # type: ignore
|
return self.format # type: ignore
|
||||||
if self.type in ("X25519", "X448", "Ed25519", "Ed448"):
|
if self.type in ("X25519", "X448", "Ed25519", "Ed448"):
|
||||||
return "pkcs8"
|
return "pkcs8"
|
||||||
else:
|
return "pkcs1"
|
||||||
return "pkcs1"
|
|
||||||
|
|
||||||
def generate_private_key(self) -> None:
|
def generate_private_key(self) -> None:
|
||||||
"""(Re-)Generate private key."""
|
"""(Re-)Generate private key."""
|
||||||
@@ -427,6 +426,9 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
export_encoding = (
|
export_encoding = (
|
||||||
cryptography.hazmat.primitives.serialization.Encoding.Raw
|
cryptography.hazmat.primitives.serialization.Encoding.Raw
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# pylint does not notice that all possible values for export_format_txt have been covered.
|
||||||
|
raise AssertionError("Can never be reached") # pragma: no cover
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
|
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
|
||||||
@@ -469,8 +471,8 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
raise AssertionError("existing_private_key_bytes not set")
|
raise AssertionError("existing_private_key_bytes not set")
|
||||||
try:
|
try:
|
||||||
# Interpret bytes depending on format.
|
# Interpret bytes depending on format.
|
||||||
format = identify_private_key_format(data)
|
key_format = identify_private_key_format(data)
|
||||||
if format == "raw":
|
if key_format == "raw":
|
||||||
if len(data) == 56:
|
if len(data) == 56:
|
||||||
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
|
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
@@ -497,15 +499,13 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
data
|
data
|
||||||
)
|
)
|
||||||
raise PrivateKeyError("Cannot load raw key")
|
raise PrivateKeyError("Cannot load raw key")
|
||||||
else:
|
|
||||||
return (
|
return cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
data,
|
||||||
data,
|
None if self.passphrase is None else to_bytes(self.passphrase),
|
||||||
None if self.passphrase is None else to_bytes(self.passphrase),
|
)
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise PrivateKeyError(e)
|
raise PrivateKeyError(e) from e
|
||||||
|
|
||||||
def _ensure_existing_private_key_loaded(self) -> None:
|
def _ensure_existing_private_key_loaded(self) -> None:
|
||||||
if self.existing_private_key is None and self.has_existing():
|
if self.existing_private_key is None and self.has_existing():
|
||||||
@@ -515,21 +515,20 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
if self.existing_private_key_bytes is None:
|
if self.existing_private_key_bytes is None:
|
||||||
raise AssertionError("existing_private_key_bytes not set")
|
raise AssertionError("existing_private_key_bytes not set")
|
||||||
try:
|
try:
|
||||||
format = identify_private_key_format(self.existing_private_key_bytes)
|
key_format = identify_private_key_format(self.existing_private_key_bytes)
|
||||||
if format == "raw":
|
if key_format == "raw":
|
||||||
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
|
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
|
||||||
# actually load the key (and return False when this fails).
|
# actually load the key (and return False when this fails).
|
||||||
self._load_privatekey()
|
self._load_privatekey()
|
||||||
# Loading the key succeeded. Only return True when no passphrase was
|
# Loading the key succeeded. Only return True when no passphrase was
|
||||||
# provided.
|
# provided.
|
||||||
return self.passphrase is None
|
return self.passphrase is None
|
||||||
else:
|
return bool(
|
||||||
return bool(
|
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
self.existing_private_key_bytes,
|
||||||
self.existing_private_key_bytes,
|
None if self.passphrase is None else to_bytes(self.passphrase),
|
||||||
None if self.passphrase is None else to_bytes(self.passphrase),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -588,8 +587,8 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
|
|||||||
if self.format == "auto_ignore":
|
if self.format == "auto_ignore":
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
format = identify_private_key_format(self.existing_private_key_bytes)
|
key_format = identify_private_key_format(self.existing_private_key_bytes)
|
||||||
return format == self._get_wanted_format()
|
return key_format == self._get_wanted_format()
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -603,16 +602,16 @@ def select_backend(module: GeneralAnsibleModule) -> PrivateKeyBackend:
|
|||||||
|
|
||||||
def get_privatekey_argument_spec() -> ArgumentSpec:
|
def get_privatekey_argument_spec() -> ArgumentSpec:
|
||||||
return ArgumentSpec(
|
return ArgumentSpec(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
size=dict(type="int", default=4096),
|
"size": {"type": "int", "default": 4096},
|
||||||
type=dict(
|
"type": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="RSA",
|
"default": "RSA",
|
||||||
choices=["DSA", "ECC", "Ed25519", "Ed448", "RSA", "X25519", "X448"],
|
"choices": ["DSA", "ECC", "Ed25519", "Ed448", "RSA", "X25519", "X448"],
|
||||||
),
|
},
|
||||||
curve=dict(
|
"curve": {
|
||||||
type="str",
|
"type": "str",
|
||||||
choices=[
|
"choices": [
|
||||||
"secp224r1",
|
"secp224r1",
|
||||||
"secp256k1",
|
"secp256k1",
|
||||||
"secp256r1",
|
"secp256r1",
|
||||||
@@ -633,32 +632,36 @@ def get_privatekey_argument_spec() -> ArgumentSpec:
|
|||||||
"sect571k1",
|
"sect571k1",
|
||||||
"sect571r1",
|
"sect571r1",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
passphrase=dict(type="str", no_log=True),
|
"passphrase": {"type": "str", "no_log": True},
|
||||||
cipher=dict(type="str", default="auto"),
|
"cipher": {"type": "str", "default": "auto"},
|
||||||
format=dict(
|
"format": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="auto_ignore",
|
"default": "auto_ignore",
|
||||||
choices=["pkcs1", "pkcs8", "raw", "auto", "auto_ignore"],
|
"choices": ["pkcs1", "pkcs8", "raw", "auto", "auto_ignore"],
|
||||||
),
|
},
|
||||||
format_mismatch=dict(
|
"format_mismatch": {
|
||||||
type="str", default="regenerate", choices=["regenerate", "convert"]
|
"type": "str",
|
||||||
),
|
"default": "regenerate",
|
||||||
select_crypto_backend=dict(
|
"choices": ["regenerate", "convert"],
|
||||||
type="str", choices=["auto", "cryptography"], default="auto"
|
},
|
||||||
),
|
"select_crypto_backend": {
|
||||||
regenerate=dict(
|
"type": "str",
|
||||||
type="str",
|
"choices": ["auto", "cryptography"],
|
||||||
default="full_idempotence",
|
"default": "auto",
|
||||||
choices=[
|
},
|
||||||
|
"regenerate": {
|
||||||
|
"type": "str",
|
||||||
|
"default": "full_idempotence",
|
||||||
|
"choices": [
|
||||||
"never",
|
"never",
|
||||||
"fail",
|
"fail",
|
||||||
"partial_idempotence",
|
"partial_idempotence",
|
||||||
"full_idempotence",
|
"full_idempotence",
|
||||||
"always",
|
"always",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
),
|
},
|
||||||
required_if=[
|
required_if=[
|
||||||
("type", "ECC", ["curve"]),
|
("type", "ECC", ["curve"]),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
|
|||||||
assert self.dest_private_key_bytes is not None
|
assert self.dest_private_key_bytes is not None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
format, self.dest_private_key = self._load_private_key(
|
key_format, self.dest_private_key = self._load_private_key(
|
||||||
data=self.dest_private_key_bytes,
|
data=self.dest_private_key_bytes,
|
||||||
passphrase=self.dest_passphrase,
|
passphrase=self.dest_passphrase,
|
||||||
current_hint=self.src_private_key,
|
current_hint=self.src_private_key,
|
||||||
@@ -129,7 +129,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return format != self.format or not cryptography_compare_private_keys(
|
return key_format != self.format or not cryptography_compare_private_keys(
|
||||||
self.dest_private_key, self.src_private_key
|
self.dest_private_key, self.src_private_key
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -141,7 +141,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
|
|||||||
# Implementation with using cryptography
|
# Implementation with using cryptography
|
||||||
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(PrivateKeyConvertCryptographyBackend, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
def get_private_key_data(self) -> bytes:
|
def get_private_key_data(self) -> bytes:
|
||||||
"""Return bytes for self.src_private_key in output format"""
|
"""Return bytes for self.src_private_key in output format"""
|
||||||
@@ -166,6 +166,9 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
|||||||
export_encoding = (
|
export_encoding = (
|
||||||
cryptography.hazmat.primitives.serialization.Encoding.Raw
|
cryptography.hazmat.primitives.serialization.Encoding.Raw
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
# pylint does not notice that all possible values for self.format have been covered.
|
||||||
|
raise AssertionError("Can never be reached") # pragma: no cover
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
|
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
|
||||||
@@ -208,20 +211,20 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
|||||||
) -> tuple[str, PrivateKeyTypes]:
|
) -> tuple[str, PrivateKeyTypes]:
|
||||||
try:
|
try:
|
||||||
# Interpret bytes depending on format.
|
# Interpret bytes depending on format.
|
||||||
format = identify_private_key_format(data)
|
key_format = identify_private_key_format(data)
|
||||||
if format == "raw":
|
if key_format == "raw":
|
||||||
if passphrase is not None:
|
if passphrase is not None:
|
||||||
raise PrivateKeyError("Cannot load raw key with passphrase")
|
raise PrivateKeyError("Cannot load raw key with passphrase")
|
||||||
if len(data) == 56:
|
if len(data) == 56:
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
|
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if len(data) == 57:
|
if len(data) == 57:
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(
|
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
@@ -233,14 +236,14 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
|
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
@@ -248,29 +251,29 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
|
|||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
|
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception:
|
except Exception:
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
||||||
data
|
data
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
raise PrivateKeyError("Cannot load raw key")
|
raise PrivateKeyError("Cannot load raw key")
|
||||||
else:
|
|
||||||
return (
|
return (
|
||||||
format,
|
key_format,
|
||||||
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||||
data,
|
data,
|
||||||
None if passphrase is None else to_bytes(passphrase),
|
None if passphrase is None else to_bytes(passphrase),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise PrivateKeyError(e)
|
raise PrivateKeyError(e) from e
|
||||||
|
|
||||||
|
|
||||||
def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
|
def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
|
||||||
@@ -282,13 +285,17 @@ def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
|
|||||||
|
|
||||||
def get_privatekey_argument_spec() -> ArgumentSpec:
|
def get_privatekey_argument_spec() -> ArgumentSpec:
|
||||||
return ArgumentSpec(
|
return ArgumentSpec(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
src_path=dict(type="path"),
|
"src_path": {"type": "path"},
|
||||||
src_content=dict(type="str"),
|
"src_content": {"type": "str"},
|
||||||
src_passphrase=dict(type="str", no_log=True),
|
"src_passphrase": {"type": "str", "no_log": True},
|
||||||
dest_passphrase=dict(type="str", no_log=True),
|
"dest_passphrase": {"type": "str", "no_log": True},
|
||||||
format=dict(type="str", required=True, choices=["pkcs1", "pkcs8", "raw"]),
|
"format": {
|
||||||
),
|
"type": "str",
|
||||||
|
"required": True,
|
||||||
|
"choices": ["pkcs1", "pkcs8", "raw"],
|
||||||
|
},
|
||||||
|
},
|
||||||
mutually_exclusive=[
|
mutually_exclusive=[
|
||||||
["src_path", "src_content"],
|
["src_path", "src_content"],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ def _is_cryptography_key_consistent(
|
|||||||
# key._backend was removed in cryptography 42.0.0
|
# key._backend was removed in cryptography 42.0.0
|
||||||
backend = getattr(key, "_backend", None)
|
backend = getattr(key, "_backend", None)
|
||||||
if backend is not None:
|
if backend is not None:
|
||||||
return bool(backend._lib.RSA_check_key(key._rsa_cdata)) # type: ignore
|
return bool(backend._lib.RSA_check_key(key._rsa_cdata)) # type: ignore # pylint: disable=protected-access
|
||||||
if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
if isinstance(key, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||||
result = _check_dsa_consistency(
|
result = _check_dsa_consistency(
|
||||||
key_public_data=key_public_data, key_private_data=key_private_data
|
key_public_data=key_public_data, key_private_data=key_private_data
|
||||||
@@ -195,19 +195,21 @@ def _is_cryptography_key_consistent(
|
|||||||
|
|
||||||
class PrivateKeyConsistencyError(OpenSSLObjectError):
|
class PrivateKeyConsistencyError(OpenSSLObjectError):
|
||||||
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
||||||
super(PrivateKeyConsistencyError, self).__init__(msg)
|
super().__init__(msg)
|
||||||
self.error_message = msg
|
self.error_message = msg
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
|
|
||||||
class PrivateKeyParseError(OpenSSLObjectError):
|
class PrivateKeyParseError(OpenSSLObjectError):
|
||||||
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
||||||
super(PrivateKeyParseError, self).__init__(msg)
|
super().__init__(msg)
|
||||||
self.error_message = msg
|
self.error_message = msg
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
|
|
||||||
class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
||||||
|
key: PrivateKeyTypes
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
@@ -257,14 +259,14 @@ class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
|||||||
)
|
)
|
||||||
result["can_parse_key"] = True
|
result["can_parse_key"] = True
|
||||||
except OpenSSLObjectError as exc:
|
except OpenSSLObjectError as exc:
|
||||||
raise PrivateKeyParseError(str(exc), result=result)
|
raise PrivateKeyParseError(str(exc), result=result) from exc
|
||||||
|
|
||||||
result["public_key"] = to_native(self._get_public_key(binary=False))
|
result["public_key"] = to_native(self._get_public_key(binary=False))
|
||||||
pk = self._get_public_key(binary=True)
|
pk = self._get_public_key(binary=True)
|
||||||
result["public_key_fingerprints"] = (
|
result["public_key_fingerprints"] = (
|
||||||
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
|
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
|
||||||
if pk is not None
|
if pk is not None
|
||||||
else dict()
|
else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
key_type, key_public_data, key_private_data = self._get_key_info(
|
key_type, key_public_data, key_private_data = self._get_key_info(
|
||||||
@@ -295,9 +297,7 @@ class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, *, module: GeneralAnsibleModule, content: bytes, **kwargs
|
self, *, module: GeneralAnsibleModule, content: bytes, **kwargs
|
||||||
) -> None:
|
) -> None:
|
||||||
super(PrivateKeyInfoRetrievalCryptography, self).__init__(
|
super().__init__(module=module, content=content, **kwargs)
|
||||||
module=module, content=content, **kwargs
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_public_key(self, *, binary: bool) -> bytes:
|
def _get_public_key(self, *, binary: bool) -> bytes:
|
||||||
return self.key.public_key().public_bytes(
|
return self.key.public_key().public_bytes(
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def _get_cryptography_public_key_info(
|
|||||||
|
|
||||||
class PublicKeyParseError(OpenSSLObjectError):
|
class PublicKeyParseError(OpenSSLObjectError):
|
||||||
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
|
||||||
super(PublicKeyParseError, self).__init__(msg)
|
super().__init__(msg)
|
||||||
self.error_message = msg
|
self.error_message = msg
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
@@ -132,13 +132,13 @@ class PublicKeyInfoRetrieval(metaclass=abc.ABCMeta):
|
|||||||
try:
|
try:
|
||||||
self.key = load_publickey(content=self.content)
|
self.key = load_publickey(content=self.content)
|
||||||
except OpenSSLObjectError as e:
|
except OpenSSLObjectError as e:
|
||||||
raise PublicKeyParseError(str(e), result={})
|
raise PublicKeyParseError(str(e), result={}) from e
|
||||||
|
|
||||||
pk = self._get_public_key(binary=True)
|
pk = self._get_public_key(binary=True)
|
||||||
result["fingerprints"] = (
|
result["fingerprints"] = (
|
||||||
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
|
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
|
||||||
if pk is not None
|
if pk is not None
|
||||||
else dict()
|
else {}
|
||||||
)
|
)
|
||||||
|
|
||||||
key_type, key_public_data = self._get_key_info()
|
key_type, key_public_data = self._get_key_info()
|
||||||
@@ -157,9 +157,7 @@ class PublicKeyInfoRetrievalCryptography(PublicKeyInfoRetrieval):
|
|||||||
content: bytes | None = None,
|
content: bytes | None = None,
|
||||||
key: PublicKeyTypes | None = None,
|
key: PublicKeyTypes | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super(PublicKeyInfoRetrievalCryptography, self).__init__(
|
super().__init__(module=module, content=content, key=key)
|
||||||
module=module, content=content, key=key
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_public_key(self, binary: bool) -> bytes:
|
def _get_public_key(self, binary: bool) -> bytes:
|
||||||
if self.key is None:
|
if self.key is None:
|
||||||
|
|||||||
@@ -161,19 +161,21 @@ def load_privatekey(
|
|||||||
else:
|
else:
|
||||||
priv_key_detail = content
|
priv_key_detail = content
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return load_pem_private_key(
|
return load_pem_private_key(
|
||||||
priv_key_detail,
|
priv_key_detail,
|
||||||
None if passphrase is None else to_bytes(passphrase),
|
None if passphrase is None else to_bytes(passphrase),
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError as exc:
|
||||||
raise OpenSSLBadPassphraseError(
|
raise OpenSSLBadPassphraseError(
|
||||||
"Wrong or empty passphrase provided for private key"
|
"Wrong or empty passphrase provided for private key"
|
||||||
)
|
) from exc
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise OpenSSLBadPassphraseError("Wrong passphrase provided for private key")
|
raise OpenSSLBadPassphraseError(
|
||||||
|
"Wrong passphrase provided for private key"
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
|
||||||
def load_certificate_privatekey(
|
def load_certificate_privatekey(
|
||||||
@@ -232,12 +234,12 @@ def load_publickey(
|
|||||||
with open(path, "rb") as b_priv_key_fh:
|
with open(path, "rb") as b_priv_key_fh:
|
||||||
content = b_priv_key_fh.read()
|
content = b_priv_key_fh.read()
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return serialization.load_pem_public_key(content)
|
return serialization.load_pem_public_key(content)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise OpenSSLObjectError(f"Error while deserializing key: {e}")
|
raise OpenSSLObjectError(f"Error while deserializing key: {e}") from e
|
||||||
|
|
||||||
|
|
||||||
def load_certificate(
|
def load_certificate(
|
||||||
@@ -257,17 +259,17 @@ def load_certificate(
|
|||||||
else:
|
else:
|
||||||
cert_content = content
|
cert_content = content
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
if der_support_enabled is False or identify_pem_format(cert_content):
|
if der_support_enabled is False or identify_pem_format(cert_content):
|
||||||
try:
|
try:
|
||||||
return x509.load_pem_x509_certificate(cert_content)
|
return x509.load_pem_x509_certificate(cert_content)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
elif der_support_enabled:
|
elif der_support_enabled:
|
||||||
try:
|
try:
|
||||||
return x509.load_der_x509_certificate(cert_content)
|
return x509.load_der_x509_certificate(cert_content)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise OpenSSLObjectError(f"Cannot parse DER certificate: {exc}")
|
raise OpenSSLObjectError(f"Cannot parse DER certificate: {exc}") from exc
|
||||||
|
|
||||||
|
|
||||||
def load_certificate_request(
|
def load_certificate_request(
|
||||||
@@ -283,11 +285,11 @@ def load_certificate_request(
|
|||||||
else:
|
else:
|
||||||
csr_content = content
|
csr_content = content
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
try:
|
try:
|
||||||
return x509.load_pem_x509_csr(csr_content)
|
return x509.load_pem_x509_csr(csr_content)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
|
|
||||||
|
|
||||||
def parse_name_field(
|
def parse_name_field(
|
||||||
@@ -344,7 +346,7 @@ def parse_ordered_name_field(
|
|||||||
except (TypeError, ValueError) as exc:
|
except (TypeError, ValueError) as exc:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Error while processing entry #{index + 1} in {name_field_name}: {exc}"
|
f"Error while processing entry #{index + 1} in {name_field_name}: {exc}"
|
||||||
)
|
) from exc
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@@ -425,9 +427,7 @@ class OpenSSLObject(metaclass=abc.ABCMeta):
|
|||||||
self.changed = True
|
self.changed = True
|
||||||
except OSError as exc:
|
except OSError as exc:
|
||||||
if exc.errno != errno.ENOENT:
|
if exc.errno != errno.ENOENT:
|
||||||
raise OpenSSLObjectError(exc)
|
raise OpenSSLObjectError(exc) from exc
|
||||||
else:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
|
|||||||
@@ -43,16 +43,20 @@ valid_file_format = re.compile(r".*(\.)(yml|yaml|json)$")
|
|||||||
|
|
||||||
|
|
||||||
def ecs_client_argument_spec() -> dict[str, t.Any]:
|
def ecs_client_argument_spec() -> dict[str, t.Any]:
|
||||||
return dict(
|
return {
|
||||||
entrust_api_user=dict(type="str", required=True),
|
"entrust_api_user": {"type": "str", "required": True},
|
||||||
entrust_api_key=dict(type="str", required=True, no_log=True),
|
"entrust_api_key": {"type": "str", "required": True, "no_log": True},
|
||||||
entrust_api_client_cert_path=dict(type="path", required=True),
|
"entrust_api_client_cert_path": {"type": "path", "required": True},
|
||||||
entrust_api_client_cert_key_path=dict(type="path", required=True, no_log=True),
|
"entrust_api_client_cert_key_path": {
|
||||||
entrust_api_specification_path=dict(
|
"type": "path",
|
||||||
type="path",
|
"required": True,
|
||||||
default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
"no_log": True,
|
||||||
),
|
},
|
||||||
)
|
"entrust_api_specification_path": {
|
||||||
|
"type": "path",
|
||||||
|
"default": "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class SessionConfigurationException(Exception):
|
class SessionConfigurationException(Exception):
|
||||||
@@ -182,8 +186,7 @@ class RestOperation:
|
|||||||
if result or result == {}:
|
if result or result == {}:
|
||||||
if result_code and result_code < 400:
|
if result_code and result_code < 400:
|
||||||
return result
|
return result
|
||||||
else:
|
raise RestOperationException(result)
|
||||||
raise RestOperationException(result)
|
|
||||||
|
|
||||||
# Raise a generic RestOperationException if this fails
|
# Raise a generic RestOperationException if this fails
|
||||||
raise RestOperationException(
|
raise RestOperationException(
|
||||||
@@ -323,9 +326,9 @@ class ECSSession:
|
|||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
raise SessionConfigurationException(
|
raise SessionConfigurationException(
|
||||||
f"Error downloading specification from address '{entrust_api_specification_path}', received error code '{e.getcode()}'"
|
f"Error downloading specification from address '{entrust_api_specification_path}', received error code '{e.getcode()}'"
|
||||||
)
|
) from e
|
||||||
else:
|
else:
|
||||||
with open(entrust_api_specification_path) as f:
|
with open(entrust_api_specification_path, "rb") as f:
|
||||||
if ".json" in entrust_api_specification_path:
|
if ".json" in entrust_api_specification_path:
|
||||||
self._spec = json.load(f)
|
self._spec = json.load(f)
|
||||||
elif (
|
elif (
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ def restore_on_failure(
|
|||||||
if backup_file is not None:
|
if backup_file is not None:
|
||||||
module.atomic_move(os.path.abspath(backup_file), os.path.abspath(path))
|
module.atomic_move(os.path.abspath(backup_file), os.path.abspath(path))
|
||||||
raise
|
raise
|
||||||
else:
|
module.add_cleanup_file(backup_file)
|
||||||
module.add_cleanup_file(backup_file)
|
|
||||||
|
|
||||||
return backup_and_restore
|
return backup_and_restore
|
||||||
|
|
||||||
@@ -91,9 +90,8 @@ def _restore_all_on_failure(
|
|||||||
os.path.abspath(backup), os.path.abspath(destination)
|
os.path.abspath(backup), os.path.abspath(destination)
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
else:
|
for destination, backup in backups:
|
||||||
for destination, backup in backups:
|
self.module.add_cleanup_file(backup)
|
||||||
self.module.add_cleanup_file(backup)
|
|
||||||
|
|
||||||
return backup_and_restore
|
return backup_and_restore
|
||||||
|
|
||||||
@@ -126,7 +124,7 @@ class OpensshModule(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
result["changed"] = self.changed
|
result["changed"] = self.changed
|
||||||
|
|
||||||
if self.module._diff:
|
if self.module._diff: # pylint: disable=protected-access
|
||||||
result["diff"] = self.diff
|
result["diff"] = self.diff
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@@ -219,7 +217,7 @@ class KeygenCommand:
|
|||||||
serial_number: int | None,
|
serial_number: int | None,
|
||||||
signature_algorithm: str | None,
|
signature_algorithm: str | None,
|
||||||
signing_key_path: str,
|
signing_key_path: str,
|
||||||
type: t.Literal["host", "user"] | None,
|
cert_type: t.Literal["host", "user"] | None,
|
||||||
time_parameters: OpensshCertificateTimeParameters,
|
time_parameters: OpensshCertificateTimeParameters,
|
||||||
use_agent: bool,
|
use_agent: bool,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -235,7 +233,7 @@ class KeygenCommand:
|
|||||||
args.extend(["-n", ",".join(principals)])
|
args.extend(["-n", ",".join(principals)])
|
||||||
if serial_number is not None:
|
if serial_number is not None:
|
||||||
args.extend(["-z", str(serial_number)])
|
args.extend(["-z", str(serial_number)])
|
||||||
if type == "host":
|
if cert_type == "host":
|
||||||
args.extend(["-h"])
|
args.extend(["-h"])
|
||||||
if use_agent:
|
if use_agent:
|
||||||
args.extend(["-U"])
|
args.extend(["-U"])
|
||||||
@@ -252,7 +250,7 @@ class KeygenCommand:
|
|||||||
*,
|
*,
|
||||||
private_key_path: str,
|
private_key_path: str,
|
||||||
size: int,
|
size: int,
|
||||||
type: str,
|
key_type: str,
|
||||||
comment: str | None,
|
comment: str | None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> tuple[int, str, str]:
|
) -> tuple[int, str, str]:
|
||||||
@@ -264,7 +262,7 @@ class KeygenCommand:
|
|||||||
"-b",
|
"-b",
|
||||||
str(size),
|
str(size),
|
||||||
"-t",
|
"-t",
|
||||||
type,
|
key_type,
|
||||||
"-f",
|
"-f",
|
||||||
private_key_path,
|
private_key_path,
|
||||||
"-C",
|
"-C",
|
||||||
@@ -313,7 +311,7 @@ class KeygenCommand:
|
|||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The private key at {private_key_path} is not writeable preventing a comment update ({e})"
|
f"The private key at {private_key_path} is not writeable preventing a comment update ({e})"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
command = [self._bin_path, "-q"]
|
command = [self._bin_path, "-q"]
|
||||||
if force_new_format:
|
if force_new_format:
|
||||||
@@ -327,12 +325,12 @@ _PrivateKey = t.TypeVar("_PrivateKey", bound="PrivateKey")
|
|||||||
|
|
||||||
class PrivateKey:
|
class PrivateKey:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, *, size: int, key_type: str, fingerprint: str, format: str = ""
|
self, *, size: int, key_type: str, fingerprint: str, key_format: str = ""
|
||||||
) -> None:
|
) -> None:
|
||||||
self._size = size
|
self._size = size
|
||||||
self._type = key_type
|
self._type = key_type
|
||||||
self._fingerprint = fingerprint
|
self._fingerprint = fingerprint
|
||||||
self._format = format
|
self._format = key_format
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def size(self) -> int:
|
def size(self) -> int:
|
||||||
@@ -428,11 +426,8 @@ class PublicKey:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def load(cls: t.Type[_PublicKey], path: str | os.PathLike) -> _PublicKey | None:
|
def load(cls: t.Type[_PublicKey], path: str | os.PathLike) -> _PublicKey | None:
|
||||||
try:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
with open(path, "r") as f:
|
properties = f.read().strip(" \n").split(" ", 2)
|
||||||
properties = f.read().strip(" \n").split(" ", 2)
|
|
||||||
except (IOError, OSError):
|
|
||||||
raise
|
|
||||||
|
|
||||||
if len(properties) < 2:
|
if len(properties) < 2:
|
||||||
return None
|
return None
|
||||||
@@ -454,14 +449,14 @@ def parse_private_key_format(
|
|||||||
*,
|
*,
|
||||||
path: str | os.PathLike,
|
path: str | os.PathLike,
|
||||||
) -> t.Literal["SSH", "PKCS8", "PKCS1", ""]:
|
) -> t.Literal["SSH", "PKCS8", "PKCS1", ""]:
|
||||||
with open(path, "r") as file:
|
with open(path, "r", encoding="utf-8") as file:
|
||||||
header = file.readline().strip()
|
header = file.readline().strip()
|
||||||
|
|
||||||
if header == "-----BEGIN OPENSSH PRIVATE KEY-----":
|
if header == "-----BEGIN OPENSSH PRIVATE KEY-----":
|
||||||
return "SSH"
|
return "SSH"
|
||||||
elif header == "-----BEGIN PRIVATE KEY-----":
|
if header == "-----BEGIN PRIVATE KEY-----":
|
||||||
return "PKCS8"
|
return "PKCS8"
|
||||||
elif header == "-----BEGIN RSA PRIVATE KEY-----":
|
if header == "-----BEGIN RSA PRIVATE KEY-----":
|
||||||
return "PKCS1"
|
return "PKCS1"
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ if t.TYPE_CHECKING:
|
|||||||
class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
||||||
|
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(KeypairBackend, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
self.comment: str | None = self.module.params["comment"]
|
self.comment: str | None = self.module.params["comment"]
|
||||||
self.private_key_path: str = self.module.params["path"]
|
self.private_key_path: str = self.module.params["path"]
|
||||||
@@ -189,9 +189,9 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
|||||||
def _should_generate(self) -> bool:
|
def _should_generate(self) -> bool:
|
||||||
if self.original_private_key is None:
|
if self.original_private_key is None:
|
||||||
return True
|
return True
|
||||||
elif self.regenerate == "never":
|
if self.regenerate == "never":
|
||||||
return False
|
return False
|
||||||
elif self.regenerate == "fail":
|
if self.regenerate == "fail":
|
||||||
if not self._private_key_valid():
|
if not self._private_key_valid():
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="Key has wrong type and/or size. Will not proceed. "
|
msg="Key has wrong type and/or size. Will not proceed. "
|
||||||
@@ -199,10 +199,9 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
|||||||
+ "`partial_idempotence`, `full_idempotence` or `always`, or with `force=true`."
|
+ "`partial_idempotence`, `full_idempotence` or `always`, or with `force=true`."
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
elif self.regenerate in ("partial_idempotence", "full_idempotence"):
|
if self.regenerate in ("partial_idempotence", "full_idempotence"):
|
||||||
return not self._private_key_valid()
|
return not self._private_key_valid()
|
||||||
else:
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
def _private_key_valid(self) -> bool:
|
def _private_key_valid(self) -> bool:
|
||||||
if self.original_private_key is None:
|
if self.original_private_key is None:
|
||||||
@@ -358,7 +357,7 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
class KeypairBackendOpensshBin(KeypairBackend):
|
class KeypairBackendOpensshBin(KeypairBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(KeypairBackendOpensshBin, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
if self.module.params["private_key_format"] != "auto":
|
if self.module.params["private_key_format"] != "auto":
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
@@ -371,7 +370,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
|||||||
self.ssh_keygen.generate_keypair(
|
self.ssh_keygen.generate_keypair(
|
||||||
private_key_path=private_key_path,
|
private_key_path=private_key_path,
|
||||||
size=self.size,
|
size=self.size,
|
||||||
type=self.type,
|
key_type=self.type,
|
||||||
comment=self.comment,
|
comment=self.comment,
|
||||||
check_rc=True,
|
check_rc=True,
|
||||||
)
|
)
|
||||||
@@ -391,7 +390,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
|||||||
return PublicKey.from_string(public_key_content)
|
return PublicKey.from_string(public_key_content)
|
||||||
|
|
||||||
def _private_key_readable(self) -> bool:
|
def _private_key_readable(self) -> bool:
|
||||||
rc, stdout, stderr = self.ssh_keygen.get_matching_public_key(
|
rc, _stdout, stderr = self.ssh_keygen.get_matching_public_key(
|
||||||
private_key_path=self.private_key_path, check_rc=False
|
private_key_path=self.private_key_path, check_rc=False
|
||||||
)
|
)
|
||||||
return not (
|
return not (
|
||||||
@@ -425,7 +424,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
|
|||||||
|
|
||||||
class KeypairBackendCryptography(KeypairBackend):
|
class KeypairBackendCryptography(KeypairBackend):
|
||||||
def __init__(self, *, module: AnsibleModule) -> None:
|
def __init__(self, *, module: AnsibleModule) -> None:
|
||||||
super(KeypairBackendCryptography, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
|
|
||||||
if self.type == "rsa1":
|
if self.type == "rsa1":
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
@@ -489,7 +488,7 @@ class KeypairBackendCryptography(KeypairBackend):
|
|||||||
size=keypair.size,
|
size=keypair.size,
|
||||||
key_type=keypair.key_type,
|
key_type=keypair.key_type,
|
||||||
fingerprint=keypair.fingerprint,
|
fingerprint=keypair.fingerprint,
|
||||||
format=parse_private_key_format(path=self.private_key_path),
|
key_format=parse_private_key_format(path=self.private_key_path),
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_public_key(self) -> PublicKey | t.Literal[""]:
|
def _get_public_key(self) -> PublicKey | t.Literal[""]:
|
||||||
@@ -522,10 +521,9 @@ class KeypairBackendCryptography(KeypairBackend):
|
|||||||
OpensshKeypair.load(
|
OpensshKeypair.load(
|
||||||
path=self.private_key_path, passphrase=None, no_public_key=True
|
path=self.private_key_path, passphrase=None, no_public_key=True
|
||||||
)
|
)
|
||||||
|
return False
|
||||||
except (InvalidPrivateKeyFileError, InvalidPassphraseError):
|
except (InvalidPrivateKeyFileError, InvalidPassphraseError):
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -124,11 +124,9 @@ class OpensshCertificateTimeParameters:
|
|||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, type(self)):
|
if not isinstance(other, type(self)):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
else:
|
return (
|
||||||
return (
|
self._valid_from == other._valid_from and self._valid_to == other._valid_to
|
||||||
self._valid_from == other._valid_from
|
)
|
||||||
and self._valid_to == other._valid_to
|
|
||||||
)
|
|
||||||
|
|
||||||
def __ne__(self, other: object) -> bool:
|
def __ne__(self, other: object) -> bool:
|
||||||
return not self == other
|
return not self == other
|
||||||
@@ -188,12 +186,11 @@ class OpensshCertificateTimeParameters:
|
|||||||
return "always"
|
return "always"
|
||||||
if dt == _FOREVER:
|
if dt == _FOREVER:
|
||||||
return "forever"
|
return "forever"
|
||||||
else:
|
return (
|
||||||
return (
|
dt.isoformat().replace("+00:00", "")
|
||||||
dt.isoformat().replace("+00:00", "")
|
if date_format == "human_readable"
|
||||||
if date_format == "human_readable"
|
else dt.strftime("%Y%m%d%H%M%S")
|
||||||
else dt.strftime("%Y%m%d%H%M%S")
|
)
|
||||||
)
|
|
||||||
if date_format == "timestamp":
|
if date_format == "timestamp":
|
||||||
td = dt - _ALWAYS
|
td = dt - _ALWAYS
|
||||||
return int(
|
return int(
|
||||||
@@ -203,22 +200,17 @@ class OpensshCertificateTimeParameters:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def to_datetime(time_string_or_timestamp: str | bytes | int) -> datetime:
|
def to_datetime(time_string_or_timestamp: str | bytes | int) -> datetime:
|
||||||
try:
|
if isinstance(time_string_or_timestamp, (str, bytes)):
|
||||||
if isinstance(time_string_or_timestamp, (str, bytes)):
|
return OpensshCertificateTimeParameters._time_string_to_datetime(
|
||||||
result = OpensshCertificateTimeParameters._time_string_to_datetime(
|
to_text(time_string_or_timestamp.strip())
|
||||||
to_text(time_string_or_timestamp.strip())
|
)
|
||||||
)
|
if isinstance(time_string_or_timestamp, int):
|
||||||
elif isinstance(time_string_or_timestamp, int):
|
return OpensshCertificateTimeParameters._timestamp_to_datetime(
|
||||||
result = OpensshCertificateTimeParameters._timestamp_to_datetime(
|
time_string_or_timestamp
|
||||||
time_string_or_timestamp
|
)
|
||||||
)
|
raise ValueError(
|
||||||
else:
|
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
|
||||||
raise ValueError(
|
)
|
||||||
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
|
|
||||||
)
|
|
||||||
except ValueError:
|
|
||||||
raise
|
|
||||||
return result
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _timestamp_to_datetime(timestamp: int) -> datetime:
|
def _timestamp_to_datetime(timestamp: int) -> datetime:
|
||||||
@@ -228,8 +220,8 @@ class OpensshCertificateTimeParameters:
|
|||||||
return _FOREVER
|
return _FOREVER
|
||||||
try:
|
try:
|
||||||
return datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
|
return datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
|
||||||
except OverflowError:
|
except OverflowError as e:
|
||||||
raise ValueError
|
raise ValueError from e
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _time_string_to_datetime(time_string: str) -> datetime:
|
def _time_string_to_datetime(time_string: str) -> datetime:
|
||||||
@@ -382,16 +374,15 @@ class OpensshCertificateInfo(metaclass=abc.ABCMeta):
|
|||||||
def cert_type(self) -> t.Literal["user", "host", ""]:
|
def cert_type(self) -> t.Literal["user", "host", ""]:
|
||||||
if self._cert_type == _USER_TYPE:
|
if self._cert_type == _USER_TYPE:
|
||||||
return "user"
|
return "user"
|
||||||
elif self._cert_type == _HOST_TYPE:
|
if self._cert_type == _HOST_TYPE:
|
||||||
return "host"
|
return "host"
|
||||||
else:
|
return ""
|
||||||
return ""
|
|
||||||
|
|
||||||
@cert_type.setter
|
@cert_type.setter
|
||||||
def cert_type(self, cert_type: t.Literal["user", "host"] | int) -> None:
|
def cert_type(self, cert_type: t.Literal["user", "host"] | int) -> None:
|
||||||
if cert_type == "user" or cert_type == _USER_TYPE:
|
if cert_type in ("user", _USER_TYPE):
|
||||||
self._cert_type = _USER_TYPE
|
self._cert_type = _USER_TYPE
|
||||||
elif cert_type == "host" or cert_type == _HOST_TYPE:
|
elif cert_type in ("host", _HOST_TYPE):
|
||||||
self._cert_type = _HOST_TYPE
|
self._cert_type = _HOST_TYPE
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"{cert_type} is not a valid certificate type")
|
raise ValueError(f"{cert_type} is not a valid certificate type")
|
||||||
@@ -412,7 +403,7 @@ class OpensshCertificateInfo(metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
class OpensshRSACertificateInfo(OpensshCertificateInfo):
|
class OpensshRSACertificateInfo(OpensshCertificateInfo):
|
||||||
def __init__(self, *, e: int | None = None, n: int | None = None, **kwargs) -> None:
|
def __init__(self, *, e: int | None = None, n: int | None = None, **kwargs) -> None:
|
||||||
super(OpensshRSACertificateInfo, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.type_string = _SSH_TYPE_STRINGS["rsa"] + _CERT_SUFFIX_V01
|
self.type_string = _SSH_TYPE_STRINGS["rsa"] + _CERT_SUFFIX_V01
|
||||||
self.e = e
|
self.e = e
|
||||||
self.n = n
|
self.n = n
|
||||||
@@ -444,7 +435,7 @@ class OpensshDSACertificateInfo(OpensshCertificateInfo):
|
|||||||
y: int | None = None,
|
y: int | None = None,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> None:
|
) -> None:
|
||||||
super(OpensshDSACertificateInfo, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.type_string = _SSH_TYPE_STRINGS["dsa"] + _CERT_SUFFIX_V01
|
self.type_string = _SSH_TYPE_STRINGS["dsa"] + _CERT_SUFFIX_V01
|
||||||
self.p = p
|
self.p = p
|
||||||
self.q = q
|
self.q = q
|
||||||
@@ -476,7 +467,7 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, *, curve: bytes | None = None, public_key: bytes | None = None, **kwargs
|
self, *, curve: bytes | None = None, public_key: bytes | None = None, **kwargs
|
||||||
):
|
):
|
||||||
super(OpensshECDSACertificateInfo, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self._curve = None
|
self._curve = None
|
||||||
if curve is not None:
|
if curve is not None:
|
||||||
self.curve = curve
|
self.curve = curve
|
||||||
@@ -519,7 +510,7 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo):
|
|||||||
|
|
||||||
class OpensshED25519CertificateInfo(OpensshCertificateInfo):
|
class OpensshED25519CertificateInfo(OpensshCertificateInfo):
|
||||||
def __init__(self, *, pk: bytes | None = None, **kwargs) -> None:
|
def __init__(self, *, pk: bytes | None = None, **kwargs) -> None:
|
||||||
super(OpensshED25519CertificateInfo, self).__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.type_string = _SSH_TYPE_STRINGS["ed25519"] + _CERT_SUFFIX_V01
|
self.type_string = _SSH_TYPE_STRINGS["ed25519"] + _CERT_SUFFIX_V01
|
||||||
self.pk = pk
|
self.pk = pk
|
||||||
|
|
||||||
@@ -559,13 +550,13 @@ class OpensshCertificate:
|
|||||||
with open(path, "rb") as cert_file:
|
with open(path, "rb") as cert_file:
|
||||||
data = cert_file.read()
|
data = cert_file.read()
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
raise ValueError(f"{path} cannot be opened for reading: {e}")
|
raise ValueError(f"{path} cannot be opened for reading: {e}") from e
|
||||||
|
|
||||||
try:
|
try:
|
||||||
format_identifier, b64_cert = data.split(b" ")[:2]
|
format_identifier, b64_cert = data.split(b" ")[:2]
|
||||||
cert = binascii.a2b_base64(b64_cert)
|
cert = binascii.a2b_base64(b64_cert)
|
||||||
except (binascii.Error, ValueError):
|
except (binascii.Error, ValueError) as e:
|
||||||
raise ValueError("Certificate not in OpenSSH format")
|
raise ValueError("Certificate not in OpenSSH format") from e
|
||||||
|
|
||||||
for key_type, string in _SSH_TYPE_STRINGS.items():
|
for key_type, string in _SSH_TYPE_STRINGS.items():
|
||||||
if format_identifier == string + _CERT_SUFFIX_V01:
|
if format_identifier == string + _CERT_SUFFIX_V01:
|
||||||
@@ -585,7 +576,7 @@ class OpensshCertificate:
|
|||||||
cert_info = cls._parse_cert_info(pub_key_type, parser)
|
cert_info = cls._parse_cert_info(pub_key_type, parser)
|
||||||
signature = parser.string()
|
signature = parser.string()
|
||||||
except (TypeError, ValueError) as e:
|
except (TypeError, ValueError) as e:
|
||||||
raise ValueError(f"Invalid certificate data: {e}")
|
raise ValueError(f"Invalid certificate data: {e}") from e
|
||||||
|
|
||||||
if parser.remaining_bytes():
|
if parser.remaining_bytes():
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -751,10 +742,9 @@ def apply_directives(directives: t.Iterable[str]) -> list[OpensshCertificateOpti
|
|||||||
|
|
||||||
if "clear" in directives:
|
if "clear" in directives:
|
||||||
return []
|
return []
|
||||||
else:
|
return list(
|
||||||
return list(
|
set(default_options()) - set(directive_to_option[d] for d in directives)
|
||||||
set(default_options()) - set(directive_to_option[d] for d in directives)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def default_options() -> list[OpensshCertificateOption]:
|
def default_options() -> list[OpensshCertificateOption]:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ try:
|
|||||||
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
|
||||||
from cryptography.hazmat.primitives import hashes, serialization
|
from cryptography.hazmat.primitives import hashes, serialization
|
||||||
from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding, rsa
|
from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding, rsa
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
|
||||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
||||||
Ed25519PrivateKey,
|
Ed25519PrivateKey,
|
||||||
Ed25519PublicKey,
|
Ed25519PublicKey,
|
||||||
@@ -68,6 +69,10 @@ except ImportError:
|
|||||||
CRYPTOGRAPHY_VERSION = "0.0"
|
CRYPTOGRAPHY_VERSION = "0.0"
|
||||||
_ALGORITHM_PARAMETERS = {}
|
_ALGORITHM_PARAMETERS = {}
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils._crypto.cryptography_support import (
|
||||||
|
is_potential_certificate_issuer_private_key,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if t.TYPE_CHECKING:
|
if t.TYPE_CHECKING:
|
||||||
KeyFormat = t.Literal["SSH", "PKCS8", "PKCS1"]
|
KeyFormat = t.Literal["SSH", "PKCS8", "PKCS1"]
|
||||||
@@ -309,10 +314,10 @@ class AsymmetricKeypair:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
self.verify(signature=self.sign(b"message"), data=b"message")
|
self.verify(signature=self.sign(b"message"), data=b"message")
|
||||||
except InvalidSignatureError:
|
except InvalidSignatureError as e:
|
||||||
raise InvalidPublicKeyFileError(
|
raise InvalidPublicKeyFileError(
|
||||||
"The private key and public key of this keypair do not match"
|
"The private key and public key of this keypair do not match"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
def __eq__(self, other: object) -> bool:
|
def __eq__(self, other: object) -> bool:
|
||||||
if not isinstance(other, AsymmetricKeypair):
|
if not isinstance(other, AsymmetricKeypair):
|
||||||
@@ -368,7 +373,7 @@ class AsymmetricKeypair:
|
|||||||
data, **_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"] # type: ignore
|
data, **_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"] # type: ignore
|
||||||
)
|
)
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise InvalidDataError(e)
|
raise InvalidDataError(e) from e
|
||||||
|
|
||||||
def verify(self, *, signature: bytes, data: bytes) -> None:
|
def verify(self, *, signature: bytes, data: bytes) -> None:
|
||||||
"""Verifies that the signature associated with the provided data was signed
|
"""Verifies that the signature associated with the provided data was signed
|
||||||
@@ -383,8 +388,8 @@ class AsymmetricKeypair:
|
|||||||
data,
|
data,
|
||||||
**_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"], # type: ignore
|
**_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"], # type: ignore
|
||||||
)
|
)
|
||||||
except InvalidSignature:
|
except InvalidSignature as e:
|
||||||
raise InvalidSignatureError
|
raise InvalidSignatureError from e
|
||||||
|
|
||||||
def update_passphrase(self, passphrase: bytes | None = None) -> None:
|
def update_passphrase(self, passphrase: bytes | None = None) -> None:
|
||||||
"""Updates the encryption algorithm of this key pair
|
"""Updates the encryption algorithm of this key pair
|
||||||
@@ -661,10 +666,10 @@ def load_privatekey(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
privatekey_loader = privatekey_loaders[key_format]
|
privatekey_loader = privatekey_loaders[key_format]
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
raise InvalidKeyFormatError(
|
raise InvalidKeyFormatError(
|
||||||
f"{key_format} is not a valid key format ({','.join(privatekey_loaders)})"
|
f"{key_format} is not a valid key format ({','.join(privatekey_loaders)})"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise InvalidPrivateKeyFileError(f"No file was found at {path}")
|
raise InvalidPrivateKeyFileError(f"No file was found at {path}")
|
||||||
@@ -673,32 +678,33 @@ def load_privatekey(
|
|||||||
with open(path, "rb") as f:
|
with open(path, "rb") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
privatekey = privatekey_loader( # type: ignore
|
try:
|
||||||
|
privatekey = privatekey_loader(
|
||||||
data=content,
|
data=content,
|
||||||
password=passphrase,
|
password=passphrase,
|
||||||
)
|
)
|
||||||
|
except ValueError as exc:
|
||||||
except ValueError as exc:
|
# Revert to PEM if key could not be loaded in SSH format
|
||||||
# Revert to PEM if key could not be loaded in SSH format
|
if key_format == "SSH":
|
||||||
if key_format == "SSH":
|
privatekey = privatekey_loaders["PEM"](
|
||||||
try:
|
|
||||||
privatekey = privatekey_loaders["PEM"]( # type: ignore
|
|
||||||
data=content,
|
data=content,
|
||||||
password=passphrase,
|
password=passphrase,
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
else:
|
||||||
raise InvalidPrivateKeyFileError(e)
|
raise InvalidPrivateKeyFileError(exc) from exc
|
||||||
except TypeError as e:
|
except ValueError as e:
|
||||||
raise InvalidPassphraseError(e)
|
raise InvalidPrivateKeyFileError(e) from e
|
||||||
except UnsupportedAlgorithm as e:
|
|
||||||
raise InvalidAlgorithmError(e)
|
|
||||||
else:
|
|
||||||
raise InvalidPrivateKeyFileError(exc)
|
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise InvalidPassphraseError(e)
|
raise InvalidPassphraseError(e) from e
|
||||||
except UnsupportedAlgorithm as e:
|
except UnsupportedAlgorithm as e:
|
||||||
raise InvalidAlgorithmError(e)
|
raise InvalidAlgorithmError(e) from e
|
||||||
|
|
||||||
|
if not is_potential_certificate_issuer_private_key(privatekey) or isinstance(
|
||||||
|
privatekey, Ed448PrivateKey
|
||||||
|
):
|
||||||
|
raise InvalidPrivateKeyFileError(
|
||||||
|
f"{privatekey} is not a supported private key type"
|
||||||
|
)
|
||||||
return privatekey
|
return privatekey
|
||||||
|
|
||||||
|
|
||||||
@@ -713,10 +719,10 @@ def load_publickey(
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
publickey_loader = publickey_loaders[key_format]
|
publickey_loader = publickey_loaders[key_format]
|
||||||
except KeyError:
|
except KeyError as e:
|
||||||
raise InvalidKeyFormatError(
|
raise InvalidKeyFormatError(
|
||||||
f"{key_format} is not a valid key format ({','.join(publickey_loaders)})"
|
f"{key_format} is not a valid key format ({','.join(publickey_loaders)})"
|
||||||
)
|
) from e
|
||||||
|
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
raise InvalidPublicKeyFileError(f"No file was found at {path}")
|
raise InvalidPublicKeyFileError(f"No file was found at {path}")
|
||||||
@@ -729,9 +735,9 @@ def load_publickey(
|
|||||||
data=content,
|
data=content,
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise InvalidPublicKeyFileError(e)
|
raise InvalidPublicKeyFileError(e) from e
|
||||||
except UnsupportedAlgorithm as e:
|
except UnsupportedAlgorithm as e:
|
||||||
raise InvalidAlgorithmError(e)
|
raise InvalidAlgorithmError(e) from e
|
||||||
|
|
||||||
return publickey
|
return publickey
|
||||||
|
|
||||||
@@ -749,8 +755,7 @@ def compare_publickeys(pk1: PublicKeyTypes, pk2: PublicKeyTypes) -> bool:
|
|||||||
serialization.Encoding.Raw, serialization.PublicFormat.Raw
|
serialization.Encoding.Raw, serialization.PublicFormat.Raw
|
||||||
)
|
)
|
||||||
return a_bytes == b_bytes
|
return a_bytes == b_bytes
|
||||||
else:
|
return pk1.public_numbers() == pk2.public_numbers() # type: ignore
|
||||||
return pk1.public_numbers() == pk2.public_numbers() # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def compare_encryption_algorithms(
|
def compare_encryption_algorithms(
|
||||||
@@ -761,12 +766,11 @@ def compare_encryption_algorithms(
|
|||||||
ea2, serialization.NoEncryption
|
ea2, serialization.NoEncryption
|
||||||
):
|
):
|
||||||
return True
|
return True
|
||||||
elif isinstance(ea1, serialization.BestAvailableEncryption) and isinstance(
|
if isinstance(ea1, serialization.BestAvailableEncryption) and isinstance(
|
||||||
ea2, serialization.BestAvailableEncryption
|
ea2, serialization.BestAvailableEncryption
|
||||||
):
|
):
|
||||||
return ea1.password == ea2.password
|
return ea1.password == ea2.password
|
||||||
else:
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_encryption_algorithm(
|
def get_encryption_algorithm(
|
||||||
@@ -775,7 +779,7 @@ def get_encryption_algorithm(
|
|||||||
try:
|
try:
|
||||||
return serialization.BestAvailableEncryption(passphrase)
|
return serialization.BestAvailableEncryption(passphrase)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise InvalidPassphraseError(e)
|
raise InvalidPassphraseError(e) from e
|
||||||
|
|
||||||
|
|
||||||
def validate_comment(comment: str) -> None:
|
def validate_comment(comment: str) -> None:
|
||||||
@@ -796,7 +800,7 @@ def extract_comment(path: str | os.PathLike) -> str:
|
|||||||
else:
|
else:
|
||||||
comment = ""
|
comment = ""
|
||||||
except (IOError, OSError) as e:
|
except (IOError, OSError) as e:
|
||||||
raise InvalidPublicKeyFileError(e)
|
raise InvalidPublicKeyFileError(e) from e
|
||||||
|
|
||||||
return comment
|
return comment
|
||||||
|
|
||||||
|
|||||||
@@ -177,10 +177,9 @@ class OpensshParser:
|
|||||||
def _check_position(self, offset: int) -> int:
|
def _check_position(self, offset: int) -> int:
|
||||||
if self._pos + offset > len(self._data):
|
if self._pos + offset > len(self._data):
|
||||||
raise ValueError(f"Insufficient data remaining at position: {self._pos}")
|
raise ValueError(f"Insufficient data remaining at position: {self._pos}")
|
||||||
elif self._pos + offset < 0:
|
if self._pos + offset < 0:
|
||||||
raise ValueError("Position cannot be less than zero.")
|
raise ValueError("Position cannot be less than zero.")
|
||||||
else:
|
return self._pos + offset
|
||||||
return self._pos + offset
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def signature_data(cls, *, signature_string: bytes) -> dict[str, bytes | int]:
|
def signature_data(cls, *, signature_string: bytes) -> dict[str, bytes | int]:
|
||||||
@@ -306,7 +305,9 @@ class _OpensshWriter:
|
|||||||
try:
|
try:
|
||||||
self.string(",".join(value).encode("ASCII"))
|
self.string(",".join(value).encode("ASCII"))
|
||||||
except UnicodeEncodeError as e:
|
except UnicodeEncodeError as e:
|
||||||
raise ValueError(f"Name-list's must consist of US-ASCII characters: {e}")
|
raise ValueError(
|
||||||
|
f"Name-list's must consist of US-ASCII characters: {e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def parse_serial(value: str | bytes) -> int:
|
|||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The {i + 1}{th(i + 1)} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}"
|
f"The {i + 1}{th(i + 1)} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}"
|
||||||
)
|
) from exc
|
||||||
result = (result << 8) | part_value
|
result = (result << 8) | part_value
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|||||||
@@ -102,8 +102,7 @@ def convert_relative_to_datetime(
|
|||||||
|
|
||||||
if parsed_result.group("prefix") == "+":
|
if parsed_result.group("prefix") == "+":
|
||||||
return now + offset
|
return now + offset
|
||||||
else:
|
return now - offset
|
||||||
return now - offset
|
|
||||||
|
|
||||||
|
|
||||||
def get_relative_time_option(
|
def get_relative_time_option(
|
||||||
|
|||||||
@@ -184,25 +184,29 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec()
|
argument_spec = create_default_argspec()
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
terms_agreed=dict(type="bool", default=False),
|
terms_agreed={"type": "bool", "default": False},
|
||||||
state=dict(
|
state={
|
||||||
type="str", required=True, choices=["absent", "present", "changed_key"]
|
"type": "str",
|
||||||
),
|
"required": True,
|
||||||
allow_creation=dict(type="bool", default=True),
|
"choices": ["absent", "present", "changed_key"],
|
||||||
contact=dict(type="list", elements="str", default=[]),
|
},
|
||||||
new_account_key_src=dict(type="path"),
|
allow_creation={"type": "bool", "default": True},
|
||||||
new_account_key_content=dict(type="str", no_log=True),
|
contact={"type": "list", "elements": "str", "default": []},
|
||||||
new_account_key_passphrase=dict(type="str", no_log=True),
|
new_account_key_src={"type": "path"},
|
||||||
external_account_binding=dict(
|
new_account_key_content={"type": "str", "no_log": True},
|
||||||
type="dict",
|
new_account_key_passphrase={"type": "str", "no_log": True},
|
||||||
options=dict(
|
external_account_binding={
|
||||||
kid=dict(type="str", required=True),
|
"type": "dict",
|
||||||
alg=dict(
|
"options": {
|
||||||
type="str", required=True, choices=["HS256", "HS384", "HS512"]
|
"kid": {"type": "str", "required": True},
|
||||||
),
|
"alg": {
|
||||||
key=dict(type="str", required=True, no_log=True),
|
"type": "str",
|
||||||
),
|
"required": True,
|
||||||
),
|
"choices": ["HS256", "HS384", "HS512"],
|
||||||
|
},
|
||||||
|
"key": {"type": "str", "required": True, "no_log": True},
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
mutually_exclusive=[("new_account_key_src", "new_account_key_content")],
|
mutually_exclusive=[("new_account_key_src", "new_account_key_content")],
|
||||||
@@ -254,7 +258,7 @@ def main() -> t.NoReturn:
|
|||||||
if not module.check_mode:
|
if not module.check_mode:
|
||||||
# Deactivate it
|
# Deactivate it
|
||||||
deactivate_payload = {"status": "deactivated"}
|
deactivate_payload = {"status": "deactivated"}
|
||||||
result, info = client.send_signed_request(
|
result, _info = client.send_signed_request(
|
||||||
t.cast(str, client.account_uri),
|
t.cast(str, client.account_uri),
|
||||||
deactivate_payload,
|
deactivate_payload,
|
||||||
error_msg="Failed to deactivate account",
|
error_msg="Failed to deactivate account",
|
||||||
@@ -302,7 +306,7 @@ def main() -> t.NoReturn:
|
|||||||
except KeyParsingError as e:
|
except KeyParsingError as e:
|
||||||
raise ModuleFailException(
|
raise ModuleFailException(
|
||||||
f"Error while parsing new account key: {e.msg}"
|
f"Error while parsing new account key: {e.msg}"
|
||||||
)
|
) from e
|
||||||
# Verify that the account exists and has not been deactivated
|
# Verify that the account exists and has not been deactivated
|
||||||
created, account_data = account.setup_account(allow_creation=False)
|
created, account_data = account.setup_account(allow_creation=False)
|
||||||
if created:
|
if created:
|
||||||
@@ -335,18 +339,18 @@ def main() -> t.NoReturn:
|
|||||||
key_data=new_key_data,
|
key_data=new_key_data,
|
||||||
)
|
)
|
||||||
# Send request and verify result
|
# Send request and verify result
|
||||||
result, info = client.send_signed_request(
|
result, _info = client.send_signed_request(
|
||||||
url,
|
url,
|
||||||
data,
|
data,
|
||||||
error_msg="Failed to rollover account key",
|
error_msg="Failed to rollover account key",
|
||||||
expected_status_codes=[200],
|
expected_status_codes=[200],
|
||||||
)
|
)
|
||||||
if module._diff:
|
if module._diff: # pylint: disable=protected-access
|
||||||
client.account_key_data = new_key_data
|
client.account_key_data = new_key_data
|
||||||
if client.account_jws_header:
|
if client.account_jws_header:
|
||||||
client.account_jws_header["alg"] = new_key_data["alg"]
|
client.account_jws_header["alg"] = new_key_data["alg"]
|
||||||
diff_after = account.get_account_data() or {}
|
diff_after = account.get_account_data() or {}
|
||||||
elif module._diff:
|
elif module._diff: # pylint: disable=protected-access
|
||||||
# Kind of fake diff_after
|
# Kind of fake diff_after
|
||||||
diff_after = dict(diff_before)
|
diff_after = dict(diff_before)
|
||||||
diff_after["public_account_key"] = new_key_data["jwk"]
|
diff_after["public_account_key"] = new_key_data["jwk"]
|
||||||
@@ -355,7 +359,7 @@ def main() -> t.NoReturn:
|
|||||||
"changed": changed,
|
"changed": changed,
|
||||||
"account_uri": client.account_uri,
|
"account_uri": client.account_uri,
|
||||||
}
|
}
|
||||||
if module._diff:
|
if module._diff: # pylint: disable=protected-access
|
||||||
result["diff"] = {
|
result["diff"] = {
|
||||||
"before": diff_before,
|
"before": diff_before,
|
||||||
"after": diff_after,
|
"after": diff_after,
|
||||||
|
|||||||
@@ -273,9 +273,11 @@ def get_order(client: ACMEClient, order_url: str) -> dict[str, t.Any]:
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec()
|
argument_spec = create_default_argspec()
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
retrieve_orders=dict(
|
retrieve_orders={
|
||||||
type="str", default="ignore", choices=["ignore", "url_list", "object_list"]
|
"type": "str",
|
||||||
),
|
"default": "ignore",
|
||||||
|
"choices": ["ignore", "url_list", "object_list"],
|
||||||
|
},
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
backend = create_backend(module, needs_acme_v2=True)
|
backend = create_backend(module, needs_acme_v2=True)
|
||||||
|
|||||||
@@ -109,8 +109,8 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(with_account=False)
|
argument_spec = create_default_argspec(with_account=False)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
certificate_path=dict(type="path"),
|
certificate_path={"type": "path"},
|
||||||
certificate_content=dict(type="str"),
|
certificate_content={"type": "str"},
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
required_one_of=[("certificate_path", "certificate_content")],
|
required_one_of=[("certificate_path", "certificate_content")],
|
||||||
|
|||||||
@@ -578,7 +578,6 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.certificate
|
|||||||
from ansible_collections.community.crypto.plugins.module_utils._acme.challenges import (
|
from ansible_collections.community.crypto.plugins.module_utils._acme.challenges import (
|
||||||
combine_identifier,
|
combine_identifier,
|
||||||
normalize_combined_identifier,
|
normalize_combined_identifier,
|
||||||
split_identifier,
|
|
||||||
wait_for_validation,
|
wait_for_validation,
|
||||||
)
|
)
|
||||||
from ansible_collections.community.crypto.plugins.module_utils._acme.errors import (
|
from ansible_collections.community.crypto.plugins.module_utils._acme.errors import (
|
||||||
@@ -760,7 +759,6 @@ class ACMECertificateClient:
|
|||||||
data: dict[str, t.Any] = {}
|
data: dict[str, t.Any] = {}
|
||||||
data_dns: dict[str, list[str]] = {}
|
data_dns: dict[str, list[str]] = {}
|
||||||
for type_identifier, authz in self.authorizations.items():
|
for type_identifier, authz in self.authorizations.items():
|
||||||
identifier_type, identifier = split_identifier(type_identifier)
|
|
||||||
# Skip valid authentications: their challenges are already valid
|
# Skip valid authentications: their challenges are already valid
|
||||||
# and do not need to be returned
|
# and do not need to be returned
|
||||||
if authz.status == "valid":
|
if authz.status == "valid":
|
||||||
@@ -802,7 +800,7 @@ class ACMECertificateClient:
|
|||||||
|
|
||||||
# Step 2: validate pending challenges
|
# Step 2: validate pending challenges
|
||||||
authzs_to_wait_for = []
|
authzs_to_wait_for = []
|
||||||
for type_identifier, authz in self.authorizations.items():
|
for authz in self.authorizations.values():
|
||||||
if authz.status == "pending":
|
if authz.status == "pending":
|
||||||
if self.challenge is not None:
|
if self.challenge is not None:
|
||||||
authz.call_validate(
|
authz.call_validate(
|
||||||
@@ -951,52 +949,54 @@ def main() -> t.NoReturn:
|
|||||||
argument_spec = create_default_argspec(with_certificate=True)
|
argument_spec = create_default_argspec(with_certificate=True)
|
||||||
argument_spec.argument_spec["csr"]["aliases"] = ["src"]
|
argument_spec.argument_spec["csr"]["aliases"] = ["src"]
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
modify_account=dict(type="bool", default=True),
|
modify_account={"type": "bool", "default": True},
|
||||||
account_email=dict(type="str"),
|
account_email={"type": "str"},
|
||||||
agreement=dict(
|
agreement={
|
||||||
type="str",
|
"type": "str",
|
||||||
removed_in_version="4.0.0",
|
"removed_in_version": "4.0.0",
|
||||||
removed_from_collection="community.crypto",
|
"removed_from_collection": "community.crypto",
|
||||||
),
|
},
|
||||||
terms_agreed=dict(type="bool", default=False),
|
terms_agreed={"type": "bool", "default": False},
|
||||||
challenge=dict(
|
challenge={
|
||||||
type="str",
|
"type": "str",
|
||||||
default="http-01",
|
"default": "http-01",
|
||||||
choices=["http-01", "dns-01", "tls-alpn-01", NO_CHALLENGE],
|
"choices": ["http-01", "dns-01", "tls-alpn-01", NO_CHALLENGE],
|
||||||
),
|
},
|
||||||
data=dict(type="dict"),
|
data={"type": "dict"},
|
||||||
dest=dict(type="path", aliases=["cert"]),
|
dest={"type": "path", "aliases": ["cert"]},
|
||||||
fullchain_dest=dict(type="path", aliases=["fullchain"]),
|
fullchain_dest={"type": "path", "aliases": ["fullchain"]},
|
||||||
chain_dest=dict(type="path", aliases=["chain"]),
|
chain_dest={"type": "path", "aliases": ["chain"]},
|
||||||
remaining_days=dict(type="int", default=10),
|
remaining_days={"type": "int", "default": 10},
|
||||||
deactivate_authzs=dict(type="bool", default=False),
|
deactivate_authzs={"type": "bool", "default": False},
|
||||||
force=dict(type="bool", default=False),
|
force={"type": "bool", "default": False},
|
||||||
retrieve_all_alternates=dict(type="bool", default=False),
|
retrieve_all_alternates={"type": "bool", "default": False},
|
||||||
select_chain=dict(
|
select_chain={
|
||||||
type="list",
|
"type": "list",
|
||||||
elements="dict",
|
"elements": "dict",
|
||||||
options=dict(
|
"options": {
|
||||||
test_certificates=dict(
|
"test_certificates": {
|
||||||
type="str", default="all", choices=["first", "last", "all"]
|
"type": "str",
|
||||||
),
|
"default": "all",
|
||||||
issuer=dict(type="dict"),
|
"choices": ["first", "last", "all"],
|
||||||
subject=dict(type="dict"),
|
},
|
||||||
subject_key_identifier=dict(type="str"),
|
"issuer": {"type": "dict"},
|
||||||
authority_key_identifier=dict(type="str"),
|
"subject": {"type": "dict"},
|
||||||
),
|
"subject_key_identifier": {"type": "str"},
|
||||||
),
|
"authority_key_identifier": {"type": "str"},
|
||||||
include_renewal_cert_id=dict(
|
},
|
||||||
type="str",
|
},
|
||||||
choices=["never", "when_ari_supported", "always"],
|
include_renewal_cert_id={
|
||||||
default="never",
|
"type": "str",
|
||||||
),
|
"choices": ["never", "when_ari_supported", "always"],
|
||||||
profile=dict(type="str"),
|
"default": "never",
|
||||||
order_creation_error_strategy=dict(
|
},
|
||||||
type="str",
|
profile={"type": "str"},
|
||||||
default="auto",
|
order_creation_error_strategy={
|
||||||
choices=["auto", "always", "fail", "retry_without_replaces_cert_id"],
|
"type": "str",
|
||||||
),
|
"default": "auto",
|
||||||
order_creation_max_retries=dict(type="int", default=3),
|
"choices": ["auto", "always", "fail", "retry_without_replaces_cert_id"],
|
||||||
|
},
|
||||||
|
order_creation_max_retries={"type": "int", "default": 3},
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
required_one_of=[
|
required_one_of=[
|
||||||
@@ -1045,9 +1045,9 @@ def main() -> t.NoReturn:
|
|||||||
if module.params["deactivate_authzs"]:
|
if module.params["deactivate_authzs"]:
|
||||||
client.deactivate_authzs()
|
client.deactivate_authzs()
|
||||||
data, data_dns = client.get_challenges_data(first_step=is_first_step)
|
data, data_dns = client.get_challenges_data(first_step=is_first_step)
|
||||||
auths = dict()
|
auths = {}
|
||||||
assert client.authorizations is not None
|
assert client.authorizations is not None
|
||||||
for k, v in client.authorizations.items():
|
for v in client.authorizations.values():
|
||||||
# Remove "type:" from key
|
# Remove "type:" from key
|
||||||
auths[v.identifier] = v.to_json()
|
auths[v.identifier] = v.to_json()
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.orders impo
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec()
|
argument_spec = create_default_argspec()
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
order_uri=dict(type="str", required=True),
|
order_uri={"type": "str", "required": True},
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
|
|
||||||
|
|||||||
@@ -388,15 +388,15 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(with_certificate=True)
|
argument_spec = create_default_argspec(with_certificate=True)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
deactivate_authzs=dict(type="bool", default=True),
|
deactivate_authzs={"type": "bool", "default": True},
|
||||||
replaces_cert_id=dict(type="str"),
|
replaces_cert_id={"type": "str"},
|
||||||
profile=dict(type="str"),
|
profile={"type": "str"},
|
||||||
order_creation_error_strategy=dict(
|
order_creation_error_strategy={
|
||||||
type="str",
|
"type": "str",
|
||||||
default="auto",
|
"default": "auto",
|
||||||
choices=["auto", "always", "fail", "retry_without_replaces_cert_id"],
|
"choices": ["auto", "always", "fail", "retry_without_replaces_cert_id"],
|
||||||
),
|
},
|
||||||
order_creation_max_retries=dict(type="int", default=3),
|
order_creation_max_retries={"type": "int", "default": 3},
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module()
|
module = argument_spec.create_ansible_module()
|
||||||
|
|
||||||
|
|||||||
@@ -340,29 +340,31 @@ if t.TYPE_CHECKING:
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(with_certificate=True)
|
argument_spec = create_default_argspec(with_certificate=True)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
order_uri=dict(type="str", required=True),
|
order_uri={"type": "str", "required": True},
|
||||||
cert_dest=dict(type="path"),
|
cert_dest={"type": "path"},
|
||||||
fullchain_dest=dict(type="path"),
|
fullchain_dest={"type": "path"},
|
||||||
chain_dest=dict(type="path"),
|
chain_dest={"type": "path"},
|
||||||
deactivate_authzs=dict(
|
deactivate_authzs={
|
||||||
type="str",
|
"type": "str",
|
||||||
default="always",
|
"default": "always",
|
||||||
choices=["never", "always", "on_error", "on_success"],
|
"choices": ["never", "always", "on_error", "on_success"],
|
||||||
),
|
},
|
||||||
retrieve_all_alternates=dict(type="bool", default=False),
|
retrieve_all_alternates={"type": "bool", "default": False},
|
||||||
select_chain=dict(
|
select_chain={
|
||||||
type="list",
|
"type": "list",
|
||||||
elements="dict",
|
"elements": "dict",
|
||||||
options=dict(
|
"options": {
|
||||||
test_certificates=dict(
|
"test_certificates": {
|
||||||
type="str", default="all", choices=["first", "last", "all"]
|
"type": "str",
|
||||||
),
|
"default": "all",
|
||||||
issuer=dict(type="dict"),
|
"choices": ["first", "last", "all"],
|
||||||
subject=dict(type="dict"),
|
},
|
||||||
subject_key_identifier=dict(type="str"),
|
"issuer": {"type": "dict"},
|
||||||
authority_key_identifier=dict(type="str"),
|
"subject": {"type": "dict"},
|
||||||
),
|
"subject_key_identifier": {"type": "str"},
|
||||||
),
|
"authority_key_identifier": {"type": "str"},
|
||||||
|
},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module()
|
module = argument_spec.create_ansible_module()
|
||||||
|
|
||||||
@@ -371,7 +373,7 @@ def main() -> t.NoReturn:
|
|||||||
try:
|
try:
|
||||||
client = ACMECertificateClient(module=module, backend=backend)
|
client = ACMECertificateClient(module=module, backend=backend)
|
||||||
select_chain_matcher = client.parse_select_chain(module.params["select_chain"])
|
select_chain_matcher = client.parse_select_chain(module.params["select_chain"])
|
||||||
other = dict()
|
other = {}
|
||||||
done = False
|
done = False
|
||||||
order = None
|
order = None
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(with_certificate=False)
|
argument_spec = create_default_argspec(with_certificate=False)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
order_uri=dict(type="str", required=True),
|
order_uri={"type": "str", "required": True},
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
|
|
||||||
|
|||||||
@@ -252,9 +252,9 @@ if t.TYPE_CHECKING:
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(with_certificate=False)
|
argument_spec = create_default_argspec(with_certificate=False)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
order_uri=dict(type="str", required=True),
|
order_uri={"type": "str", "required": True},
|
||||||
challenge=dict(type="str", choices=["http-01", "dns-01", "tls-alpn-01"]),
|
challenge={"type": "str", "choices": ["http-01", "dns-01", "tls-alpn-01"]},
|
||||||
deactivate_authzs=dict(type="bool", default=True),
|
deactivate_authzs={"type": "bool", "default": True},
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module()
|
module = argument_spec.create_ansible_module()
|
||||||
|
|
||||||
@@ -330,13 +330,13 @@ def main() -> t.NoReturn:
|
|||||||
changed=len(authzs_with_challenges_to_wait_for) > 0,
|
changed=len(authzs_with_challenges_to_wait_for) > 0,
|
||||||
account_uri=client.client.account_uri,
|
account_uri=client.client.account_uri,
|
||||||
validating_challenges=[
|
validating_challenges=[
|
||||||
dict(
|
{
|
||||||
identifier=authz.identifier,
|
"identifier": authz.identifier,
|
||||||
identifier_type=authz.identifier_type,
|
"identifier_type": authz.identifier_type,
|
||||||
authz_url=authz.url,
|
"authz_url": authz.url,
|
||||||
challenge_type=challenge_type,
|
"challenge_type": challenge_type,
|
||||||
challenge_url=challenge.url if challenge else None,
|
"challenge_url": challenge.url if challenge else None,
|
||||||
)
|
}
|
||||||
for authz, challenge_type, challenge in authzs_with_challenges_to_wait_for
|
for authz, challenge_type, challenge in authzs_with_challenges_to_wait_for
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -179,16 +179,18 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.utils impor
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(with_account=False)
|
argument_spec = create_default_argspec(with_account=False)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
certificate_path=dict(type="path"),
|
certificate_path={"type": "path"},
|
||||||
certificate_content=dict(type="str"),
|
certificate_content={"type": "str"},
|
||||||
use_ari=dict(type="bool", default=True),
|
use_ari={"type": "bool", "default": True},
|
||||||
ari_algorithm=dict(
|
ari_algorithm={
|
||||||
type="str", choices=["standard", "start"], default="standard"
|
"type": "str",
|
||||||
),
|
"choices": ["standard", "start"],
|
||||||
remaining_days=dict(type="int"),
|
"default": "standard",
|
||||||
remaining_percentage=dict(type="float"),
|
},
|
||||||
now=dict(type="str"),
|
remaining_days={"type": "int"},
|
||||||
treat_parsing_error_as_non_existing=dict(type="bool", default=False),
|
remaining_percentage={"type": "float"},
|
||||||
|
now={"type": "str"},
|
||||||
|
treat_parsing_error_as_non_existing={"type": "bool", "default": False},
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
mutually_exclusive=[("certificate_path", "certificate_content")],
|
mutually_exclusive=[("certificate_path", "certificate_content")],
|
||||||
@@ -196,13 +198,13 @@ def main() -> t.NoReturn:
|
|||||||
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
module = argument_spec.create_ansible_module(supports_check_mode=True)
|
||||||
backend = create_backend(module, needs_acme_v2=True)
|
backend = create_backend(module, needs_acme_v2=True)
|
||||||
|
|
||||||
result = dict(
|
result = {
|
||||||
changed=False,
|
"changed": False,
|
||||||
msg="The certificate is still valid and no condition was reached",
|
"msg": "The certificate is still valid and no condition was reached",
|
||||||
exists=False,
|
"exists": False,
|
||||||
parsable=False,
|
"parsable": False,
|
||||||
supports_ari=False,
|
"supports_ari": False,
|
||||||
)
|
}
|
||||||
|
|
||||||
def complete(should_renew: bool, **kwargs) -> t.NoReturn:
|
def complete(should_renew: bool, **kwargs) -> t.NoReturn:
|
||||||
result["should_renew"] = should_renew
|
result["should_renew"] = should_renew
|
||||||
|
|||||||
@@ -134,11 +134,11 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.utils impor
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(require_account_key=False)
|
argument_spec = create_default_argspec(require_account_key=False)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
private_key_src=dict(type="path"),
|
private_key_src={"type": "path"},
|
||||||
private_key_content=dict(type="str", no_log=True),
|
private_key_content={"type": "str", "no_log": True},
|
||||||
private_key_passphrase=dict(type="str", no_log=True),
|
private_key_passphrase={"type": "str", "no_log": True},
|
||||||
certificate=dict(type="path", required=True),
|
certificate={"type": "path", "required": True},
|
||||||
revoke_reason=dict(type="int"),
|
revoke_reason={"type": "int"},
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
required_one_of=[
|
required_one_of=[
|
||||||
@@ -186,7 +186,9 @@ def main() -> t.NoReturn:
|
|||||||
passphrase=passphrase,
|
passphrase=passphrase,
|
||||||
)
|
)
|
||||||
except KeyParsingError as e:
|
except KeyParsingError as e:
|
||||||
raise ModuleFailException(f"Error while parsing private key: {e.msg}")
|
raise ModuleFailException(
|
||||||
|
f"Error while parsing private key: {e.msg}"
|
||||||
|
) from e
|
||||||
# Step 2: sign revokation request with private key
|
# Step 2: sign revokation request with private key
|
||||||
jws_header = {
|
jws_header = {
|
||||||
"alg": private_key_data["alg"],
|
"alg": private_key_data["alg"],
|
||||||
|
|||||||
@@ -200,13 +200,13 @@ def encode_octet_string(octet_string: bytes) -> bytes:
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
challenge=dict(type="str", required=True, choices=["tls-alpn-01"]),
|
"challenge": {"type": "str", "required": True, "choices": ["tls-alpn-01"]},
|
||||||
challenge_data=dict(type="dict", required=True),
|
"challenge_data": {"type": "dict", "required": True},
|
||||||
private_key_src=dict(type="path"),
|
"private_key_src": {"type": "path"},
|
||||||
private_key_content=dict(type="str", no_log=True),
|
"private_key_content": {"type": "str", "no_log": True},
|
||||||
private_key_passphrase=dict(type="str", no_log=True),
|
"private_key_passphrase": {"type": "str", "no_log": True},
|
||||||
),
|
},
|
||||||
required_one_of=(["private_key_src", "private_key_content"],),
|
required_one_of=(["private_key_src", "private_key_content"],),
|
||||||
mutually_exclusive=(["private_key_src", "private_key_content"],),
|
mutually_exclusive=(["private_key_src", "private_key_content"],),
|
||||||
)
|
)
|
||||||
@@ -239,7 +239,7 @@ def main() -> t.NoReturn:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ModuleFailException(f"Error while loading private key: {e}")
|
raise ModuleFailException(f"Error while loading private key: {e}") from e
|
||||||
if isinstance(
|
if isinstance(
|
||||||
private_key,
|
private_key,
|
||||||
(
|
(
|
||||||
@@ -317,6 +317,8 @@ def main() -> t.NoReturn:
|
|||||||
private_key,
|
private_key,
|
||||||
cryptography.hazmat.primitives.hashes.SHA256(),
|
cryptography.hazmat.primitives.hashes.SHA256(),
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
raise AssertionError("Can never be reached") # pragma: no cover
|
||||||
|
|
||||||
module.exit_json(
|
module.exit_json(
|
||||||
changed=True,
|
changed=True,
|
||||||
|
|||||||
@@ -240,12 +240,14 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = create_default_argspec(require_account_key=False)
|
argument_spec = create_default_argspec(require_account_key=False)
|
||||||
argument_spec.update_argspec(
|
argument_spec.update_argspec(
|
||||||
url=dict(type="str"),
|
url={"type": "str"},
|
||||||
method=dict(
|
method={
|
||||||
type="str", choices=["get", "post", "directory-only"], default="get"
|
"type": "str",
|
||||||
),
|
"choices": ["get", "post", "directory-only"],
|
||||||
content=dict(type="str"),
|
"default": "get",
|
||||||
fail_on_acme_error=dict(type="bool", default=True),
|
},
|
||||||
|
content={"type": "str"},
|
||||||
|
fail_on_acme_error={"type": "bool", "default": True},
|
||||||
)
|
)
|
||||||
argument_spec.update(
|
argument_spec.update(
|
||||||
required_if=[
|
required_if=[
|
||||||
@@ -263,7 +265,7 @@ def main() -> t.NoReturn:
|
|||||||
try:
|
try:
|
||||||
# Get hold of ACMEClient and ACMEAccount objects (includes directory)
|
# Get hold of ACMEClient and ACMEAccount objects (includes directory)
|
||||||
client = ACMEClient(module=module, backend=backend)
|
client = ACMEClient(module=module, backend=backend)
|
||||||
method = module.params["method"]
|
method: t.Literal["get", "post", "directory-only"] = module.params["method"]
|
||||||
result["directory"] = client.directory.directory
|
result["directory"] = client.directory.directory
|
||||||
# Do we have to do more requests?
|
# Do we have to do more requests?
|
||||||
if method != "directory-only":
|
if method != "directory-only":
|
||||||
@@ -283,12 +285,14 @@ def main() -> t.NoReturn:
|
|||||||
encode_payload=False,
|
encode_payload=False,
|
||||||
fail_on_error=False,
|
fail_on_error=False,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
raise AssertionError("Can never be reached") # pragma: no cover
|
||||||
# Update results
|
# Update results
|
||||||
result.update(
|
result.update(
|
||||||
dict(
|
{
|
||||||
headers=info,
|
"headers": info,
|
||||||
output_text=to_native(data),
|
"output_text": to_native(data),
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
# See if we can parse the result as JSON
|
# See if we can parse the result as JSON
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -328,12 +328,12 @@ def format_cert(cert: Certificate) -> str:
|
|||||||
def check_cycle(
|
def check_cycle(
|
||||||
module: AnsibleModule,
|
module: AnsibleModule,
|
||||||
occured_certificates: set[cryptography.x509.Certificate],
|
occured_certificates: set[cryptography.x509.Certificate],
|
||||||
next: Certificate,
|
next_certificate: Certificate,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Make sure that next is not in occured_certificates so far, and add it.
|
Make sure that next_certificate is not in occured_certificates so far, and add it.
|
||||||
"""
|
"""
|
||||||
next_cert = next.cert
|
next_cert = next_certificate.cert
|
||||||
if next_cert in occured_certificates:
|
if next_cert in occured_certificates:
|
||||||
module.fail_json(msg="Found cycle while building certificate chain")
|
module.fail_json(msg="Found cycle while building certificate chain")
|
||||||
occured_certificates.add(next_cert)
|
occured_certificates.add(next_cert)
|
||||||
@@ -341,11 +341,15 @@ def check_cycle(
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
input_chain=dict(type="str", required=True),
|
"input_chain": {"type": "str", "required": True},
|
||||||
root_certificates=dict(type="list", required=True, elements="path"),
|
"root_certificates": {"type": "list", "required": True, "elements": "path"},
|
||||||
intermediate_certificates=dict(type="list", default=[], elements="path"),
|
"intermediate_certificates": {
|
||||||
),
|
"type": "list",
|
||||||
|
"default": [],
|
||||||
|
"elements": "path",
|
||||||
|
},
|
||||||
|
},
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -382,7 +386,7 @@ def main() -> t.NoReturn:
|
|||||||
# Try to complete chain
|
# Try to complete chain
|
||||||
current: Certificate | None = chain[-1]
|
current: Certificate | None = chain[-1]
|
||||||
completed = []
|
completed = []
|
||||||
occured_certificates = set([cert.cert for cert in chain])
|
occured_certificates = {cert.cert for cert in chain}
|
||||||
if current and current.cert in roots.certificate_by_cert:
|
if current and current.cert in roots.certificate_by_cert:
|
||||||
# Do not try to complete the chain when it is already ending with a root certificate
|
# Do not try to complete the chain when it is already ending with a root certificate
|
||||||
current = None
|
current = None
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ CRYPTOGRAPHY_VERSION: str | None
|
|||||||
CRYPTOGRAPHY_IMP_ERR: str | None
|
CRYPTOGRAPHY_IMP_ERR: str | None
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
|
import cryptography.hazmat.primitives.asymmetric
|
||||||
from cryptography.exceptions import UnsupportedAlgorithm
|
from cryptography.exceptions import UnsupportedAlgorithm
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -216,13 +217,12 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
has_dsa_sign = False
|
has_dsa_sign = False
|
||||||
try:
|
try:
|
||||||
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/
|
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/dsa/
|
||||||
import cryptography.hazmat.primitives.asymmetric.dsa
|
from cryptography.hazmat.primitives.asymmetric import dsa
|
||||||
|
|
||||||
has_dsa = True
|
has_dsa = True
|
||||||
try:
|
try:
|
||||||
# added later in 1.5
|
# added later in 1.5
|
||||||
# pylint: disable-next=pointless-statement
|
dsa.DSAPrivateKey.sign # pylint: disable=pointless-statement
|
||||||
cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign
|
|
||||||
has_dsa_sign = True
|
has_dsa_sign = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -234,13 +234,12 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
has_rsa_sign = False
|
has_rsa_sign = False
|
||||||
try:
|
try:
|
||||||
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
|
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/
|
||||||
import cryptography.hazmat.primitives.asymmetric.rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
|
||||||
has_rsa = True
|
has_rsa = True
|
||||||
try:
|
try:
|
||||||
# added later in 1.4
|
# added later in 1.4
|
||||||
# pylint: disable-next=pointless-statement
|
rsa.RSAPrivateKey.sign # pylint: disable=pointless-statement
|
||||||
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign
|
|
||||||
has_rsa_sign = True
|
has_rsa_sign = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -252,21 +251,17 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
has_ed25519_sign = False
|
has_ed25519_sign = False
|
||||||
try:
|
try:
|
||||||
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/
|
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/
|
||||||
import cryptography.hazmat.primitives.asymmetric.ed25519
|
from cryptography.hazmat.primitives.asymmetric import ed25519
|
||||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
|
|
||||||
Ed25519PrivateKey,
|
|
||||||
)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Ed25519PrivateKey.from_private_bytes(b"")
|
ed25519.Ed25519PrivateKey.from_private_bytes(b"")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
has_ed25519 = True
|
has_ed25519 = True
|
||||||
try:
|
try:
|
||||||
# added with the primitive in 2.6
|
# added with the primitive in 2.6
|
||||||
# pylint: disable-next=pointless-statement
|
ed25519.Ed25519PrivateKey.sign # pylint: disable=pointless-statement
|
||||||
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.sign
|
|
||||||
has_ed25519_sign = True
|
has_ed25519_sign = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -278,19 +273,17 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
has_ed448_sign = False
|
has_ed448_sign = False
|
||||||
try:
|
try:
|
||||||
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/
|
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/
|
||||||
import cryptography.hazmat.primitives.asymmetric.ed448
|
from cryptography.hazmat.primitives.asymmetric import ed448
|
||||||
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
Ed448PrivateKey.from_private_bytes(b"")
|
ed448.Ed448PrivateKey.from_private_bytes(b"")
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
has_ed448 = True
|
has_ed448 = True
|
||||||
try:
|
try:
|
||||||
# added with the primitive in 2.6
|
# added with the primitive in 2.6
|
||||||
# pylint: disable-next=pointless-statement
|
ed448.Ed448PrivateKey.sign # pylint: disable=pointless-statement
|
||||||
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.sign
|
|
||||||
has_ed448_sign = True
|
has_ed448_sign = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -302,24 +295,21 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
has_x25519_full = False
|
has_x25519_full = False
|
||||||
try:
|
try:
|
||||||
# added in 2.0 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x25519/
|
# added in 2.0 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x25519/
|
||||||
import cryptography.hazmat.primitives.asymmetric.x25519
|
from cryptography.hazmat.primitives.asymmetric import x25519
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# added later in 2.5
|
# added later in 2.5
|
||||||
# pylint: disable-next=pointless-statement
|
x25519.X25519PrivateKey.private_bytes # pylint: disable=pointless-statement
|
||||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.private_bytes
|
|
||||||
full = True
|
full = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
full = False
|
full = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if full:
|
if full:
|
||||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
|
x25519.X25519PrivateKey.from_private_bytes(b"")
|
||||||
b""
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
# Some versions do not support serialization and deserialization - use generate() instead
|
# Some versions do not support serialization and deserialization - use generate() instead
|
||||||
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
|
x25519.X25519PrivateKey.generate()
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -332,12 +322,10 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
has_x448 = False
|
has_x448 = False
|
||||||
try:
|
try:
|
||||||
# added in 2.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/
|
# added in 2.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/x448/
|
||||||
import cryptography.hazmat.primitives.asymmetric.x448
|
from cryptography.hazmat.primitives.asymmetric import x448
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey
|
x448.X448PrivateKey.from_private_bytes(b"")
|
||||||
|
|
||||||
X448PrivateKey.from_private_bytes(b"")
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -351,13 +339,12 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
curves = []
|
curves = []
|
||||||
try:
|
try:
|
||||||
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
|
# added in 0.5 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ec/
|
||||||
import cryptography.hazmat.primitives.asymmetric.ec
|
from cryptography.hazmat.primitives.asymmetric import ec
|
||||||
|
|
||||||
has_ec = True
|
has_ec = True
|
||||||
try:
|
try:
|
||||||
# added later in 1.5
|
# added later in 1.5
|
||||||
# pylint: disable-next=pointless-statement
|
ec.EllipticCurvePrivateKey.sign # pylint: disable=pointless-statement
|
||||||
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign
|
|
||||||
has_ec_sign = True
|
has_ec_sign = True
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
@@ -365,14 +352,10 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
for curve_name, constructor_name in CURVES:
|
for curve_name, constructor_name in CURVES:
|
||||||
ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(
|
ecclass = ec.__dict__.get(constructor_name)
|
||||||
constructor_name
|
|
||||||
)
|
|
||||||
if ecclass:
|
if ecclass:
|
||||||
try:
|
try:
|
||||||
cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(
|
ec.generate_private_key(curve=ecclass())
|
||||||
curve=ecclass()
|
|
||||||
)
|
|
||||||
curves.append(curve_name)
|
curves.append(curve_name)
|
||||||
except UnsupportedAlgorithm:
|
except UnsupportedAlgorithm:
|
||||||
pass
|
pass
|
||||||
@@ -419,7 +402,7 @@ def add_openssl_information(module: AnsibleModule) -> dict[str, t.Any]:
|
|||||||
}
|
}
|
||||||
result["openssl"] = openssl_result
|
result["openssl"] = openssl_result
|
||||||
|
|
||||||
rc, out, err = module.run_command([openssl_binary, "version"])
|
rc, out, _err = module.run_command([openssl_binary, "version"])
|
||||||
if rc == 0:
|
if rc == 0:
|
||||||
openssl_result["version_output"] = out
|
openssl_result["version_output"] = out
|
||||||
parts = out.split(None, 2)
|
parts = out.split(None, 2)
|
||||||
|
|||||||
@@ -786,11 +786,7 @@ class EcsCertificate:
|
|||||||
self.set_cert_details(module)
|
self.set_cert_details(module)
|
||||||
assert self.cert_details is not None
|
assert self.cert_details is not None
|
||||||
|
|
||||||
if (
|
if self.cert_status in ("EXPIRED", "SUSPENDED", "REVOKED"):
|
||||||
self.cert_status == "EXPIRED"
|
|
||||||
or self.cert_status == "SUSPENDED"
|
|
||||||
or self.cert_status == "REVOKED"
|
|
||||||
):
|
|
||||||
return False
|
return False
|
||||||
if self.cert_days < module.params["remaining_days"]:
|
if self.cert_days < module.params["remaining_days"]:
|
||||||
return False
|
return False
|
||||||
@@ -803,7 +799,7 @@ class EcsCertificate:
|
|||||||
|
|
||||||
# Read the CSR contents
|
# Read the CSR contents
|
||||||
if self.csr and os.path.exists(self.csr):
|
if self.csr and os.path.exists(self.csr):
|
||||||
with open(self.csr, "r") as csr_file:
|
with open(self.csr, "r", encoding="utf-8") as csr_file:
|
||||||
body["csr"] = csr_file.read()
|
body["csr"] = csr_file.read()
|
||||||
|
|
||||||
# Check if the path is already a cert
|
# Check if the path is already a cert
|
||||||
@@ -849,6 +845,8 @@ class EcsCertificate:
|
|||||||
result = self.ecs_client.ReissueCertRequest( # type: ignore[attr-defined] # pylint: disable=no-member
|
result = self.ecs_client.ReissueCertRequest( # type: ignore[attr-defined] # pylint: disable=no-member
|
||||||
trackingId=self.tracking_id, Body=body
|
trackingId=self.tracking_id, Body=body
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
raise AssertionError("Can never be reached") # pragma: no cover
|
||||||
self.tracking_id = result.get("trackingId")
|
self.tracking_id = result.get("trackingId")
|
||||||
self.set_cert_details(module)
|
self.set_cert_details(module)
|
||||||
assert self.cert_details is not None
|
assert self.cert_details is not None
|
||||||
@@ -914,61 +912,61 @@ class EcsCertificate:
|
|||||||
|
|
||||||
|
|
||||||
def custom_fields_spec() -> dict[str, dict[str, str]]:
|
def custom_fields_spec() -> dict[str, dict[str, str]]:
|
||||||
return dict(
|
return {
|
||||||
text1=dict(type="str"),
|
"text1": {"type": "str"},
|
||||||
text2=dict(type="str"),
|
"text2": {"type": "str"},
|
||||||
text3=dict(type="str"),
|
"text3": {"type": "str"},
|
||||||
text4=dict(type="str"),
|
"text4": {"type": "str"},
|
||||||
text5=dict(type="str"),
|
"text5": {"type": "str"},
|
||||||
text6=dict(type="str"),
|
"text6": {"type": "str"},
|
||||||
text7=dict(type="str"),
|
"text7": {"type": "str"},
|
||||||
text8=dict(type="str"),
|
"text8": {"type": "str"},
|
||||||
text9=dict(type="str"),
|
"text9": {"type": "str"},
|
||||||
text10=dict(type="str"),
|
"text10": {"type": "str"},
|
||||||
text11=dict(type="str"),
|
"text11": {"type": "str"},
|
||||||
text12=dict(type="str"),
|
"text12": {"type": "str"},
|
||||||
text13=dict(type="str"),
|
"text13": {"type": "str"},
|
||||||
text14=dict(type="str"),
|
"text14": {"type": "str"},
|
||||||
text15=dict(type="str"),
|
"text15": {"type": "str"},
|
||||||
number1=dict(type="float"),
|
"number1": {"type": "float"},
|
||||||
number2=dict(type="float"),
|
"number2": {"type": "float"},
|
||||||
number3=dict(type="float"),
|
"number3": {"type": "float"},
|
||||||
number4=dict(type="float"),
|
"number4": {"type": "float"},
|
||||||
number5=dict(type="float"),
|
"number5": {"type": "float"},
|
||||||
date1=dict(type="str"),
|
"date1": {"type": "str"},
|
||||||
date2=dict(type="str"),
|
"date2": {"type": "str"},
|
||||||
date3=dict(type="str"),
|
"date3": {"type": "str"},
|
||||||
date4=dict(type="str"),
|
"date4": {"type": "str"},
|
||||||
date5=dict(type="str"),
|
"date5": {"type": "str"},
|
||||||
email1=dict(type="str"),
|
"email1": {"type": "str"},
|
||||||
email2=dict(type="str"),
|
"email2": {"type": "str"},
|
||||||
email3=dict(type="str"),
|
"email3": {"type": "str"},
|
||||||
email4=dict(type="str"),
|
"email4": {"type": "str"},
|
||||||
email5=dict(type="str"),
|
"email5": {"type": "str"},
|
||||||
dropdown1=dict(type="str"),
|
"dropdown1": {"type": "str"},
|
||||||
dropdown2=dict(type="str"),
|
"dropdown2": {"type": "str"},
|
||||||
dropdown3=dict(type="str"),
|
"dropdown3": {"type": "str"},
|
||||||
dropdown4=dict(type="str"),
|
"dropdown4": {"type": "str"},
|
||||||
dropdown5=dict(type="str"),
|
"dropdown5": {"type": "str"},
|
||||||
)
|
}
|
||||||
|
|
||||||
|
|
||||||
def ecs_certificate_argument_spec() -> dict[str, dict[str, t.Any]]:
|
def ecs_certificate_argument_spec() -> dict[str, dict[str, t.Any]]:
|
||||||
return dict(
|
return {
|
||||||
backup=dict(type="bool", default=False),
|
"backup": {"type": "bool", "default": False},
|
||||||
force=dict(type="bool", default=False),
|
"force": {"type": "bool", "default": False},
|
||||||
path=dict(type="path", required=True),
|
"path": {"type": "path", "required": True},
|
||||||
full_chain_path=dict(type="path"),
|
"full_chain_path": {"type": "path"},
|
||||||
tracking_id=dict(type="int"),
|
"tracking_id": {"type": "int"},
|
||||||
remaining_days=dict(type="int", default=30),
|
"remaining_days": {"type": "int", "default": 30},
|
||||||
request_type=dict(
|
"request_type": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="new",
|
"default": "new",
|
||||||
choices=["new", "renew", "reissue", "validate_only"],
|
"choices": ["new", "renew", "reissue", "validate_only"],
|
||||||
),
|
},
|
||||||
cert_type=dict(
|
"cert_type": {
|
||||||
type="str",
|
"type": "str",
|
||||||
choices=[
|
"choices": [
|
||||||
"STANDARD_SSL",
|
"STANDARD_SSL",
|
||||||
"ADVANTAGE_SSL",
|
"ADVANTAGE_SSL",
|
||||||
"UC_SSL",
|
"UC_SSL",
|
||||||
@@ -984,26 +982,27 @@ def ecs_certificate_argument_spec() -> dict[str, dict[str, t.Any]]:
|
|||||||
"CDS_ENT_PRO",
|
"CDS_ENT_PRO",
|
||||||
"SMIME_ENT",
|
"SMIME_ENT",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
csr=dict(type="str"),
|
"csr": {"type": "str"},
|
||||||
subject_alt_name=dict(type="list", elements="str"),
|
"subject_alt_name": {"type": "list", "elements": "str"},
|
||||||
eku=dict(
|
"eku": {
|
||||||
type="str", choices=["SERVER_AUTH", "CLIENT_AUTH", "SERVER_AND_CLIENT_AUTH"]
|
"type": "str",
|
||||||
),
|
"choices": ["SERVER_AUTH", "CLIENT_AUTH", "SERVER_AND_CLIENT_AUTH"],
|
||||||
ct_log=dict(type="bool"),
|
},
|
||||||
client_id=dict(type="int", default=1),
|
"ct_log": {"type": "bool"},
|
||||||
org=dict(type="str"),
|
"client_id": {"type": "int", "default": 1},
|
||||||
ou=dict(type="list", elements="str"),
|
"org": {"type": "str"},
|
||||||
end_user_key_storage_agreement=dict(type="bool"),
|
"ou": {"type": "list", "elements": "str"},
|
||||||
tracking_info=dict(type="str"),
|
"end_user_key_storage_agreement": {"type": "bool"},
|
||||||
requester_name=dict(type="str", required=True),
|
"tracking_info": {"type": "str"},
|
||||||
requester_email=dict(type="str", required=True),
|
"requester_name": {"type": "str", "required": True},
|
||||||
requester_phone=dict(type="str", required=True),
|
"requester_email": {"type": "str", "required": True},
|
||||||
additional_emails=dict(type="list", elements="str"),
|
"requester_phone": {"type": "str", "required": True},
|
||||||
custom_fields=dict(type="dict", default=None, options=custom_fields_spec()),
|
"additional_emails": {"type": "list", "elements": "str"},
|
||||||
cert_expiry=dict(type="str"),
|
"custom_fields": {"type": "dict", "options": custom_fields_spec()},
|
||||||
cert_lifetime=dict(type="str", choices=["P1Y", "P2Y", "P3Y"]),
|
"cert_expiry": {"type": "str"},
|
||||||
)
|
"cert_lifetime": {"type": "str", "choices": ["P1Y", "P2Y", "P3Y"]},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
|
|||||||
@@ -268,6 +268,7 @@ class EcsDomain:
|
|||||||
# method of the domain, we'll use module.params when requesting a new
|
# method of the domain, we'll use module.params when requesting a new
|
||||||
# one, in case the verification method has changed.
|
# one, in case the verification method has changed.
|
||||||
self.verification_method = None
|
self.verification_method = None
|
||||||
|
self.client_id: str | None = None
|
||||||
|
|
||||||
# Instantiate the ECS client and then try a no-op connection to verify credentials are valid
|
# Instantiate the ECS client and then try a no-op connection to verify credentials are valid
|
||||||
try:
|
try:
|
||||||
@@ -321,18 +322,15 @@ class EcsDomain:
|
|||||||
clientId=module.params["client_id"], domain=module.params["domain_name"]
|
clientId=module.params["client_id"], domain=module.params["domain_name"]
|
||||||
)
|
)
|
||||||
self.set_domain_details(domain_details)
|
self.set_domain_details(domain_details)
|
||||||
if (
|
if self.domain_status not in (
|
||||||
self.domain_status != "APPROVED"
|
"APPROVED",
|
||||||
and self.domain_status != "INITIAL_VERIFICATION"
|
"INITIAL_VERIFICATION",
|
||||||
and self.domain_status != "RE_VERIFICATION"
|
"RE_VERIFICATION",
|
||||||
):
|
):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If domain verification is in process, we want to return the random values and treat it as a valid.
|
# If domain verification is in process, we want to return the random values and treat it as a valid.
|
||||||
if (
|
if self.domain_status in ("INITIAL_VERIFICATION", "RE_VERIFICATION"):
|
||||||
self.domain_status == "INITIAL_VERIFICATION"
|
|
||||||
or self.domain_status == "RE_VERIFICATION"
|
|
||||||
):
|
|
||||||
# Unless the verification method has changed, in which case we need to do a reverify request.
|
# Unless the verification method has changed, in which case we need to do a reverify request.
|
||||||
if self.verification_method != module.params["verification_method"]:
|
if self.verification_method != module.params["verification_method"]:
|
||||||
return False
|
return False
|
||||||
@@ -383,7 +381,7 @@ class EcsDomain:
|
|||||||
module.params["verification_method"] == "dns"
|
module.params["verification_method"] == "dns"
|
||||||
or module.params["verification_method"] == "web_server"
|
or module.params["verification_method"] == "web_server"
|
||||||
):
|
):
|
||||||
for i in range(4):
|
for _i in range(4):
|
||||||
# Check both that random values are now available, and that they're different than were populated by previous 'check'
|
# Check both that random values are now available, and that they're different than were populated by previous 'check'
|
||||||
if module.params["verification_method"] == "dns":
|
if module.params["verification_method"] == "dns":
|
||||||
if (
|
if (
|
||||||
@@ -445,14 +443,16 @@ class EcsDomain:
|
|||||||
|
|
||||||
|
|
||||||
def ecs_domain_argument_spec() -> dict[str, dict[str, t.Any]]:
|
def ecs_domain_argument_spec() -> dict[str, dict[str, t.Any]]:
|
||||||
return dict(
|
return {
|
||||||
client_id=dict(type="int", default=1),
|
"client_id": {"type": "int", "default": 1},
|
||||||
domain_name=dict(type="str", required=True),
|
"domain_name": {"type": "str", "required": True},
|
||||||
verification_method=dict(
|
"verification_method": {
|
||||||
type="str", required=True, choices=["dns", "email", "manual", "web_server"]
|
"type": "str",
|
||||||
),
|
"required": True,
|
||||||
verification_email=dict(type="str"),
|
"choices": ["dns", "email", "manual", "web_server"],
|
||||||
)
|
},
|
||||||
|
"verification_email": {"type": "str"},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
|
|||||||
@@ -324,23 +324,25 @@ def send_starttls_packet(sock: socket, server_type: t.Literal["mysql"]) -> None:
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
ca_cert=dict(type="path"),
|
"ca_cert": {"type": "path"},
|
||||||
host=dict(type="str", required=True),
|
"host": {"type": "str", "required": True},
|
||||||
port=dict(type="int", required=True),
|
"port": {"type": "int", "required": True},
|
||||||
proxy_host=dict(type="str"),
|
"proxy_host": {"type": "str"},
|
||||||
proxy_port=dict(type="int", default=8080),
|
"proxy_port": {"type": "int", "default": 8080},
|
||||||
server_name=dict(type="str"),
|
"server_name": {"type": "str"},
|
||||||
timeout=dict(type="int", default=10),
|
"timeout": {"type": "int", "default": 10},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", choices=["auto", "cryptography"], default="auto"
|
"type": "str",
|
||||||
),
|
"default": "auto",
|
||||||
starttls=dict(type="str", choices=["mysql"]),
|
"choices": ["auto", "cryptography"],
|
||||||
ciphers=dict(type="list", elements="str"),
|
},
|
||||||
asn1_base64=dict(type="bool", default=True),
|
"starttls": {"type": "str", "choices": ["mysql"]},
|
||||||
tls_ctx_options=dict(type="list", elements="raw"),
|
"ciphers": {"type": "list", "elements": "str"},
|
||||||
get_certificate_chain=dict(type="bool", default=False),
|
"asn1_base64": {"type": "bool", "default": True},
|
||||||
),
|
"tls_ctx_options": {"type": "list", "elements": "raw"},
|
||||||
|
"get_certificate_chain": {"type": "bool", "default": False},
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
ca_cert: str | None = module.params.get("ca_cert")
|
ca_cert: str | None = module.params.get("ca_cert")
|
||||||
@@ -444,7 +446,8 @@ def main() -> t.NoReturn:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Add the int value of the item to ctx options
|
# Add the int value of the item to ctx options
|
||||||
ctx.options |= tls_ctx_option_int
|
# (pylint does not yet notice that module.fail_json cannot return)
|
||||||
|
ctx.options |= tls_ctx_option_int # pylint: disable=possibly-used-before-assignment
|
||||||
except Exception:
|
except Exception:
|
||||||
module.fail_json(
|
module.fail_json(
|
||||||
msg=f"Failed to add {tls_ctx_option_str or tls_ctx_option_int} to CTX options"
|
msg=f"Failed to add {tls_ctx_option_str or tls_ctx_option_int} to CTX options"
|
||||||
@@ -465,9 +468,16 @@ def main() -> t.NoReturn:
|
|||||||
def _convert_chain(chain):
|
def _convert_chain(chain):
|
||||||
if not chain:
|
if not chain:
|
||||||
return []
|
return []
|
||||||
return [c.public_bytes(ssl._ssl.ENCODING_DER) for c in chain]
|
return [
|
||||||
|
c.public_bytes(
|
||||||
|
ssl._ssl.ENCODING_DER # pylint: disable=protected-access
|
||||||
|
)
|
||||||
|
for c in chain
|
||||||
|
]
|
||||||
|
|
||||||
ssl_obj = tls_sock._sslobj # This is of type ssl._ssl._SSLSocket
|
ssl_obj = (
|
||||||
|
tls_sock._sslobj # pylint: disable=protected-access
|
||||||
|
) # This is of type ssl._ssl._SSLSocket
|
||||||
verified_der_chain = _convert_chain(ssl_obj.get_verified_chain())
|
verified_der_chain = _convert_chain(ssl_obj.get_verified_chain())
|
||||||
unverified_der_chain = _convert_chain(ssl_obj.get_unverified_chain())
|
unverified_der_chain = _convert_chain(ssl_obj.get_unverified_chain())
|
||||||
else:
|
else:
|
||||||
@@ -482,7 +492,9 @@ def main() -> t.NoReturn:
|
|||||||
(
|
(
|
||||||
c
|
c
|
||||||
if isinstance(c, bytes)
|
if isinstance(c, bytes)
|
||||||
else c.public_bytes(ssl._ssl.ENCODING_DER)
|
else c.public_bytes(
|
||||||
|
ssl._ssl.ENCODING_DER # pylint: disable=protected-access
|
||||||
|
)
|
||||||
)
|
)
|
||||||
for c in chain
|
for c in chain
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -500,21 +500,21 @@ class Handler:
|
|||||||
|
|
||||||
def get_device_by_uuid(self, uuid: str | None) -> str | None:
|
def get_device_by_uuid(self, uuid: str | None) -> str | None:
|
||||||
"""Returns the device that holds UUID passed by user"""
|
"""Returns the device that holds UUID passed by user"""
|
||||||
self._blkid_bin = self._module.get_bin_path("blkid", True)
|
blkid_bin = self._module.get_bin_path("blkid", True)
|
||||||
if uuid is None:
|
if uuid is None:
|
||||||
return None
|
return None
|
||||||
rc, stdout, dummy = self._run_command([self._blkid_bin, "--uuid", uuid])
|
rc, stdout, dummy = self._run_command([blkid_bin, "--uuid", uuid])
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return None
|
return None
|
||||||
return stdout.strip()
|
return stdout.strip()
|
||||||
|
|
||||||
def get_device_by_label(self, label: str) -> str | None:
|
def get_device_by_label(self, label: str) -> str | None:
|
||||||
"""Returns the device that holds label passed by user"""
|
"""Returns the device that holds label passed by user"""
|
||||||
self._blkid_bin = self._module.get_bin_path("blkid", True)
|
blkid_bin = self._module.get_bin_path("blkid", True)
|
||||||
label = self._module.params["label"]
|
label = self._module.params["label"]
|
||||||
if label is None:
|
if label is None:
|
||||||
return None
|
return None
|
||||||
rc, stdout, dummy = self._run_command([self._blkid_bin, "--label", label])
|
rc, stdout, dummy = self._run_command([blkid_bin, "--label", label])
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
return None
|
return None
|
||||||
return stdout.strip()
|
return stdout.strip()
|
||||||
@@ -536,7 +536,7 @@ class Handler:
|
|||||||
class CryptHandler(Handler):
|
class CryptHandler(Handler):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(CryptHandler, self).__init__(module)
|
super().__init__(module)
|
||||||
self._cryptsetup_bin = self._module.get_bin_path("cryptsetup", True)
|
self._cryptsetup_bin = self._module.get_bin_path("cryptsetup", True)
|
||||||
|
|
||||||
def get_container_name_by_device(self, device: str) -> str | None:
|
def get_container_name_by_device(self, device: str) -> str | None:
|
||||||
@@ -722,7 +722,7 @@ class CryptHandler(Handler):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"Error while wiping LUKS container signatures for {device}: {exc}"
|
f"Error while wiping LUKS container signatures for {device}: {exc}"
|
||||||
)
|
) from exc
|
||||||
|
|
||||||
def run_luks_add_key(
|
def run_luks_add_key(
|
||||||
self,
|
self,
|
||||||
@@ -869,7 +869,7 @@ class CryptHandler(Handler):
|
|||||||
class ConditionsHandler(Handler):
|
class ConditionsHandler(Handler):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule, crypthandler: CryptHandler) -> None:
|
def __init__(self, module: AnsibleModule, crypthandler: CryptHandler) -> None:
|
||||||
super(ConditionsHandler, self).__init__(module)
|
super().__init__(module)
|
||||||
self._crypthandler = crypthandler
|
self._crypthandler = crypthandler
|
||||||
self.device = self.get_device_name()
|
self.device = self.get_device_name()
|
||||||
|
|
||||||
@@ -953,6 +953,7 @@ class ConditionsHandler(Handler):
|
|||||||
) or self._module.params["state"] != "closed":
|
) or self._module.params["state"] != "closed":
|
||||||
# conditions for close not fulfilled
|
# conditions for close not fulfilled
|
||||||
return False
|
return False
|
||||||
|
luks_is_open = False
|
||||||
|
|
||||||
if self.device is not None:
|
if self.device is not None:
|
||||||
name = self._crypthandler.get_container_name_by_device(self.device)
|
name = self._crypthandler.get_container_name_by_device(self.device)
|
||||||
@@ -1084,52 +1085,58 @@ class ConditionsHandler(Handler):
|
|||||||
|
|
||||||
def run_module() -> t.NoReturn:
|
def run_module() -> t.NoReturn:
|
||||||
# available arguments/parameters that a user can pass
|
# available arguments/parameters that a user can pass
|
||||||
module_args = dict(
|
module_args = {
|
||||||
state=dict(
|
"state": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="present",
|
"default": "present",
|
||||||
choices=["present", "absent", "opened", "closed"],
|
"choices": ["present", "absent", "opened", "closed"],
|
||||||
),
|
},
|
||||||
device=dict(type="str"),
|
"device": {"type": "str"},
|
||||||
name=dict(type="str"),
|
"name": {"type": "str"},
|
||||||
keyfile=dict(type="path"),
|
"keyfile": {"type": "path"},
|
||||||
new_keyfile=dict(type="path"),
|
"new_keyfile": {"type": "path"},
|
||||||
remove_keyfile=dict(type="path"),
|
"remove_keyfile": {"type": "path"},
|
||||||
passphrase=dict(type="str", no_log=True),
|
"passphrase": {"type": "str", "no_log": True},
|
||||||
new_passphrase=dict(type="str", no_log=True),
|
"new_passphrase": {"type": "str", "no_log": True},
|
||||||
remove_passphrase=dict(type="str", no_log=True),
|
"remove_passphrase": {"type": "str", "no_log": True},
|
||||||
passphrase_encoding=dict(
|
"passphrase_encoding": {
|
||||||
type="str", default="text", choices=["text", "base64"], no_log=False
|
"type": "str",
|
||||||
),
|
"default": "text",
|
||||||
keyslot=dict(type="int", no_log=False),
|
"choices": ["text", "base64"],
|
||||||
new_keyslot=dict(type="int", no_log=False),
|
"no_log": False,
|
||||||
remove_keyslot=dict(type="int", no_log=False),
|
},
|
||||||
force_remove_last_key=dict(type="bool", default=False),
|
"keyslot": {"type": "int", "no_log": False},
|
||||||
keysize=dict(type="int"),
|
"new_keyslot": {"type": "int", "no_log": False},
|
||||||
label=dict(type="str"),
|
"remove_keyslot": {"type": "int", "no_log": False},
|
||||||
uuid=dict(type="str"),
|
"force_remove_last_key": {"type": "bool", "default": False},
|
||||||
type=dict(type="str", choices=["luks1", "luks2"]),
|
"keysize": {"type": "int"},
|
||||||
cipher=dict(type="str"),
|
"label": {"type": "str"},
|
||||||
hash=dict(type="str"),
|
"uuid": {"type": "str"},
|
||||||
pbkdf=dict(
|
"type": {"type": "str", "choices": ["luks1", "luks2"]},
|
||||||
type="dict",
|
"cipher": {"type": "str"},
|
||||||
options=dict(
|
"hash": {"type": "str"},
|
||||||
iteration_time=dict(type="float"),
|
"pbkdf": {
|
||||||
iteration_count=dict(type="int"),
|
"type": "dict",
|
||||||
algorithm=dict(type="str", choices=["argon2i", "argon2id", "pbkdf2"]),
|
"options": {
|
||||||
memory=dict(type="int"),
|
"iteration_time": {"type": "float"},
|
||||||
parallel=dict(type="int"),
|
"iteration_count": {"type": "int"},
|
||||||
),
|
"algorithm": {
|
||||||
mutually_exclusive=[("iteration_time", "iteration_count")],
|
"type": "str",
|
||||||
),
|
"choices": ["argon2i", "argon2id", "pbkdf2"],
|
||||||
sector_size=dict(type="int"),
|
},
|
||||||
perf_same_cpu_crypt=dict(type="bool", default=False),
|
"memory": {"type": "int"},
|
||||||
perf_submit_from_crypt_cpus=dict(type="bool", default=False),
|
"parallel": {"type": "int"},
|
||||||
perf_no_read_workqueue=dict(type="bool", default=False),
|
},
|
||||||
perf_no_write_workqueue=dict(type="bool", default=False),
|
"mutually_exclusive": [("iteration_time", "iteration_count")],
|
||||||
persistent=dict(type="bool", default=False),
|
},
|
||||||
allow_discards=dict(type="bool", default=False),
|
"sector_size": {"type": "int"},
|
||||||
)
|
"perf_same_cpu_crypt": {"type": "bool", "default": False},
|
||||||
|
"perf_submit_from_crypt_cpus": {"type": "bool", "default": False},
|
||||||
|
"perf_no_read_workqueue": {"type": "bool", "default": False},
|
||||||
|
"perf_no_write_workqueue": {"type": "bool", "default": False},
|
||||||
|
"persistent": {"type": "bool", "default": False},
|
||||||
|
"allow_discards": {"type": "bool", "default": False},
|
||||||
|
}
|
||||||
|
|
||||||
mutually_exclusive = [
|
mutually_exclusive = [
|
||||||
("keyfile", "passphrase"),
|
("keyfile", "passphrase"),
|
||||||
@@ -1145,9 +1152,12 @@ def run_module() -> t.NoReturn:
|
|||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
mutually_exclusive=mutually_exclusive,
|
mutually_exclusive=mutually_exclusive,
|
||||||
)
|
)
|
||||||
module.run_command_environ_update = dict(
|
module.run_command_environ_update = {
|
||||||
LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
|
"LANG": "C",
|
||||||
)
|
"LC_ALL": "C",
|
||||||
|
"LC_MESSAGES": "C",
|
||||||
|
"LC_CTYPE": "C",
|
||||||
|
}
|
||||||
|
|
||||||
if module.params["device"] is not None:
|
if module.params["device"] is not None:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -304,7 +304,7 @@ from ansible_collections.community.crypto.plugins.module_utils._version import (
|
|||||||
|
|
||||||
class Certificate(OpensshModule):
|
class Certificate(OpensshModule):
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(Certificate, self).__init__(module=module)
|
super().__init__(module=module)
|
||||||
self.ssh_keygen = KeygenCommand(self.module)
|
self.ssh_keygen = KeygenCommand(self.module)
|
||||||
|
|
||||||
self.identifier: str = self.module.params["identifier"] or ""
|
self.identifier: str = self.module.params["identifier"] or ""
|
||||||
@@ -406,19 +406,18 @@ class Certificate(OpensshModule):
|
|||||||
def _should_generate(self) -> bool:
|
def _should_generate(self) -> bool:
|
||||||
if self.regenerate == "never":
|
if self.regenerate == "never":
|
||||||
return self.original_data is None
|
return self.original_data is None
|
||||||
elif self.regenerate == "fail":
|
if self.regenerate == "fail":
|
||||||
if self.original_data and not self._is_fully_valid():
|
if self.original_data and not self._is_fully_valid():
|
||||||
self.module.fail_json(
|
self.module.fail_json(
|
||||||
msg="Certificate does not match the provided options.",
|
msg="Certificate does not match the provided options.",
|
||||||
cert=get_cert_dict(self.original_data),
|
cert=get_cert_dict(self.original_data),
|
||||||
)
|
)
|
||||||
return self.original_data is None
|
return self.original_data is None
|
||||||
elif self.regenerate == "partial_idempotence":
|
if self.regenerate == "partial_idempotence":
|
||||||
return self.original_data is None or not self._is_partially_valid()
|
return self.original_data is None or not self._is_partially_valid()
|
||||||
elif self.regenerate == "full_idempotence":
|
if self.regenerate == "full_idempotence":
|
||||||
return self.original_data is None or not self._is_fully_valid()
|
return self.original_data is None or not self._is_fully_valid()
|
||||||
else:
|
return True
|
||||||
return True
|
|
||||||
|
|
||||||
def _is_fully_valid(self) -> bool:
|
def _is_fully_valid(self) -> bool:
|
||||||
if self.original_data is None:
|
if self.original_data is None:
|
||||||
@@ -542,10 +541,10 @@ class Certificate(OpensshModule):
|
|||||||
serial_number=self.serial_number,
|
serial_number=self.serial_number,
|
||||||
signature_algorithm=self.signature_algorithm,
|
signature_algorithm=self.signature_algorithm,
|
||||||
signing_key_path=self.signing_key,
|
signing_key_path=self.signing_key,
|
||||||
type=self.type,
|
cert_type=self.type,
|
||||||
time_parameters=self.time_parameters,
|
time_parameters=self.time_parameters,
|
||||||
use_agent=self.use_agent,
|
use_agent=self.use_agent,
|
||||||
environ_update=dict(TZ="UTC"),
|
environ_update={"TZ": "UTC"},
|
||||||
check_rc=True,
|
check_rc=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -625,38 +624,43 @@ def get_cert_dict(data: OpensshCertificate | None) -> dict[str, t.Any]:
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
force=dict(type="bool", default=False),
|
"force": {"type": "bool", "default": False},
|
||||||
identifier=dict(type="str"),
|
"identifier": {"type": "str"},
|
||||||
options=dict(type="list", elements="str"),
|
"options": {"type": "list", "elements": "str"},
|
||||||
path=dict(type="path", required=True),
|
"path": {"type": "path", "required": True},
|
||||||
pkcs11_provider=dict(type="str"),
|
"pkcs11_provider": {"type": "str"},
|
||||||
principals=dict(type="list", elements="str"),
|
"principals": {"type": "list", "elements": "str"},
|
||||||
public_key=dict(type="path"),
|
"public_key": {"type": "path"},
|
||||||
regenerate=dict(
|
"regenerate": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="partial_idempotence",
|
"default": "partial_idempotence",
|
||||||
choices=[
|
"choices": [
|
||||||
"never",
|
"never",
|
||||||
"fail",
|
"fail",
|
||||||
"partial_idempotence",
|
"partial_idempotence",
|
||||||
"full_idempotence",
|
"full_idempotence",
|
||||||
"always",
|
"always",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
signature_algorithm=dict(
|
"signature_algorithm": {
|
||||||
type="str", choices=["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"]
|
"type": "str",
|
||||||
),
|
"choices": ["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"],
|
||||||
signing_key=dict(type="path"),
|
},
|
||||||
serial_number=dict(type="int"),
|
"signing_key": {"type": "path"},
|
||||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
"serial_number": {"type": "int"},
|
||||||
type=dict(type="str", choices=["host", "user"]),
|
"state": {
|
||||||
use_agent=dict(type="bool", default=False),
|
"type": "str",
|
||||||
valid_at=dict(type="str"),
|
"default": "present",
|
||||||
valid_from=dict(type="str"),
|
"choices": ["absent", "present"],
|
||||||
valid_to=dict(type="str"),
|
},
|
||||||
ignore_timestamps=dict(type="bool", default=False),
|
"type": {"type": "str", "choices": ["host", "user"]},
|
||||||
),
|
"use_agent": {"type": "bool", "default": False},
|
||||||
|
"valid_at": {"type": "str"},
|
||||||
|
"valid_from": {"type": "str"},
|
||||||
|
"valid_to": {"type": "str"},
|
||||||
|
"ignore_timestamps": {"type": "bool", "default": False},
|
||||||
|
},
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
required_if=[
|
required_if=[
|
||||||
|
|||||||
@@ -209,41 +209,45 @@ from ansible_collections.community.crypto.plugins.module_utils._openssh.backends
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
"state": {
|
||||||
size=dict(type="int"),
|
"type": "str",
|
||||||
type=dict(
|
"default": "present",
|
||||||
type="str",
|
"choices": ["present", "absent"],
|
||||||
default="rsa",
|
},
|
||||||
choices=["rsa", "dsa", "rsa1", "ecdsa", "ed25519"],
|
"size": {"type": "int"},
|
||||||
),
|
"type": {
|
||||||
force=dict(type="bool", default=False),
|
"type": "str",
|
||||||
path=dict(type="path", required=True),
|
"default": "rsa",
|
||||||
comment=dict(type="str"),
|
"choices": ["rsa", "dsa", "rsa1", "ecdsa", "ed25519"],
|
||||||
regenerate=dict(
|
},
|
||||||
type="str",
|
"force": {"type": "bool", "default": False},
|
||||||
default="partial_idempotence",
|
"path": {"type": "path", "required": True},
|
||||||
choices=[
|
"comment": {"type": "str"},
|
||||||
|
"regenerate": {
|
||||||
|
"type": "str",
|
||||||
|
"default": "partial_idempotence",
|
||||||
|
"choices": [
|
||||||
"never",
|
"never",
|
||||||
"fail",
|
"fail",
|
||||||
"partial_idempotence",
|
"partial_idempotence",
|
||||||
"full_idempotence",
|
"full_idempotence",
|
||||||
"always",
|
"always",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
passphrase=dict(type="str", no_log=True),
|
"passphrase": {"type": "str", "no_log": True},
|
||||||
private_key_format=dict(
|
"private_key_format": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="auto",
|
"default": "auto",
|
||||||
no_log=False,
|
"no_log": False,
|
||||||
choices=["auto", "pkcs1", "pkcs8", "ssh"],
|
"choices": ["auto", "pkcs1", "pkcs8", "ssh"],
|
||||||
),
|
},
|
||||||
backend=dict(
|
"backend": {
|
||||||
type="str",
|
"type": "str",
|
||||||
default="auto",
|
"default": "auto",
|
||||||
choices=["auto", "cryptography", "opensshbin"],
|
"choices": ["auto", "cryptography", "opensshbin"],
|
||||||
),
|
},
|
||||||
),
|
},
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ class CertificateSigningRequestModule(OpenSSLObject):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, module: AnsibleModule, module_backend: CertificateSigningRequestBackend
|
self, module: AnsibleModule, module_backend: CertificateSigningRequestBackend
|
||||||
) -> None:
|
) -> None:
|
||||||
super(CertificateSigningRequestModule, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -308,7 +308,7 @@ class CertificateSigningRequestModule(OpenSSLObject):
|
|||||||
self.module_backend.set_existing(csr_bytes=None)
|
self.module_backend.set_existing(csr_bytes=None)
|
||||||
if self.backup and not self.check_mode:
|
if self.backup and not self.check_mode:
|
||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
super(CertificateSigningRequestModule, self).remove(module)
|
super().remove(module)
|
||||||
|
|
||||||
def dump(self) -> dict[str, t.Any]:
|
def dump(self) -> dict[str, t.Any]:
|
||||||
"""Serialize the object into a dictionary."""
|
"""Serialize the object into a dictionary."""
|
||||||
@@ -327,13 +327,17 @@ class CertificateSigningRequestModule(OpenSSLObject):
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = get_csr_argument_spec()
|
argument_spec = get_csr_argument_spec()
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
"state": {
|
||||||
force=dict(type="bool", default=False),
|
"type": "str",
|
||||||
path=dict(type="path", required=True),
|
"default": "present",
|
||||||
backup=dict(type="bool", default=False),
|
"choices": ["absent", "present"],
|
||||||
return_content=dict(type="bool", default=False),
|
},
|
||||||
)
|
"force": {"type": "bool", "default": False},
|
||||||
|
"path": {"type": "path", "required": True},
|
||||||
|
"backup": {"type": "bool", "default": False},
|
||||||
|
"return_content": {"type": "bool", "default": False},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
argument_spec.required_if.extend(
|
argument_spec.required_if.extend(
|
||||||
[("state", "present", rof, True) for rof in argument_spec.required_one_of]
|
[("state", "present", rof, True) for rof in argument_spec.required_one_of]
|
||||||
|
|||||||
@@ -321,16 +321,20 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.module_ba
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
path=dict(type="path"),
|
"path": {"type": "path"},
|
||||||
content=dict(type="str"),
|
"content": {"type": "str"},
|
||||||
name_encoding=dict(
|
"name_encoding": {
|
||||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
"type": "str",
|
||||||
),
|
"default": "ignore",
|
||||||
select_crypto_backend=dict(
|
"choices": ["ignore", "idna", "unicode"],
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
},
|
||||||
),
|
"select_crypto_backend": {
|
||||||
),
|
"type": "str",
|
||||||
|
"default": "auto",
|
||||||
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_one_of=(["path", "content"],),
|
required_one_of=(["path", "content"],),
|
||||||
mutually_exclusive=(["path", "content"],),
|
mutually_exclusive=(["path", "content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -178,9 +178,9 @@ class CertificateSigningRequestModule:
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = get_csr_argument_spec()
|
argument_spec = get_csr_argument_spec()
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
content=dict(type="str"),
|
"content": {"type": "str"},
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(
|
module = argument_spec.create_ansible_module(
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -252,7 +252,7 @@ class DHParameterBase:
|
|||||||
class DHParameterAbsent(DHParameterBase):
|
class DHParameterAbsent(DHParameterBase):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(DHParameterAbsent, self).__init__(module)
|
super().__init__(module)
|
||||||
|
|
||||||
def _do_generate(self, module: AnsibleModule) -> None:
|
def _do_generate(self, module: AnsibleModule) -> None:
|
||||||
"""Actually generate the DH params."""
|
"""Actually generate the DH params."""
|
||||||
@@ -265,7 +265,7 @@ class DHParameterAbsent(DHParameterBase):
|
|||||||
class DHParameterOpenSSL(DHParameterBase):
|
class DHParameterOpenSSL(DHParameterBase):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(DHParameterOpenSSL, self).__init__(module)
|
super().__init__(module)
|
||||||
self.openssl_bin = module.get_bin_path("openssl", True)
|
self.openssl_bin = module.get_bin_path("openssl", True)
|
||||||
|
|
||||||
def _do_generate(self, module: AnsibleModule) -> None:
|
def _do_generate(self, module: AnsibleModule) -> None:
|
||||||
@@ -320,7 +320,7 @@ class DHParameterOpenSSL(DHParameterBase):
|
|||||||
class DHParameterCryptography(DHParameterBase):
|
class DHParameterCryptography(DHParameterBase):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(DHParameterCryptography, self).__init__(module)
|
super().__init__(module)
|
||||||
|
|
||||||
def _do_generate(self, module: AnsibleModule) -> None:
|
def _do_generate(self, module: AnsibleModule) -> None:
|
||||||
"""Actually generate the DH params."""
|
"""Actually generate the DH params."""
|
||||||
@@ -359,17 +359,23 @@ def main() -> t.NoReturn:
|
|||||||
"""Main function"""
|
"""Main function"""
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
"state": {
|
||||||
size=dict(type="int", default=4096),
|
"type": "str",
|
||||||
force=dict(type="bool", default=False),
|
"default": "present",
|
||||||
path=dict(type="path", required=True),
|
"choices": ["absent", "present"],
|
||||||
backup=dict(type="bool", default=False),
|
},
|
||||||
select_crypto_backend=dict(
|
"size": {"type": "int", "default": 4096},
|
||||||
type="str", default="auto", choices=["auto", "cryptography", "openssl"]
|
"force": {"type": "bool", "default": False},
|
||||||
),
|
"path": {"type": "path", "required": True},
|
||||||
return_content=dict(type="bool", default=False),
|
"backup": {"type": "bool", "default": False},
|
||||||
),
|
"select_crypto_backend": {
|
||||||
|
"type": "str",
|
||||||
|
"default": "auto",
|
||||||
|
"choices": ["auto", "cryptography", "openssl"],
|
||||||
|
},
|
||||||
|
"return_content": {"type": "bool", "default": False},
|
||||||
|
},
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ class Pkcs(OpenSSLObject):
|
|||||||
path: str
|
path: str
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule, iter_size_default: int = 2048) -> None:
|
def __init__(self, module: AnsibleModule, iter_size_default: int = 2048) -> None:
|
||||||
super(Pkcs, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -413,7 +413,7 @@ class Pkcs(OpenSSLObject):
|
|||||||
with open(self.certificate_path, "rb") as fh:
|
with open(self.certificate_path, "rb") as fh:
|
||||||
self.certificate_content = fh.read()
|
self.certificate_content = fh.read()
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise PkcsError(exc)
|
raise PkcsError(exc) from exc
|
||||||
elif certificate_content is not None:
|
elif certificate_content is not None:
|
||||||
self.certificate_content = to_bytes(certificate_content)
|
self.certificate_content = to_bytes(certificate_content)
|
||||||
|
|
||||||
@@ -423,7 +423,7 @@ class Pkcs(OpenSSLObject):
|
|||||||
with open(self.privatekey_path, "rb") as fh:
|
with open(self.privatekey_path, "rb") as fh:
|
||||||
self.privatekey_content = fh.read()
|
self.privatekey_content = fh.read()
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise PkcsError(exc)
|
raise PkcsError(exc) from exc
|
||||||
elif privatekey_content is not None:
|
elif privatekey_content is not None:
|
||||||
self.privatekey_content = to_bytes(privatekey_content)
|
self.privatekey_content = to_bytes(privatekey_content)
|
||||||
|
|
||||||
@@ -480,11 +480,9 @@ class Pkcs(OpenSSLObject):
|
|||||||
def _get_friendly_name(self, pkcs12: PKCS12) -> bytes | None:
|
def _get_friendly_name(self, pkcs12: PKCS12) -> bytes | None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def check(self, module: AnsibleModule, perms_required: bool = True) -> bool:
|
def check(self, module: AnsibleModule, *, perms_required: bool = True) -> bool:
|
||||||
"""Ensure the resource is in its desired state."""
|
"""Ensure the resource is in its desired state."""
|
||||||
state_and_perms = super(Pkcs, self).check(
|
state_and_perms = super().check(module=module, perms_required=perms_required)
|
||||||
module=module, perms_required=perms_required
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_pkey_passphrase() -> bool:
|
def _check_pkey_passphrase() -> bool:
|
||||||
if self.privatekey_passphrase:
|
if self.privatekey_passphrase:
|
||||||
@@ -599,7 +597,7 @@ class Pkcs(OpenSSLObject):
|
|||||||
def remove(self, module: AnsibleModule) -> None:
|
def remove(self, module: AnsibleModule) -> None:
|
||||||
if self.backup:
|
if self.backup:
|
||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
super(Pkcs, self).remove(module)
|
super().remove(module)
|
||||||
|
|
||||||
def parse(self) -> tuple[
|
def parse(self) -> tuple[
|
||||||
bytes | None,
|
bytes | None,
|
||||||
@@ -616,7 +614,7 @@ class Pkcs(OpenSSLObject):
|
|||||||
pkcs12_content = pkcs12_fh.read()
|
pkcs12_content = pkcs12_fh.read()
|
||||||
return self.parse_bytes(pkcs12_content)
|
return self.parse_bytes(pkcs12_content)
|
||||||
except IOError as exc:
|
except IOError as exc:
|
||||||
raise PkcsError(exc)
|
raise PkcsError(exc) from exc
|
||||||
|
|
||||||
def generate(self, module: AnsibleModule) -> None:
|
def generate(self, module: AnsibleModule) -> None:
|
||||||
# Empty method because OpenSSLObject wants this
|
# Empty method because OpenSSLObject wants this
|
||||||
@@ -635,7 +633,7 @@ class Pkcs(OpenSSLObject):
|
|||||||
|
|
||||||
class PkcsCryptography(Pkcs):
|
class PkcsCryptography(Pkcs):
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(PkcsCryptography, self).__init__(module, iter_size_default=50000)
|
super().__init__(module, iter_size_default=50000)
|
||||||
if (
|
if (
|
||||||
self.encryption_level == "compatibility2022"
|
self.encryption_level == "compatibility2022"
|
||||||
and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022
|
and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022
|
||||||
@@ -656,7 +654,7 @@ class PkcsCryptography(Pkcs):
|
|||||||
passphrase=self.privatekey_passphrase,
|
passphrase=self.privatekey_passphrase,
|
||||||
)
|
)
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
raise PkcsError(exc)
|
raise PkcsError(exc) from exc
|
||||||
|
|
||||||
cert = None
|
cert = None
|
||||||
if self.certificate_content:
|
if self.certificate_content:
|
||||||
@@ -725,7 +723,7 @@ class PkcsCryptography(Pkcs):
|
|||||||
|
|
||||||
return (pkey, crt, other_certs, friendly_name)
|
return (pkey, crt, other_certs, friendly_name)
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
raise PkcsError(exc)
|
raise PkcsError(exc) from exc
|
||||||
|
|
||||||
def _dump_privatekey(self, pkcs12: PKCS12) -> bytes | None:
|
def _dump_privatekey(self, pkcs12: PKCS12) -> bytes | None:
|
||||||
return (
|
return (
|
||||||
@@ -759,39 +757,49 @@ def select_backend(module: AnsibleModule) -> Pkcs:
|
|||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = dict(
|
argument_spec = {
|
||||||
action=dict(type="str", default="export", choices=["export", "parse"]),
|
"action": {"type": "str", "default": "export", "choices": ["export", "parse"]},
|
||||||
other_certificates=dict(
|
"other_certificates": {
|
||||||
type="list", elements="path", aliases=["ca_certificates"]
|
"type": "list",
|
||||||
),
|
"elements": "path",
|
||||||
other_certificates_parse_all=dict(type="bool", default=False),
|
"aliases": ["ca_certificates"],
|
||||||
other_certificates_content=dict(type="list", elements="str"),
|
},
|
||||||
certificate_path=dict(type="path"),
|
"other_certificates_parse_all": {"type": "bool", "default": False},
|
||||||
certificate_content=dict(type="str"),
|
"other_certificates_content": {"type": "list", "elements": "str"},
|
||||||
force=dict(type="bool", default=False),
|
"certificate_path": {"type": "path"},
|
||||||
friendly_name=dict(type="str", aliases=["name"]),
|
"certificate_content": {"type": "str"},
|
||||||
encryption_level=dict(
|
"force": {"type": "bool", "default": False},
|
||||||
type="str", choices=["auto", "compatibility2022"], default="auto"
|
"friendly_name": {"type": "str", "aliases": ["name"]},
|
||||||
),
|
"encryption_level": {
|
||||||
iter_size=dict(type="int"),
|
"type": "str",
|
||||||
maciter_size=dict(
|
"choices": ["auto", "compatibility2022"],
|
||||||
type="int",
|
"default": "auto",
|
||||||
removed_in_version="4.0.0",
|
},
|
||||||
removed_from_collection="community.crypto",
|
"iter_size": {"type": "int"},
|
||||||
),
|
"maciter_size": {
|
||||||
passphrase=dict(type="str", no_log=True),
|
"type": "int",
|
||||||
path=dict(type="path", required=True),
|
"removed_in_version": "4.0.0",
|
||||||
privatekey_passphrase=dict(type="str", no_log=True),
|
"removed_from_collection": "community.crypto",
|
||||||
privatekey_path=dict(type="path"),
|
},
|
||||||
privatekey_content=dict(type="str", no_log=True),
|
"passphrase": {"type": "str", "no_log": True},
|
||||||
state=dict(type="str", default="present", choices=["absent", "present"]),
|
"path": {"type": "path", "required": True},
|
||||||
src=dict(type="path"),
|
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
backup=dict(type="bool", default=False),
|
"privatekey_path": {"type": "path"},
|
||||||
return_content=dict(type="bool", default=False),
|
"privatekey_content": {"type": "str", "no_log": True},
|
||||||
select_crypto_backend=dict(
|
"state": {
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
"type": "str",
|
||||||
),
|
"default": "present",
|
||||||
)
|
"choices": ["absent", "present"],
|
||||||
|
},
|
||||||
|
"src": {"type": "path"},
|
||||||
|
"backup": {"type": "bool", "default": False},
|
||||||
|
"return_content": {"type": "bool", "default": False},
|
||||||
|
"select_crypto_backend": {
|
||||||
|
"type": "str",
|
||||||
|
"default": "auto",
|
||||||
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
required_if = [
|
required_if = [
|
||||||
["action", "parse", ["src"]],
|
["action", "parse", ["src"]],
|
||||||
@@ -837,7 +845,7 @@ def main() -> t.NoReturn:
|
|||||||
pkcs12.write(module, pkcs12_content, 0o600)
|
pkcs12.write(module, pkcs12_content, 0o600)
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
pkey, cert, other_certs, friendly_name = pkcs12.parse()
|
pkey, cert, other_certs, _friendly_name = pkcs12.parse()
|
||||||
dump_content = "".join(
|
dump_content = "".join(
|
||||||
[
|
[
|
||||||
to_native(pem)
|
to_native(pem)
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ class PrivateKeyModule(OpenSSLObject):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, module: AnsibleModule, module_backend: PrivateKeyBackend
|
self, module: AnsibleModule, module_backend: PrivateKeyBackend
|
||||||
) -> None:
|
) -> None:
|
||||||
super(PrivateKeyModule, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -216,8 +216,6 @@ class PrivateKeyModule(OpenSSLObject):
|
|||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
self.module_backend.generate_private_key()
|
self.module_backend.generate_private_key()
|
||||||
privatekey_data = self.module_backend.get_private_key_data()
|
privatekey_data = self.module_backend.get_private_key_data()
|
||||||
if self.return_content:
|
|
||||||
self.privatekey_bytes = privatekey_data
|
|
||||||
write_file(module=module, content=privatekey_data, default_mode=0o600)
|
write_file(module=module, content=privatekey_data, default_mode=0o600)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
elif self.module_backend.needs_conversion():
|
elif self.module_backend.needs_conversion():
|
||||||
@@ -227,8 +225,6 @@ class PrivateKeyModule(OpenSSLObject):
|
|||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
self.module_backend.convert_private_key()
|
self.module_backend.convert_private_key()
|
||||||
privatekey_data = self.module_backend.get_private_key_data()
|
privatekey_data = self.module_backend.get_private_key_data()
|
||||||
if self.return_content:
|
|
||||||
self.privatekey_bytes = privatekey_data
|
|
||||||
write_file(module=module, content=privatekey_data, default_mode=0o600)
|
write_file(module=module, content=privatekey_data, default_mode=0o600)
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
@@ -244,7 +240,7 @@ class PrivateKeyModule(OpenSSLObject):
|
|||||||
self.module_backend.set_existing(privatekey_bytes=None)
|
self.module_backend.set_existing(privatekey_bytes=None)
|
||||||
if self.backup and not self.check_mode:
|
if self.backup and not self.check_mode:
|
||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
super(PrivateKeyModule, self).remove(module)
|
super().remove(module)
|
||||||
|
|
||||||
def dump(self) -> dict[str, t.Any]:
|
def dump(self) -> dict[str, t.Any]:
|
||||||
"""Serialize the object into a dictionary."""
|
"""Serialize the object into a dictionary."""
|
||||||
@@ -262,13 +258,17 @@ def main() -> t.NoReturn:
|
|||||||
|
|
||||||
argument_spec = get_privatekey_argument_spec()
|
argument_spec = get_privatekey_argument_spec()
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
"state": {
|
||||||
force=dict(type="bool", default=False),
|
"type": "str",
|
||||||
path=dict(type="path", required=True),
|
"default": "present",
|
||||||
backup=dict(type="bool", default=False),
|
"choices": ["present", "absent"],
|
||||||
return_content=dict(type="bool", default=False),
|
},
|
||||||
)
|
"force": {"type": "bool", "default": False},
|
||||||
|
"path": {"type": "path", "required": True},
|
||||||
|
"backup": {"type": "bool", "default": False},
|
||||||
|
"return_content": {"type": "bool", "default": False},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(
|
module = argument_spec.create_ansible_module(
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class PrivateKeyConvertModule(OpenSSLObject):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self, module: AnsibleModule, module_backend: PrivateKeyConvertBackend
|
self, module: AnsibleModule, module_backend: PrivateKeyConvertBackend
|
||||||
) -> None:
|
) -> None:
|
||||||
super(PrivateKeyConvertModule, self).__init__(
|
super().__init__(
|
||||||
path=module.params["dest_path"],
|
path=module.params["dest_path"],
|
||||||
state="present",
|
state="present",
|
||||||
force=False,
|
force=False,
|
||||||
@@ -145,10 +145,10 @@ def main() -> t.NoReturn:
|
|||||||
|
|
||||||
argument_spec = get_privatekey_argument_spec()
|
argument_spec = get_privatekey_argument_spec()
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
dest_path=dict(type="path", required=True),
|
"dest_path": {"type": "path", "required": True},
|
||||||
backup=dict(type="bool", default=False),
|
"backup": {"type": "bool", "default": False},
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(
|
module = argument_spec.create_ansible_module(
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -215,26 +215,28 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.module_ba
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
path=dict(type="path"),
|
"path": {"type": "path"},
|
||||||
content=dict(type="str", no_log=True),
|
"content": {"type": "str", "no_log": True},
|
||||||
passphrase=dict(type="str", no_log=True),
|
"passphrase": {"type": "str", "no_log": True},
|
||||||
return_private_key_data=dict(type="bool", default=False),
|
"return_private_key_data": {"type": "bool", "default": False},
|
||||||
check_consistency=dict(type="bool", default=False),
|
"check_consistency": {"type": "bool", "default": False},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
"type": "str",
|
||||||
),
|
"default": "auto",
|
||||||
),
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_one_of=(["path", "content"],),
|
required_one_of=(["path", "content"],),
|
||||||
mutually_exclusive=(["path", "content"],),
|
mutually_exclusive=(["path", "content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = dict(
|
result = {
|
||||||
can_load_key=False,
|
"can_load_key": False,
|
||||||
can_parse_key=False,
|
"can_parse_key": False,
|
||||||
key_is_consistent=None,
|
"key_is_consistent": None,
|
||||||
)
|
}
|
||||||
|
|
||||||
if module.params["content"] is not None:
|
if module.params["content"] is not None:
|
||||||
data = module.params["content"].encode("utf-8")
|
data = module.params["content"].encode("utf-8")
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ class PublicKeyError(OpenSSLObjectError):
|
|||||||
class PublicKey(OpenSSLObject):
|
class PublicKey(OpenSSLObject):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(PublicKey, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -287,11 +287,10 @@ class PublicKey(OpenSSLObject):
|
|||||||
crypto_serialization.Encoding.OpenSSH,
|
crypto_serialization.Encoding.OpenSSH,
|
||||||
crypto_serialization.PublicFormat.OpenSSH,
|
crypto_serialization.PublicFormat.OpenSSH,
|
||||||
)
|
)
|
||||||
else:
|
return self.privatekey.public_key().public_bytes(
|
||||||
return self.privatekey.public_key().public_bytes(
|
crypto_serialization.Encoding.PEM,
|
||||||
crypto_serialization.Encoding.PEM,
|
crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
|
||||||
crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def generate(self, module: AnsibleModule) -> None:
|
def generate(self, module: AnsibleModule) -> None:
|
||||||
"""Generate the public key."""
|
"""Generate the public key."""
|
||||||
@@ -316,9 +315,9 @@ class PublicKey(OpenSSLObject):
|
|||||||
|
|
||||||
self.changed = True
|
self.changed = True
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
raise PublicKeyError(exc)
|
raise PublicKeyError(exc) from exc
|
||||||
except (IOError, OSError) as exc:
|
except (IOError, OSError) as exc:
|
||||||
raise PublicKeyError(exc)
|
raise PublicKeyError(exc) from exc
|
||||||
|
|
||||||
self.fingerprint = get_fingerprint(
|
self.fingerprint = get_fingerprint(
|
||||||
path=self.privatekey_path,
|
path=self.privatekey_path,
|
||||||
@@ -331,12 +330,10 @@ class PublicKey(OpenSSLObject):
|
|||||||
elif module.set_fs_attributes_if_different(file_args, False):
|
elif module.set_fs_attributes_if_different(file_args, False):
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
def check(self, module: AnsibleModule, perms_required: bool = True) -> bool:
|
def check(self, module: AnsibleModule, *, perms_required: bool = True) -> bool:
|
||||||
"""Ensure the resource is in its desired state."""
|
"""Ensure the resource is in its desired state."""
|
||||||
|
|
||||||
state_and_perms = super(PublicKey, self).check(
|
state_and_perms = super().check(module=module, perms_required=perms_required)
|
||||||
module=module, perms_required=perms_required
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_privatekey() -> bool:
|
def _check_privatekey() -> bool:
|
||||||
if self.privatekey_path is not None and not os.path.exists(
|
if self.privatekey_path is not None and not os.path.exists(
|
||||||
@@ -374,7 +371,7 @@ class PublicKey(OpenSSLObject):
|
|||||||
try:
|
try:
|
||||||
desired_publickey = self._create_publickey(module)
|
desired_publickey = self._create_publickey(module)
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
raise PublicKeyError(exc)
|
raise PublicKeyError(exc) from exc
|
||||||
|
|
||||||
return publickey_content == desired_publickey
|
return publickey_content == desired_publickey
|
||||||
|
|
||||||
@@ -386,7 +383,7 @@ class PublicKey(OpenSSLObject):
|
|||||||
def remove(self, module: AnsibleModule) -> None:
|
def remove(self, module: AnsibleModule) -> None:
|
||||||
if self.backup:
|
if self.backup:
|
||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
super(PublicKey, self).remove(module)
|
super().remove(module)
|
||||||
|
|
||||||
def dump(self) -> dict[str, t.Any]:
|
def dump(self) -> dict[str, t.Any]:
|
||||||
"""Serialize the object into a dictionary."""
|
"""Serialize the object into a dictionary."""
|
||||||
@@ -409,10 +406,10 @@ class PublicKey(OpenSSLObject):
|
|||||||
self.publickey_bytes.decode("utf-8") if self.publickey_bytes else None
|
self.publickey_bytes.decode("utf-8") if self.publickey_bytes else None
|
||||||
)
|
)
|
||||||
|
|
||||||
result["diff"] = dict(
|
result["diff"] = {
|
||||||
before=self.diff_before,
|
"before": self.diff_before,
|
||||||
after=self.diff_after,
|
"after": self.diff_after,
|
||||||
)
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -420,20 +417,26 @@ class PublicKey(OpenSSLObject):
|
|||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
|
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
"state": {
|
||||||
force=dict(type="bool", default=False),
|
"type": "str",
|
||||||
path=dict(type="path", required=True),
|
"default": "present",
|
||||||
privatekey_path=dict(type="path"),
|
"choices": ["present", "absent"],
|
||||||
privatekey_content=dict(type="str", no_log=True),
|
},
|
||||||
format=dict(type="str", default="PEM", choices=["OpenSSH", "PEM"]),
|
"force": {"type": "bool", "default": False},
|
||||||
privatekey_passphrase=dict(type="str", no_log=True),
|
"path": {"type": "path", "required": True},
|
||||||
backup=dict(type="bool", default=False),
|
"privatekey_path": {"type": "path"},
|
||||||
select_crypto_backend=dict(
|
"privatekey_content": {"type": "str", "no_log": True},
|
||||||
type="str", choices=["auto", "cryptography"], default="auto"
|
"format": {"type": "str", "default": "PEM", "choices": ["OpenSSH", "PEM"]},
|
||||||
),
|
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
return_content=dict(type="bool", default=False),
|
"backup": {"type": "bool", "default": False},
|
||||||
),
|
"select_crypto_backend": {
|
||||||
|
"type": "str",
|
||||||
|
"choices": ["auto", "cryptography"],
|
||||||
|
"default": "auto",
|
||||||
|
},
|
||||||
|
"return_content": {"type": "bool", "default": False},
|
||||||
|
},
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
required_if=[
|
required_if=[
|
||||||
|
|||||||
@@ -166,23 +166,25 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.module_ba
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
path=dict(type="path"),
|
"path": {"type": "path"},
|
||||||
content=dict(type="str", no_log=True),
|
"content": {"type": "str", "no_log": True},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
"type": "str",
|
||||||
),
|
"default": "auto",
|
||||||
),
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_one_of=(["path", "content"],),
|
required_one_of=(["path", "content"],),
|
||||||
mutually_exclusive=(["path", "content"],),
|
mutually_exclusive=(["path", "content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = dict(
|
result = {
|
||||||
can_load_key=False,
|
"can_load_key": False,
|
||||||
can_parse_key=False,
|
"can_parse_key": False,
|
||||||
key_is_consistent=None,
|
"key_is_consistent": None,
|
||||||
)
|
}
|
||||||
|
|
||||||
if module.params["content"] is not None:
|
if module.params["content"] is not None:
|
||||||
data = module.params["content"].encode("utf-8")
|
data = module.params["content"].encode("utf-8")
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.support i
|
|||||||
class SignatureBase(OpenSSLObject):
|
class SignatureBase(OpenSSLObject):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(SignatureBase, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state="present",
|
state="present",
|
||||||
force=False,
|
force=False,
|
||||||
@@ -163,7 +163,7 @@ class SignatureBase(OpenSSLObject):
|
|||||||
class SignatureCryptography(SignatureBase):
|
class SignatureCryptography(SignatureBase):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(SignatureCryptography, self).__init__(module)
|
super().__init__(module)
|
||||||
|
|
||||||
def run(self) -> dict[str, t.Any]:
|
def run(self) -> dict[str, t.Any]:
|
||||||
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
||||||
@@ -224,20 +224,22 @@ class SignatureCryptography(SignatureBase):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise OpenSSLObjectError(e)
|
raise OpenSSLObjectError(e) from e
|
||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
privatekey_path=dict(type="path"),
|
"privatekey_path": {"type": "path"},
|
||||||
privatekey_content=dict(type="str", no_log=True),
|
"privatekey_content": {"type": "str", "no_log": True},
|
||||||
privatekey_passphrase=dict(type="str", no_log=True),
|
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
path=dict(type="path", required=True),
|
"path": {"type": "path", "required": True},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", choices=["auto", "cryptography"], default="auto"
|
"type": "str",
|
||||||
),
|
"choices": ["auto", "cryptography"],
|
||||||
),
|
"default": "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
mutually_exclusive=(["privatekey_path", "privatekey_content"],),
|
mutually_exclusive=(["privatekey_path", "privatekey_content"],),
|
||||||
required_one_of=(["privatekey_path", "privatekey_content"],),
|
required_one_of=(["privatekey_path", "privatekey_content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.support i
|
|||||||
class SignatureInfoBase(OpenSSLObject):
|
class SignatureInfoBase(OpenSSLObject):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(SignatureInfoBase, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state="present",
|
state="present",
|
||||||
force=False,
|
force=False,
|
||||||
@@ -152,7 +152,7 @@ class SignatureInfoBase(OpenSSLObject):
|
|||||||
class SignatureInfoCryptography(SignatureInfoBase):
|
class SignatureInfoCryptography(SignatureInfoBase):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(SignatureInfoCryptography, self).__init__(module)
|
super().__init__(module)
|
||||||
|
|
||||||
def run(self) -> dict[str, t.Any]:
|
def run(self) -> dict[str, t.Any]:
|
||||||
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
|
||||||
@@ -229,20 +229,22 @@ class SignatureInfoCryptography(SignatureInfoBase):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise OpenSSLObjectError(e)
|
raise OpenSSLObjectError(e) from e
|
||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
certificate_path=dict(type="path"),
|
"certificate_path": {"type": "path"},
|
||||||
certificate_content=dict(type="str"),
|
"certificate_content": {"type": "str"},
|
||||||
path=dict(type="path", required=True),
|
"path": {"type": "path", "required": True},
|
||||||
signature=dict(type="str", required=True),
|
"signature": {"type": "str", "required": True},
|
||||||
select_crypto_backend=dict(
|
"select_crypto_backend": {
|
||||||
type="str", choices=["auto", "cryptography"], default="auto"
|
"type": "str",
|
||||||
),
|
"choices": ["auto", "cryptography"],
|
||||||
),
|
"default": "auto",
|
||||||
|
},
|
||||||
|
},
|
||||||
mutually_exclusive=(["certificate_path", "certificate_content"],),
|
mutually_exclusive=(["certificate_path", "certificate_content"],),
|
||||||
required_one_of=(["certificate_path", "certificate_content"],),
|
required_one_of=(["certificate_path", "certificate_content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ if t.TYPE_CHECKING:
|
|||||||
|
|
||||||
class CertificateAbsent(OpenSSLObject):
|
class CertificateAbsent(OpenSSLObject):
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(CertificateAbsent, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -284,7 +284,7 @@ class CertificateAbsent(OpenSSLObject):
|
|||||||
def remove(self, module: AnsibleModule) -> None:
|
def remove(self, module: AnsibleModule) -> None:
|
||||||
if self.backup:
|
if self.backup:
|
||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
super(CertificateAbsent, self).remove(module)
|
super().remove(module)
|
||||||
|
|
||||||
def dump(self, check_mode: bool = False) -> dict[str, t.Any]:
|
def dump(self, check_mode: bool = False) -> dict[str, t.Any]:
|
||||||
result = {
|
result = {
|
||||||
@@ -305,7 +305,7 @@ class GenericCertificate(OpenSSLObject):
|
|||||||
"""Retrieve a certificate using the given module backend."""
|
"""Retrieve a certificate using the given module backend."""
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule, module_backend: CertificateBackend):
|
def __init__(self, module: AnsibleModule, module_backend: CertificateBackend):
|
||||||
super(GenericCertificate, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -339,12 +339,10 @@ class GenericCertificate(OpenSSLObject):
|
|||||||
file_args, self.changed
|
file_args, self.changed
|
||||||
)
|
)
|
||||||
|
|
||||||
def check(self, module: AnsibleModule, perms_required: bool = True) -> bool:
|
def check(self, module: AnsibleModule, *, perms_required: bool = True) -> bool:
|
||||||
"""Ensure the resource is in its desired state."""
|
"""Ensure the resource is in its desired state."""
|
||||||
return (
|
return (
|
||||||
super(GenericCertificate, self).check(
|
super().check(module=module, perms_required=perms_required)
|
||||||
module=module, perms_required=perms_required
|
|
||||||
)
|
|
||||||
and not self.module_backend.needs_regeneration()
|
and not self.module_backend.needs_regeneration()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -368,12 +366,16 @@ def main() -> t.NoReturn:
|
|||||||
add_ownca_provider_to_argument_spec(argument_spec)
|
add_ownca_provider_to_argument_spec(argument_spec)
|
||||||
add_selfsigned_provider_to_argument_spec(argument_spec)
|
add_selfsigned_provider_to_argument_spec(argument_spec)
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
"state": {
|
||||||
path=dict(type="path", required=True),
|
"type": "str",
|
||||||
backup=dict(type="bool", default=False),
|
"default": "present",
|
||||||
return_content=dict(type="bool", default=False),
|
"choices": ["present", "absent"],
|
||||||
)
|
},
|
||||||
|
"path": {"type": "path", "required": True},
|
||||||
|
"backup": {"type": "bool", "default": False},
|
||||||
|
"return_content": {"type": "bool", "default": False},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
argument_spec.required_if.append(("state", "present", ["provider"]))
|
argument_spec.required_if.append(("state", "present", ["provider"]))
|
||||||
module = argument_spec.create_ansible_module(
|
module = argument_spec.create_ansible_module(
|
||||||
|
|||||||
@@ -143,13 +143,13 @@ except ImportError:
|
|||||||
|
|
||||||
|
|
||||||
def parse_certificate(
|
def parse_certificate(
|
||||||
input: bytes, strict: bool = False
|
certificate_content: bytes, strict: bool = False
|
||||||
) -> tuple[bytes, t.Literal["pem", "der"], str | None]:
|
) -> tuple[bytes, t.Literal["pem", "der"], str | None]:
|
||||||
input_format: t.Literal["pem", "der"] = (
|
input_format: t.Literal["pem", "der"] = (
|
||||||
"pem" if identify_pem_format(input) else "der"
|
"pem" if identify_pem_format(certificate_content) else "der"
|
||||||
)
|
)
|
||||||
if input_format == "pem":
|
if input_format == "pem":
|
||||||
pems = split_pem_list(to_text(input))
|
pems = split_pem_list(to_text(certificate_content))
|
||||||
if len(pems) > 1 and strict:
|
if len(pems) > 1 and strict:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"The input contains {len(pems)} PEM objects, expecting only one since strict=true"
|
f"The input contains {len(pems)} PEM objects, expecting only one since strict=true"
|
||||||
@@ -159,15 +159,15 @@ def parse_certificate(
|
|||||||
raise ValueError(
|
raise ValueError(
|
||||||
f"type is {pem_header_type!r}, expecting CERTIFICATE or X509 CERTIFICATE"
|
f"type is {pem_header_type!r}, expecting CERTIFICATE or X509 CERTIFICATE"
|
||||||
)
|
)
|
||||||
input = base64.b64decode(content)
|
certificate_content = base64.b64decode(content)
|
||||||
else:
|
else:
|
||||||
pem_header_type = None
|
pem_header_type = None
|
||||||
return input, input_format, pem_header_type
|
return certificate_content, input_format, pem_header_type
|
||||||
|
|
||||||
|
|
||||||
class X509CertificateConvertModule(OpenSSLObject):
|
class X509CertificateConvertModule(OpenSSLObject):
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(X509CertificateConvertModule, self).__init__(
|
super().__init__(
|
||||||
path=module.params["dest_path"],
|
path=module.params["dest_path"],
|
||||||
state="present",
|
state="present",
|
||||||
force=False,
|
force=False,
|
||||||
@@ -287,16 +287,16 @@ class X509CertificateConvertModule(OpenSSLObject):
|
|||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
argument_spec = dict(
|
argument_spec = {
|
||||||
src_path=dict(type="path"),
|
"src_path": {"type": "path"},
|
||||||
src_content=dict(type="str"),
|
"src_content": {"type": "str"},
|
||||||
src_content_base64=dict(type="bool", default=False),
|
"src_content_base64": {"type": "bool", "default": False},
|
||||||
format=dict(type="str", required=True, choices=["pem", "der"]),
|
"format": {"type": "str", "required": True, "choices": ["pem", "der"]},
|
||||||
strict=dict(type="bool", default=False),
|
"strict": {"type": "bool", "default": False},
|
||||||
dest_path=dict(type="path", required=True),
|
"dest_path": {"type": "path", "required": True},
|
||||||
backup=dict(type="bool", default=False),
|
"backup": {"type": "bool", "default": False},
|
||||||
verify_cert_parsable=dict(type="bool", default=False),
|
"verify_cert_parsable": {"type": "bool", "default": False},
|
||||||
)
|
}
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec,
|
argument_spec,
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -410,17 +410,21 @@ from ansible_collections.community.crypto.plugins.module_utils._time import (
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
path=dict(type="path"),
|
"path": {"type": "path"},
|
||||||
content=dict(type="str"),
|
"content": {"type": "str"},
|
||||||
valid_at=dict(type="dict"),
|
"valid_at": {"type": "dict"},
|
||||||
name_encoding=dict(
|
"name_encoding": {
|
||||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
"type": "str",
|
||||||
),
|
"default": "ignore",
|
||||||
select_crypto_backend=dict(
|
"choices": ["ignore", "idna", "unicode"],
|
||||||
type="str", default="auto", choices=["auto", "cryptography"]
|
},
|
||||||
),
|
"select_crypto_backend": {
|
||||||
),
|
"type": "str",
|
||||||
|
"default": "auto",
|
||||||
|
"choices": ["auto", "cryptography"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_one_of=(["path", "content"],),
|
required_one_of=(["path", "content"],),
|
||||||
mutually_exclusive=(["path", "content"],),
|
mutually_exclusive=(["path", "content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
@@ -460,7 +464,7 @@ def main() -> t.NoReturn:
|
|||||||
not_before = module_backend.get_not_before()
|
not_before = module_backend.get_not_before()
|
||||||
not_after = module_backend.get_not_after()
|
not_after = module_backend.get_not_after()
|
||||||
|
|
||||||
result["valid_at"] = dict()
|
result["valid_at"] = {}
|
||||||
if valid_at:
|
if valid_at:
|
||||||
for k, v in valid_at.items():
|
for k, v in valid_at.items():
|
||||||
result["valid_at"][k] = not_before <= v <= not_after
|
result["valid_at"][k] = not_before <= v <= not_after
|
||||||
|
|||||||
@@ -182,9 +182,9 @@ def main() -> t.NoReturn:
|
|||||||
add_ownca_provider_to_argument_spec(argument_spec)
|
add_ownca_provider_to_argument_spec(argument_spec)
|
||||||
add_selfsigned_provider_to_argument_spec(argument_spec)
|
add_selfsigned_provider_to_argument_spec(argument_spec)
|
||||||
argument_spec.argument_spec.update(
|
argument_spec.argument_spec.update(
|
||||||
dict(
|
{
|
||||||
content=dict(type="str"),
|
"content": {"type": "str"},
|
||||||
)
|
}
|
||||||
)
|
)
|
||||||
module = argument_spec.create_ansible_module(
|
module = argument_spec.create_ansible_module(
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ class CRLError(OpenSSLObjectError):
|
|||||||
class CRL(OpenSSLObject):
|
class CRL(OpenSSLObject):
|
||||||
|
|
||||||
def __init__(self, module: AnsibleModule) -> None:
|
def __init__(self, module: AnsibleModule) -> None:
|
||||||
super(CRL, self).__init__(
|
super().__init__(
|
||||||
path=module.params["path"],
|
path=module.params["path"],
|
||||||
state=module.params["state"],
|
state=module.params["state"],
|
||||||
force=module.params["force"],
|
force=module.params["force"],
|
||||||
@@ -650,7 +650,7 @@ class CRL(OpenSSLObject):
|
|||||||
passphrase=self.privatekey_passphrase,
|
passphrase=self.privatekey_passphrase,
|
||||||
)
|
)
|
||||||
except OpenSSLBadPassphraseError as exc:
|
except OpenSSLBadPassphraseError as exc:
|
||||||
raise CRLError(exc)
|
raise CRLError(exc) from exc
|
||||||
|
|
||||||
self.crl = None
|
self.crl = None
|
||||||
try:
|
try:
|
||||||
@@ -704,7 +704,7 @@ class CRL(OpenSSLObject):
|
|||||||
def remove(self, module: AnsibleModule) -> None:
|
def remove(self, module: AnsibleModule) -> None:
|
||||||
if self.backup:
|
if self.backup:
|
||||||
self.backup_file = self.module.backup_local(self.path)
|
self.backup_file = self.module.backup_local(self.path)
|
||||||
super(CRL, self).remove(self.module)
|
super().remove(self.module)
|
||||||
|
|
||||||
def _compress_entry(self, entry: dict[str, t.Any]) -> (
|
def _compress_entry(self, entry: dict[str, t.Any]) -> (
|
||||||
tuple[
|
tuple[
|
||||||
@@ -750,27 +750,27 @@ class CRL(OpenSSLObject):
|
|||||||
entry["invalidity_date"],
|
entry["invalidity_date"],
|
||||||
entry["invalidity_date_critical"],
|
entry["invalidity_date_critical"],
|
||||||
)
|
)
|
||||||
else:
|
return (
|
||||||
return (
|
entry["serial_number"],
|
||||||
entry["serial_number"],
|
entry["revocation_date"],
|
||||||
entry["revocation_date"],
|
issuer,
|
||||||
issuer,
|
entry["issuer_critical"],
|
||||||
entry["issuer_critical"],
|
entry["reason"],
|
||||||
entry["reason"],
|
entry["reason_critical"],
|
||||||
entry["reason_critical"],
|
entry["invalidity_date"],
|
||||||
entry["invalidity_date"],
|
entry["invalidity_date_critical"],
|
||||||
entry["invalidity_date_critical"],
|
)
|
||||||
)
|
|
||||||
|
|
||||||
def check(
|
def check(
|
||||||
self,
|
self,
|
||||||
module: AnsibleModule,
|
module: AnsibleModule,
|
||||||
|
*,
|
||||||
perms_required: bool = True,
|
perms_required: bool = True,
|
||||||
ignore_conversion: bool = True,
|
ignore_conversion: bool = True,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Ensure the resource is in its desired state."""
|
"""Ensure the resource is in its desired state."""
|
||||||
|
|
||||||
state_and_perms = super(CRL, self).check(
|
state_and_perms = super().check(
|
||||||
module=self.module, perms_required=perms_required
|
module=self.module, perms_required=perms_required
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -843,16 +843,16 @@ class CRL(OpenSSLObject):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise CRLError(e)
|
raise CRLError(e) from e
|
||||||
|
|
||||||
crl = set_last_update(crl, value=self.last_update)
|
crl = set_last_update(crl, value=self.last_update)
|
||||||
if self.next_update is not None:
|
if self.next_update is not None:
|
||||||
crl = set_next_update(crl, value=self.next_update)
|
crl = set_next_update(crl, value=self.next_update)
|
||||||
|
|
||||||
if self.update and self.crl:
|
if self.update and self.crl:
|
||||||
new_entries = set(
|
new_entries = {
|
||||||
[self._compress_entry(entry) for entry in self.revoked_certificates]
|
self._compress_entry(entry) for entry in self.revoked_certificates
|
||||||
)
|
}
|
||||||
for entry in self.crl:
|
for entry in self.crl:
|
||||||
decoded_entry = self._compress_entry(
|
decoded_entry = self._compress_entry(
|
||||||
cryptography_decode_revoked_certificate(entry)
|
cryptography_decode_revoked_certificate(entry)
|
||||||
@@ -888,8 +888,7 @@ class CRL(OpenSSLObject):
|
|||||||
self.crl = crl.sign(self.privatekey, digest)
|
self.crl = crl.sign(self.privatekey, digest)
|
||||||
if self.format == "pem":
|
if self.format == "pem":
|
||||||
return self.crl.public_bytes(Encoding.PEM)
|
return self.crl.public_bytes(Encoding.PEM)
|
||||||
else:
|
return self.crl.public_bytes(Encoding.DER)
|
||||||
return self.crl.public_bytes(Encoding.DER)
|
|
||||||
|
|
||||||
def generate(self, module: AnsibleModule) -> None:
|
def generate(self, module: AnsibleModule) -> None:
|
||||||
result = None
|
result = None
|
||||||
@@ -996,49 +995,53 @@ class CRL(OpenSSLObject):
|
|||||||
if self.return_content:
|
if self.return_content:
|
||||||
result["crl"] = self.crl_content
|
result["crl"] = self.crl_content
|
||||||
|
|
||||||
result["diff"] = dict(
|
result["diff"] = {
|
||||||
before=self.diff_before,
|
"before": self.diff_before,
|
||||||
after=self.diff_after,
|
"after": self.diff_after,
|
||||||
)
|
}
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
state=dict(type="str", default="present", choices=["present", "absent"]),
|
"state": {
|
||||||
crl_mode=dict(
|
"type": "str",
|
||||||
type="str",
|
"default": "present",
|
||||||
default="generate",
|
"choices": ["present", "absent"],
|
||||||
choices=["generate", "update"],
|
},
|
||||||
),
|
"crl_mode": {
|
||||||
force=dict(type="bool", default=False),
|
"type": "str",
|
||||||
backup=dict(type="bool", default=False),
|
"default": "generate",
|
||||||
path=dict(type="path", required=True),
|
"choices": ["generate", "update"],
|
||||||
format=dict(type="str", default="pem", choices=["pem", "der"]),
|
},
|
||||||
privatekey_path=dict(type="path"),
|
"force": {"type": "bool", "default": False},
|
||||||
privatekey_content=dict(type="str", no_log=True),
|
"backup": {"type": "bool", "default": False},
|
||||||
privatekey_passphrase=dict(type="str", no_log=True),
|
"path": {"type": "path", "required": True},
|
||||||
issuer=dict(type="dict"),
|
"format": {"type": "str", "default": "pem", "choices": ["pem", "der"]},
|
||||||
issuer_ordered=dict(type="list", elements="dict"),
|
"privatekey_path": {"type": "path"},
|
||||||
last_update=dict(type="str", default="+0s"),
|
"privatekey_content": {"type": "str", "no_log": True},
|
||||||
next_update=dict(type="str"),
|
"privatekey_passphrase": {"type": "str", "no_log": True},
|
||||||
digest=dict(type="str", default="sha256"),
|
"issuer": {"type": "dict"},
|
||||||
ignore_timestamps=dict(type="bool", default=False),
|
"issuer_ordered": {"type": "list", "elements": "dict"},
|
||||||
return_content=dict(type="bool", default=False),
|
"last_update": {"type": "str", "default": "+0s"},
|
||||||
revoked_certificates=dict(
|
"next_update": {"type": "str"},
|
||||||
type="list",
|
"digest": {"type": "str", "default": "sha256"},
|
||||||
elements="dict",
|
"ignore_timestamps": {"type": "bool", "default": False},
|
||||||
options=dict(
|
"return_content": {"type": "bool", "default": False},
|
||||||
path=dict(type="path"),
|
"revoked_certificates": {
|
||||||
content=dict(type="str"),
|
"type": "list",
|
||||||
serial_number=dict(type="raw"),
|
"elements": "dict",
|
||||||
revocation_date=dict(type="str", default="+0s"),
|
"options": {
|
||||||
issuer=dict(type="list", elements="str"),
|
"path": {"type": "path"},
|
||||||
issuer_critical=dict(type="bool", default=False),
|
"content": {"type": "str"},
|
||||||
reason=dict(
|
"serial_number": {"type": "raw"},
|
||||||
type="str",
|
"revocation_date": {"type": "str", "default": "+0s"},
|
||||||
choices=[
|
"issuer": {"type": "list", "elements": "str"},
|
||||||
|
"issuer_critical": {"type": "bool", "default": False},
|
||||||
|
"reason": {
|
||||||
|
"type": "str",
|
||||||
|
"choices": [
|
||||||
"unspecified",
|
"unspecified",
|
||||||
"key_compromise",
|
"key_compromise",
|
||||||
"ca_compromise",
|
"ca_compromise",
|
||||||
@@ -1050,21 +1053,25 @@ def main() -> t.NoReturn:
|
|||||||
"aa_compromise",
|
"aa_compromise",
|
||||||
"remove_from_crl",
|
"remove_from_crl",
|
||||||
],
|
],
|
||||||
),
|
},
|
||||||
reason_critical=dict(type="bool", default=False),
|
"reason_critical": {"type": "bool", "default": False},
|
||||||
invalidity_date=dict(type="str"),
|
"invalidity_date": {"type": "str"},
|
||||||
invalidity_date_critical=dict(type="bool", default=False),
|
"invalidity_date_critical": {"type": "bool", "default": False},
|
||||||
),
|
},
|
||||||
required_one_of=[["path", "content", "serial_number"]],
|
"required_one_of": [["path", "content", "serial_number"]],
|
||||||
mutually_exclusive=[["path", "content", "serial_number"]],
|
"mutually_exclusive": [["path", "content", "serial_number"]],
|
||||||
),
|
},
|
||||||
name_encoding=dict(
|
"name_encoding": {
|
||||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
"type": "str",
|
||||||
),
|
"default": "ignore",
|
||||||
serial_numbers=dict(
|
"choices": ["ignore", "idna", "unicode"],
|
||||||
type="str", default="integer", choices=["integer", "hex-octets"]
|
},
|
||||||
),
|
"serial_numbers": {
|
||||||
),
|
"type": "str",
|
||||||
|
"default": "integer",
|
||||||
|
"choices": ["integer", "hex-octets"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_if=[
|
required_if=[
|
||||||
("state", "present", ["privatekey_path", "privatekey_content"], True),
|
("state", "present", ["privatekey_path", "privatekey_content"], True),
|
||||||
("state", "present", ["issuer", "issuer_ordered"], True),
|
("state", "present", ["issuer", "issuer_ordered"], True),
|
||||||
|
|||||||
@@ -190,14 +190,16 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.pem impor
|
|||||||
|
|
||||||
def main() -> t.NoReturn:
|
def main() -> t.NoReturn:
|
||||||
module = AnsibleModule(
|
module = AnsibleModule(
|
||||||
argument_spec=dict(
|
argument_spec={
|
||||||
path=dict(type="path"),
|
"path": {"type": "path"},
|
||||||
content=dict(type="str"),
|
"content": {"type": "str"},
|
||||||
list_revoked_certificates=dict(type="bool", default=True),
|
"list_revoked_certificates": {"type": "bool", "default": True},
|
||||||
name_encoding=dict(
|
"name_encoding": {
|
||||||
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
|
"type": "str",
|
||||||
),
|
"default": "ignore",
|
||||||
),
|
"choices": ["ignore", "idna", "unicode"],
|
||||||
|
},
|
||||||
|
},
|
||||||
required_one_of=(["path", "content"],),
|
required_one_of=(["path", "content"],),
|
||||||
mutually_exclusive=(["path", "content"],),
|
mutually_exclusive=(["path", "content"],),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ if t.TYPE_CHECKING:
|
|||||||
|
|
||||||
class _ModuleExitException(Exception):
|
class _ModuleExitException(Exception):
|
||||||
def __init__(self, result: dict[str, t.Any]) -> None:
|
def __init__(self, result: dict[str, t.Any]) -> None:
|
||||||
super(_ModuleExitException, self).__init__()
|
super().__init__()
|
||||||
self.result = result
|
self.result = result
|
||||||
|
|
||||||
|
|
||||||
@@ -187,10 +187,12 @@ class AnsibleActionModule:
|
|||||||
collection_name=d.get("collection_name"),
|
collection_name=d.get("collection_name"),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# pylint: disable-next=unknown-option-value
|
||||||
self.deprecate( # pylint: disable=ansible-deprecated-no-version
|
self.deprecate( # pylint: disable=ansible-deprecated-no-version
|
||||||
d
|
d
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
# pylint: disable-next=unknown-option-value
|
||||||
self.deprecate( # pylint: disable=ansible-deprecated-no-version
|
self.deprecate( # pylint: disable=ansible-deprecated-no-version
|
||||||
kwargs["deprecations"]
|
kwargs["deprecations"]
|
||||||
)
|
)
|
||||||
@@ -226,9 +228,9 @@ class ActionModuleBase(ActionBase, metaclass=abc.ABCMeta):
|
|||||||
|
|
||||||
def run(self, tmp=None, task_vars=None) -> dict[str, t.Any]:
|
def run(self, tmp=None, task_vars=None) -> dict[str, t.Any]:
|
||||||
if task_vars is None:
|
if task_vars is None:
|
||||||
task_vars = dict()
|
task_vars = {}
|
||||||
|
|
||||||
result = super(ActionModuleBase, self).run(tmp, task_vars)
|
result = super().run(tmp, task_vars)
|
||||||
del tmp # tmp no longer has any effect
|
del tmp # tmp no longer has any effect
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -25,8 +25,10 @@ class PluginGPGRunner(GPGRunner):
|
|||||||
if executable is None:
|
if executable is None:
|
||||||
try:
|
try:
|
||||||
executable = get_bin_path("gpg")
|
executable = get_bin_path("gpg")
|
||||||
except ValueError:
|
except ValueError as exc:
|
||||||
raise GPGError("Cannot find the `gpg` executable on the controller")
|
raise GPGError(
|
||||||
|
"Cannot find the `gpg` executable on the controller"
|
||||||
|
) from exc
|
||||||
self.executable = executable
|
self.executable = executable
|
||||||
self.cwd = cwd
|
self.cwd = cwd
|
||||||
|
|
||||||
@@ -45,17 +47,17 @@ class PluginGPGRunner(GPGRunner):
|
|||||||
Raises a ``GPGError`` in case of errors.
|
Raises a ``GPGError`` in case of errors.
|
||||||
"""
|
"""
|
||||||
command = [self.executable] + command
|
command = [self.executable] + command
|
||||||
p = Popen(
|
with Popen(
|
||||||
command, shell=False, cwd=self.cwd, stdin=PIPE, stdout=PIPE, stderr=PIPE
|
command, shell=False, cwd=self.cwd, stdin=PIPE, stdout=PIPE, stderr=PIPE
|
||||||
)
|
) as p:
|
||||||
stdout, stderr = p.communicate(input=data)
|
stdout, stderr = p.communicate(input=data)
|
||||||
stdout_n = to_native(stdout, errors="surrogate_or_replace")
|
stdout_n = to_native(stdout, errors="surrogate_or_replace")
|
||||||
stderr_n = to_native(stderr, errors="surrogate_or_replace")
|
stderr_n = to_native(stderr, errors="surrogate_or_replace")
|
||||||
if check_rc and p.returncode != 0:
|
if check_rc and p.returncode != 0:
|
||||||
raise GPGError(
|
raise GPGError(
|
||||||
f'Running {" ".join(command)} yielded return code {p.returncode} with stdout: "{stdout_n}" and stderr: "{stderr_n}")'
|
f'Running {" ".join(command)} yielded return code {p.returncode} with stdout: "{stdout_n}" and stderr: "{stderr_n}")'
|
||||||
)
|
)
|
||||||
return t.cast(int, p.returncode), stdout_n, stderr_n
|
return t.cast(int, p.returncode), stdout_n, stderr_n
|
||||||
|
|
||||||
|
|
||||||
__all__ = ("PluginGPGRunner",)
|
__all__ = ("PluginGPGRunner",)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ plugins/modules/acme_certificate_revoke.py pylint:unpacking-non-sequence
|
|||||||
plugins/modules/acme_inspect.py pylint:unpacking-non-sequence
|
plugins/modules/acme_inspect.py pylint:unpacking-non-sequence
|
||||||
plugins/modules/ecs_certificate.py no-assert
|
plugins/modules/ecs_certificate.py no-assert
|
||||||
plugins/modules/ecs_domain.py pep8:E704
|
plugins/modules/ecs_domain.py pep8:E704
|
||||||
|
plugins/modules/get_certificate.py pylint:unknown-option-value
|
||||||
plugins/modules/luks_device.py no-assert
|
plugins/modules/luks_device.py no-assert
|
||||||
plugins/modules/openssl_pkcs12.py no-assert
|
plugins/modules/openssl_pkcs12.py no-assert
|
||||||
tests/ee/roles/smoke/library/smoke_ipaddress.py shebang
|
tests/ee/roles/smoke/library/smoke_ipaddress.py shebang
|
||||||
|
|||||||
@@ -139,47 +139,61 @@ TEST_PARSE_ACME_TIMESTAMP: list[tuple[datetime.timedelta, str, dict[str, int]]]
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
"2024-01-01T00:11:22Z",
|
"2024-01-01T00:11:22Z",
|
||||||
dict(year=2024, month=1, day=1, hour=0, minute=11, second=22),
|
{
|
||||||
|
"year": 2024,
|
||||||
|
"month": 1,
|
||||||
|
"day": 1,
|
||||||
|
"hour": 0,
|
||||||
|
"minute": 11,
|
||||||
|
"second": 22,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"2024-01-01T00:11:22.123Z",
|
"2024-01-01T00:11:22.123Z",
|
||||||
dict(
|
{
|
||||||
year=2024,
|
"year": 2024,
|
||||||
month=1,
|
"month": 1,
|
||||||
day=1,
|
"day": 1,
|
||||||
hour=0,
|
"hour": 0,
|
||||||
minute=11,
|
"minute": 11,
|
||||||
second=22,
|
"second": 22,
|
||||||
microsecond=123000,
|
"microsecond": 123000,
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"2024-04-17T06:54:13.333333334Z",
|
"2024-04-17T06:54:13.333333334Z",
|
||||||
dict(
|
{
|
||||||
year=2024,
|
"year": 2024,
|
||||||
month=4,
|
"month": 4,
|
||||||
day=17,
|
"day": 17,
|
||||||
hour=6,
|
"hour": 6,
|
||||||
minute=54,
|
"minute": 54,
|
||||||
second=13,
|
"second": 13,
|
||||||
microsecond=333333,
|
"microsecond": 333333,
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"2024-01-01T00:11:22+0100",
|
"2024-01-01T00:11:22+0100",
|
||||||
dict(year=2023, month=12, day=31, hour=23, minute=11, second=22),
|
{
|
||||||
|
"year": 2023,
|
||||||
|
"month": 12,
|
||||||
|
"day": 31,
|
||||||
|
"hour": 23,
|
||||||
|
"minute": 11,
|
||||||
|
"second": 22,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"2024-01-01T00:11:22.123+0100",
|
"2024-01-01T00:11:22.123+0100",
|
||||||
dict(
|
{
|
||||||
year=2023,
|
"year": 2023,
|
||||||
month=12,
|
"month": 12,
|
||||||
day=31,
|
"day": 31,
|
||||||
hour=23,
|
"hour": 23,
|
||||||
minute=11,
|
"minute": 11,
|
||||||
second=22,
|
"second": 22,
|
||||||
microsecond=123000,
|
"microsecond": 123000,
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -192,22 +206,22 @@ TEST_INTERPOLATE_TIMESTAMP: list[
|
|||||||
TIMEZONES,
|
TIMEZONES,
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
|
||||||
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
|
||||||
0.0,
|
0.0,
|
||||||
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
|
||||||
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
|
||||||
0.5,
|
0.5,
|
||||||
dict(year=2024, month=1, day=1, hour=0, minute=30, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 30, "second": 0},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
|
||||||
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
|
||||||
1.0,
|
1.0,
|
||||||
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
|
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -216,6 +230,7 @@ TEST_INTERPOLATE_TIMESTAMP: list[
|
|||||||
class FakeBackend(CryptoBackend):
|
class FakeBackend(CryptoBackend):
|
||||||
def parse_key(
|
def parse_key(
|
||||||
self,
|
self,
|
||||||
|
*,
|
||||||
key_file: str | os.PathLike | None = None,
|
key_file: str | os.PathLike | None = None,
|
||||||
key_content: str | None = None,
|
key_content: str | None = None,
|
||||||
passphrase=None,
|
passphrase=None,
|
||||||
@@ -223,15 +238,16 @@ class FakeBackend(CryptoBackend):
|
|||||||
raise BackendException("Not implemented in fake backend")
|
raise BackendException("Not implemented in fake backend")
|
||||||
|
|
||||||
def sign(
|
def sign(
|
||||||
self, payload64: str, protected64: str, key_data: dict[str, t.Any] | None
|
self, *, payload64: str, protected64: str, key_data: dict[str, t.Any] | None
|
||||||
) -> t.NoReturn:
|
) -> t.NoReturn:
|
||||||
raise BackendException("Not implemented in fake backend")
|
raise BackendException("Not implemented in fake backend")
|
||||||
|
|
||||||
def create_mac_key(self, alg: str, key: str) -> t.NoReturn:
|
def create_mac_key(self, *, alg: str, key: str) -> t.NoReturn:
|
||||||
raise BackendException("Not implemented in fake backend")
|
raise BackendException("Not implemented in fake backend")
|
||||||
|
|
||||||
def get_ordered_csr_identifiers(
|
def get_ordered_csr_identifiers(
|
||||||
self,
|
self,
|
||||||
|
*,
|
||||||
csr_filename: str | os.PathLike | None = None,
|
csr_filename: str | os.PathLike | None = None,
|
||||||
csr_content: str | bytes | None = None,
|
csr_content: str | bytes | None = None,
|
||||||
) -> t.NoReturn:
|
) -> t.NoReturn:
|
||||||
@@ -239,6 +255,7 @@ class FakeBackend(CryptoBackend):
|
|||||||
|
|
||||||
def get_csr_identifiers(
|
def get_csr_identifiers(
|
||||||
self,
|
self,
|
||||||
|
*,
|
||||||
csr_filename: str | os.PathLike | None = None,
|
csr_filename: str | os.PathLike | None = None,
|
||||||
csr_content: str | bytes | None = None,
|
csr_content: str | bytes | None = None,
|
||||||
) -> t.NoReturn:
|
) -> t.NoReturn:
|
||||||
@@ -246,17 +263,19 @@ class FakeBackend(CryptoBackend):
|
|||||||
|
|
||||||
def get_cert_days(
|
def get_cert_days(
|
||||||
self,
|
self,
|
||||||
|
*,
|
||||||
cert_filename: str | os.PathLike | None = None,
|
cert_filename: str | os.PathLike | None = None,
|
||||||
cert_content: str | bytes | None = None,
|
cert_content: str | bytes | None = None,
|
||||||
now: datetime.datetime | None = None,
|
now: datetime.datetime | None = None,
|
||||||
) -> t.NoReturn:
|
) -> t.NoReturn:
|
||||||
raise BackendException("Not implemented in fake backend")
|
raise BackendException("Not implemented in fake backend")
|
||||||
|
|
||||||
def create_chain_matcher(self, criterium: Criterium) -> t.NoReturn:
|
def create_chain_matcher(self, *, criterium: Criterium) -> t.NoReturn:
|
||||||
raise BackendException("Not implemented in fake backend")
|
raise BackendException("Not implemented in fake backend")
|
||||||
|
|
||||||
def get_cert_information(
|
def get_cert_information(
|
||||||
self,
|
self,
|
||||||
|
*,
|
||||||
cert_filename: str | os.PathLike | None = None,
|
cert_filename: str | os.PathLike | None = None,
|
||||||
cert_content: str | bytes | None = None,
|
cert_content: str | bytes | None = None,
|
||||||
) -> t.NoReturn:
|
) -> t.NoReturn:
|
||||||
|
|||||||
@@ -136,15 +136,15 @@ def test_now(timezone: datetime.timedelta) -> None:
|
|||||||
assert now == datetime.datetime(2024, 2, 3, 4, 5, 6)
|
assert now == datetime.datetime(2024, 2, 3, 4, 5, 6)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timezone, input, expected", TEST_PARSE_ACME_TIMESTAMP)
|
@pytest.mark.parametrize("timezone, timestamp_str, expected", TEST_PARSE_ACME_TIMESTAMP)
|
||||||
def test_parse_acme_timestamp(
|
def test_parse_acme_timestamp(
|
||||||
timezone: datetime.timedelta, input: str, expected: dict[str, int]
|
timezone: datetime.timedelta, timestamp_str: str, expected: dict[str, int]
|
||||||
) -> None:
|
) -> None:
|
||||||
with freeze_time("2024-02-03 04:05:06 +00:00", tz_offset=timezone):
|
with freeze_time("2024-02-03 04:05:06 +00:00", tz_offset=timezone):
|
||||||
module = MagicMock()
|
module = MagicMock()
|
||||||
backend = CryptographyBackend(module=module)
|
backend = CryptographyBackend(module=module)
|
||||||
ts_expected = backend.get_utc_datetime(**expected)
|
ts_expected = backend.get_utc_datetime(**expected)
|
||||||
timestamp = backend.parse_acme_timestamp(input)
|
timestamp = backend.parse_acme_timestamp(timestamp_str)
|
||||||
assert ts_expected == timestamp
|
assert ts_expected == timestamp
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def test_csridentifiers_openssl(
|
|||||||
def test_normalize_ip(ip: str, result: str) -> None:
|
def test_normalize_ip(ip: str, result: str) -> None:
|
||||||
module = MagicMock()
|
module = MagicMock()
|
||||||
backend = OpenSSLCLIBackend(module=module, openssl_binary="openssl")
|
backend = OpenSSLCLIBackend(module=module, openssl_binary="openssl")
|
||||||
assert backend._normalize_ip(ip) == result
|
assert backend._normalize_ip(ip) == result # pylint: disable=protected-access
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timezone, now, expected_days", TEST_CERT_DAYS)
|
@pytest.mark.parametrize("timezone, now, expected_days", TEST_CERT_DAYS)
|
||||||
@@ -142,15 +142,15 @@ def test_now(timezone: datetime.timedelta) -> None:
|
|||||||
assert now == datetime.datetime(2024, 2, 3, 4, 5, 6, tzinfo=UTC)
|
assert now == datetime.datetime(2024, 2, 3, 4, 5, 6, tzinfo=UTC)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timezone, input, expected", TEST_PARSE_ACME_TIMESTAMP)
|
@pytest.mark.parametrize("timezone, timestamp_str, expected", TEST_PARSE_ACME_TIMESTAMP)
|
||||||
def test_parse_acme_timestamp(
|
def test_parse_acme_timestamp(
|
||||||
timezone: datetime.timedelta, input: str, expected: dict[str, int]
|
timezone: datetime.timedelta, timestamp_str: str, expected: dict[str, int]
|
||||||
) -> None:
|
) -> None:
|
||||||
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
|
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
|
||||||
module = MagicMock()
|
module = MagicMock()
|
||||||
backend = OpenSSLCLIBackend(module=module, openssl_binary="openssl")
|
backend = OpenSSLCLIBackend(module=module, openssl_binary="openssl")
|
||||||
ts_expected = backend.get_utc_datetime(**expected)
|
ts_expected = backend.get_utc_datetime(**expected)
|
||||||
timestamp = backend.parse_acme_timestamp(input)
|
timestamp = backend.parse_acme_timestamp(timestamp_str)
|
||||||
assert ts_expected == timestamp
|
assert ts_expected == timestamp
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ TEST_ACME_PROTOCOL_EXCEPTION: list[
|
|||||||
},
|
},
|
||||||
"response": create_regular_response("xxx"),
|
"response": create_regular_response("xxx"),
|
||||||
},
|
},
|
||||||
lambda content: dict(foo="bar"),
|
lambda content: {"foo": "bar"},
|
||||||
"ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}",
|
"ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}",
|
||||||
{
|
{
|
||||||
"http_url": "https://ca.example.com/foo",
|
"http_url": "https://ca.example.com/foo",
|
||||||
@@ -224,7 +224,7 @@ TEST_ACME_PROTOCOL_EXCEPTION: list[
|
|||||||
},
|
},
|
||||||
"response": create_error_response(),
|
"response": create_error_response(),
|
||||||
},
|
},
|
||||||
lambda content: dict(foo="bar"),
|
lambda content: {"foo": "bar"},
|
||||||
"ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}",
|
"ACME request failed for https://ca.example.com/foo with HTTP status 201 Created. The JSON error result: {'foo': 'bar'}",
|
||||||
{
|
{
|
||||||
"http_url": "https://ca.example.com/foo",
|
"http_url": "https://ca.example.com/foo",
|
||||||
@@ -345,9 +345,11 @@ TEST_ACME_PROTOCOL_EXCEPTION: list[
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("input, from_json, msg, args", TEST_ACME_PROTOCOL_EXCEPTION)
|
@pytest.mark.parametrize(
|
||||||
|
"parameters, from_json, msg, args", TEST_ACME_PROTOCOL_EXCEPTION
|
||||||
|
)
|
||||||
def test_acme_protocol_exception(
|
def test_acme_protocol_exception(
|
||||||
input: dict[str, t.Any],
|
parameters: dict[str, t.Any],
|
||||||
from_json: t.Callable[[t.Any], t.NoReturn] | None,
|
from_json: t.Callable[[t.Any], t.NoReturn] | None,
|
||||||
msg: str,
|
msg: str,
|
||||||
args: dict[str, t.Any],
|
args: dict[str, t.Any],
|
||||||
@@ -358,7 +360,7 @@ def test_acme_protocol_exception(
|
|||||||
module = MagicMock()
|
module = MagicMock()
|
||||||
module.from_json = from_json
|
module.from_json = from_json
|
||||||
with pytest.raises(ACMEProtocolException) as exc:
|
with pytest.raises(ACMEProtocolException) as exc:
|
||||||
raise ACMEProtocolException(module=module, **input) # type: ignore
|
raise ACMEProtocolException(module=module, **parameters) # type: ignore
|
||||||
|
|
||||||
print(exc.value.msg)
|
print(exc.value.msg)
|
||||||
print(exc.value.module_fail_args)
|
print(exc.value.module_fail_args)
|
||||||
|
|||||||
@@ -94,39 +94,51 @@ TEST_EPOCH_SECONDS: list[tuple[datetime.timedelta, float, dict[str, int]]] = (
|
|||||||
[
|
[
|
||||||
(
|
(
|
||||||
0,
|
0,
|
||||||
dict(
|
{
|
||||||
year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=0
|
"year": 1970,
|
||||||
),
|
"month": 1,
|
||||||
|
"day": 1,
|
||||||
|
"hour": 0,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0,
|
||||||
|
"microsecond": 0,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1e-6,
|
1e-6,
|
||||||
dict(
|
{
|
||||||
year=1970, day=1, month=1, hour=0, minute=0, second=0, microsecond=1
|
"year": 1970,
|
||||||
),
|
"month": 1,
|
||||||
|
"day": 1,
|
||||||
|
"hour": 0,
|
||||||
|
"minute": 0,
|
||||||
|
"second": 0,
|
||||||
|
"microsecond": 1,
|
||||||
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
1e-3,
|
1e-3,
|
||||||
dict(
|
{
|
||||||
year=1970,
|
"year": 1970,
|
||||||
day=1,
|
"month": 1,
|
||||||
month=1,
|
"day": 1,
|
||||||
hour=0,
|
"hour": 0,
|
||||||
minute=0,
|
"minute": 0,
|
||||||
second=0,
|
"second": 0,
|
||||||
microsecond=1000,
|
"microsecond": 1000,
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
3691.2,
|
3691.2,
|
||||||
dict(
|
{
|
||||||
year=1970,
|
"year": 1970,
|
||||||
day=1,
|
"month": 1,
|
||||||
month=1,
|
"day": 1,
|
||||||
hour=1,
|
"hour": 1,
|
||||||
minute=1,
|
"minute": 1,
|
||||||
second=31,
|
"second": 31,
|
||||||
microsecond=200000,
|
"microsecond": 200000,
|
||||||
),
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@@ -283,25 +295,29 @@ TEST_GET_RELATIVE_TIME_OPTION: list[
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timezone, input, expected", TEST_REMOVE_TIMEZONE)
|
@pytest.mark.parametrize("timezone, input_timestamp, expected", TEST_REMOVE_TIMEZONE)
|
||||||
def test_remove_timezone(
|
def test_remove_timezone(
|
||||||
timezone: datetime.timedelta, input: datetime.datetime, expected: datetime.datetime
|
timezone: datetime.timedelta,
|
||||||
|
input_timestamp: datetime.datetime,
|
||||||
|
expected: datetime.datetime,
|
||||||
) -> None:
|
) -> None:
|
||||||
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
|
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
|
||||||
output_1 = remove_timezone(input)
|
output_1 = remove_timezone(input_timestamp)
|
||||||
assert expected == output_1
|
assert expected == output_1
|
||||||
output_2 = add_or_remove_timezone(input, with_timezone=False)
|
output_2 = add_or_remove_timezone(input_timestamp, with_timezone=False)
|
||||||
assert expected == output_2
|
assert expected == output_2
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("timezone, input, expected", TEST_UTC_TIMEZONE)
|
@pytest.mark.parametrize("timezone, input_timestamp, expected", TEST_UTC_TIMEZONE)
|
||||||
def test_utc_timezone(
|
def test_utc_timezone(
|
||||||
timezone: datetime.timedelta, input: datetime.datetime, expected: datetime.datetime
|
timezone: datetime.timedelta,
|
||||||
|
input_timestamp: datetime.datetime,
|
||||||
|
expected: datetime.datetime,
|
||||||
) -> None:
|
) -> None:
|
||||||
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
|
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
|
||||||
output_1 = ensure_utc_timezone(input)
|
output_1 = ensure_utc_timezone(input_timestamp)
|
||||||
assert expected == output_1
|
assert expected == output_1
|
||||||
output_2 = add_or_remove_timezone(input, with_timezone=True)
|
output_2 = add_or_remove_timezone(input_timestamp, with_timezone=True)
|
||||||
assert expected == output_2
|
assert expected == output_2
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from ansible_collections.community.crypto.plugins.modules import luks_device
|
|||||||
class DummyModule:
|
class DummyModule:
|
||||||
# module to mock AnsibleModule class
|
# module to mock AnsibleModule class
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.params = dict()
|
self.params = {}
|
||||||
|
|
||||||
def fail_json(self, msg=""):
|
def fail_json(self, msg=""):
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user