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:
Felix Fontein
2025-05-18 00:57:28 +02:00
committed by GitHub
parent a3a5284f97
commit 318462fa24
96 changed files with 1748 additions and 1598 deletions

View File

@@ -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

View File

@@ -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",

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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

View File

@@ -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:
""" """

View File

@@ -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"]],

View File

@@ -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:

View File

@@ -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,

View File

@@ -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,

View File

@@ -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"',

View File

@@ -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
), ),
) )

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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:

View File

@@ -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)

View File

@@ -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

View File

@@ -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"],

View File

@@ -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",
) },
}
) )

View File

@@ -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(
( (

View File

@@ -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(
[ [

View File

@@ -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(
[ [

View File

@@ -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"],
},
}
) )

View File

@@ -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)

View File

@@ -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"],
], ],

View File

@@ -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(
[ [

View File

@@ -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"]),
], ],

View File

@@ -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"],
], ],

View File

@@ -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(

View File

@@ -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:

View File

@@ -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__ = (

View File

@@ -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 (

View File

@@ -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 ""

View File

@@ -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

View File

@@ -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]:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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(

View File

@@ -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,

View File

@@ -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)

View File

@@ -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")],

View File

@@ -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(

View File

@@ -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)

View File

@@ -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()

View File

@@ -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:

View File

@@ -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)

View File

@@ -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
], ],
) )

View File

@@ -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

View File

@@ -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"],

View File

@@ -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,

View File

@@ -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:

View File

@@ -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

View File

@@ -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)

View File

@@ -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:

View File

@@ -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:

View File

@@ -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
] ]

View File

@@ -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:

View File

@@ -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=[

View File

@@ -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,
) )

View File

@@ -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]

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,
) )

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,

View File

@@ -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")

View File

@@ -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=[

View File

@@ -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")

View File

@@ -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,

View File

@@ -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,

View File

@@ -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(

View File

@@ -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,

View File

@@ -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

View File

@@ -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,

View File

@@ -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),

View File

@@ -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,

View File

@@ -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:

View File

@@ -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",)

View File

@@ -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

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)