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,
locally-disabled,
suppressed-message,
useless-suppression,
use-symbolic-message-instead,
use-implicit-booleaness-not-comparison,
use-implicit-booleaness-not-comparison-to-string,
use-implicit-booleaness-not-comparison-to-zero,
superfluous-parens,
too-few-public-methods,
too-many-arguments,
too-many-boolean-expressions,
@@ -378,35 +377,13 @@ disable=raw-checker-failed,
wrong-import-order,
wrong-import-position,
# To clean up:
arguments-differ,
attribute-defined-outside-init,
broad-exception-caught,
broad-exception-raised,
consider-using-dict-items,
consider-using-in,
consider-using-set-comprehension,
consider-using-with,
fixme,
inconsistent-return-statements,
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-variable,
use-dict-literal,
# Cannot remove yet due to inadequacy of rules
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
# either give multiple identifier separated by comma (,) or put this option

View File

@@ -15,12 +15,13 @@ run_flake8 = true
flake8_config = ".flake8"
run_pylint = true
pylint_rcfile = ".pylintrc"
pylint_ansible_core_package = "ansible-core>=2.19.0b4"
run_yamllint = true
yamllint_config = ".yamllint"
yamllint_config_plugins = ".yamllint-docs"
yamllint_config_plugins_examples = ".yamllint-examples"
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_extra_deps = [
"cryptography",

View File

@@ -60,14 +60,14 @@ class PrivateKeyModule:
if self.module_backend.needs_regeneration():
# Regenerate
self.module_backend.generate_private_key()
privatekey_data = self.module_backend.get_private_key_data()
self.privatekey_bytes = privatekey_data
# Call get_private_key_data() to make sure that exceptions are raised now:
self.module_backend.get_private_key_data()
self.changed = True
elif self.module_backend.needs_conversion():
# Convert
self.module_backend.convert_private_key()
privatekey_data = self.module_backend.get_private_key_data()
self.privatekey_bytes = privatekey_data
# Call get_private_key_data() to make sure that exceptions are raised now:
self.module_backend.get_private_key_data()
self.changed = True
def dump(self) -> dict[str, t.Any]:
@@ -80,22 +80,20 @@ class PrivateKeyModule:
class ActionModule(ActionModuleBase):
@staticmethod
def setup_module() -> tuple[ArgumentSpec, dict[str, t.Any]]:
def setup_module(self) -> tuple[ArgumentSpec, dict[str, t.Any]]:
argument_spec = get_privatekey_argument_spec()
argument_spec.argument_spec.update(
dict(
content=dict(type="str", no_log=True),
content_base64=dict(type="bool", default=False),
return_current_key=dict(type="bool", default=False),
)
)
return argument_spec, dict(
supports_check_mode=True,
{
"content": {"type": "str", "no_log": True},
"content_base64": {"type": "bool", "default": False},
"return_current_key": {"type": "bool", "default": False},
}
)
return argument_spec, {
"supports_check_mode": True,
}
@staticmethod
def run_module(module: AnsibleActionModule) -> None:
def run_module(self, module: AnsibleActionModule) -> None:
module_backend = select_backend(module=module)
try:

View File

@@ -52,16 +52,18 @@ from ansible_collections.community.crypto.plugins.plugin_utils._gnupg import (
)
def gpg_fingerprint(input: str | bytes) -> str:
if not isinstance(input, (str, bytes)):
def gpg_fingerprint(gpg_key_content: str | bytes) -> str:
if not isinstance(gpg_key_content, (str, bytes)):
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:
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:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
class FilterModule:

View File

@@ -313,7 +313,7 @@ def openssl_csr_info_filter(
module=module, content=to_bytes(data), validate_signature=True
)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
class FilterModule:

View File

@@ -193,9 +193,9 @@ def openssl_privatekey_info_filter(
result.pop("key_is_consistent", None)
return result
except PrivateKeyParseError as exc:
raise AnsibleFilterError(exc.error_message)
raise AnsibleFilterError(exc.error_message) from exc
except OpenSSLObjectError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
class FilterModule:

View File

@@ -150,9 +150,9 @@ def openssl_publickey_info_filter(data: str | bytes) -> dict[str, t.Any]:
try:
return get_publickey_info(module=module, content=to_bytes(data))
except PublicKeyParseError as exc:
raise AnsibleFilterError(exc.error_message)
raise AnsibleFilterError(exc.error_message) from exc
except OpenSSLObjectError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
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:
if not isinstance(input, (str, bytes)):
def parse_serial_filter(serial_str: str | bytes) -> int:
if not isinstance(serial_str, (str, bytes)):
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:
return parse_serial(to_native(input))
return parse_serial(to_native(serial_str))
except ValueError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
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
def to_serial_filter(input: int) -> str:
if not isinstance(input, int):
def to_serial_filter(serial_int: int) -> str:
if not isinstance(serial_int, int):
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(
"The input for the community.crypto.to_serial filter must not be negative"
)
try:
return to_serial(input)
return to_serial(serial_int)
except ValueError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
class FilterModule:

View File

@@ -345,7 +345,7 @@ def x509_certificate_info_filter(
try:
return get_certificate_info(module=module, content=to_bytes(data))
except OpenSSLObjectError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
class FilterModule:

View File

@@ -212,7 +212,7 @@ def x509_crl_info_filter(
list_revoked_certificates=list_revoked_certificates,
)
except OpenSSLObjectError as exc:
raise AnsibleFilterError(str(exc))
raise AnsibleFilterError(str(exc)) from exc
class FilterModule:

View File

@@ -76,4 +76,4 @@ class LookupModule(LookupBase):
)
return result
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:
self.client.set_account_uri(info["location"])
return True, result
elif info["status"] == 200:
if info["status"] == 200:
# Account did exist
if result.get("status") == "deactivated":
# 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."
if not allow_creation:
return False, None
else:
raise ModuleFailException("Account is deactivated")
raise ModuleFailException("Account is deactivated")
if "location" in info:
self.client.set_account_uri(info["location"])
return False, result
elif (
if (
info["status"] in (400, 404)
and result["type"] == "urn:ietf:params:acme:error:accountDoesNotExist"
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.
# Unfortunately Digicert does not care and sends 404 instead.)
return False, None
elif (
if (
info["status"] == 403
and result["type"] == "urn:ietf:params:acme:error:unauthorized"
and "deactivated" in (result.get("detail") or "")
@@ -154,15 +153,13 @@ class ACMEAccount:
# might need adjustment in error detection.
if not allow_creation:
return False, None
else:
raise ModuleFailException("Account is deactivated")
else:
raise ACMEProtocolException(
module=self.client.module,
msg="Registering ACME account failed",
info=info,
content_json=result,
)
raise ModuleFailException("Account is deactivated")
raise ACMEProtocolException(
module=self.client.module,
msg="Registering ACME account failed",
info=info,
content_json=result,
)
def get_account_data(self) -> dict[str, t.Any] | None:
"""

View File

@@ -244,7 +244,9 @@ class ACMEClient:
passphrase=self.account_key_passphrase,
)
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_jws_header = {
"alg": self.account_key_data["alg"],
@@ -307,7 +309,7 @@ class ACMEClient:
except Exception as e:
raise ModuleFailException(
f"Failed to encode payload / headers as JSON: {e}"
)
) from e
return self.backend.sign(
payload64=payload64, protected64=protected64, key_data=key_data
@@ -456,10 +458,10 @@ class ACMEClient:
result = decoded_result
else:
result = content
except ValueError:
except ValueError as exc:
raise NetworkException(
f"Failed to parse the ACME response: {url} {content}"
)
) from exc
else:
result = content
@@ -569,10 +571,10 @@ class ACMEClient:
try:
result = self.module.from_json(content.decode("utf8"))
parsed_json_result = True
except ValueError:
except ValueError as exc:
raise NetworkException(
f"Failed to parse the ACME response: {uri} {content!r}"
)
) from exc
else:
result = content
else:
@@ -642,30 +644,32 @@ def create_default_argspec(
Provides default argument spec for the options documented in the acme doc fragment.
"""
result = ArgumentSpec(
argument_spec=dict(
acme_directory=dict(type="str", required=True),
acme_version=dict(type="int", choices=[2], default=2),
validate_certs=dict(type="bool", default=True),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "openssl", "cryptography"]
),
request_timeout=dict(type="int", default=10),
),
argument_spec={
"acme_directory": {"type": "str", "required": True},
"acme_version": {"type": "int", "choices": [2], "default": 2},
"validate_certs": {"type": "bool", "default": True},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "openssl", "cryptography"],
},
"request_timeout": {"type": "int", "default": 10},
},
)
if with_account:
result.update_argspec(
account_key_src=dict(type="path", aliases=["account_key"]),
account_key_content=dict(type="str", no_log=True),
account_key_passphrase=dict(type="str", no_log=True),
account_uri=dict(type="str"),
account_key_src={"type": "path", "aliases": ["account_key"]},
account_key_content={"type": "str", "no_log": True},
account_key_passphrase={"type": "str", "no_log": True},
account_uri={"type": "str"},
)
if require_account_key:
result.update(required_one_of=[["account_key_src", "account_key_content"]])
result.update(mutually_exclusive=[["account_key_src", "account_key_content"]])
if with_certificate:
result.update_argspec(
csr=dict(type="path"),
csr_content=dict(type="str"),
csr={"type": "path"},
csr_content={"type": "str"},
)
result.update(
required_one_of=[["csr", "csr_content"]],

View File

@@ -211,9 +211,7 @@ class CryptographyChainMatcher(ChainMatcher):
class CryptographyBackend(CryptoBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(CryptographyBackend, self).__init__(
module=module, with_timezone=CRYPTOGRAPHY_TIMEZONE
)
super().__init__(module=module, with_timezone=CRYPTOGRAPHY_TIMEZONE)
def parse_key(
self,
@@ -242,7 +240,7 @@ class CryptographyBackend(CryptoBackend):
password=to_bytes(passphrase) if passphrase is not None else None,
)
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):
rsa_pk = key.public_key().public_numbers()
return {
@@ -256,7 +254,7 @@ class CryptographyBackend(CryptoBackend):
},
"hash": "sha256",
}
elif isinstance(
if isinstance(
key, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey
):
ec_pk = key.public_key().public_numbers()
@@ -296,8 +294,7 @@ class CryptographyBackend(CryptoBackend):
"hash": hashalg,
"point_size": point_size,
}
else:
raise KeyParsingError(f'unknown key type "{type(key)}"')
raise KeyParsingError(f'unknown key type "{type(key)}"')
def sign(
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"])
ss = convert_int_to_hex(s, digits=2 * key_data["point_size"])
signature = binascii.unhexlify(rr) + binascii.unhexlify(ss)
else:
raise AssertionError("Can never be reached") # pragma: no cover
return {
"protected": protected64,
@@ -472,8 +471,10 @@ class CryptographyBackend(CryptoBackend):
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
except Exception as e:
if cert_filename is None:
raise BackendException(f"Cannot parse certificate: {e}")
raise BackendException(f"Cannot parse certificate {cert_filename}: {e}")
raise BackendException(f"Cannot parse certificate: {e}") from e
raise BackendException(
f"Cannot parse certificate {cert_filename}: {e}"
) from e
if now is None:
now = self.get_now()
@@ -508,8 +509,10 @@ class CryptographyBackend(CryptoBackend):
cert = cryptography.x509.load_pem_x509_certificate(b_cert_content)
except Exception as e:
if cert_filename is None:
raise BackendException(f"Cannot parse certificate: {e}")
raise BackendException(f"Cannot parse certificate {cert_filename}: {e}")
raise BackendException(f"Cannot parse certificate: {e}") from e
raise BackendException(
f"Cannot parse certificate {cert_filename}: {e}"
) from e
ski = None
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(
@@ -66,7 +71,7 @@ def _extract_date(
except ValueError as exc:
raise BackendException(
f"Failed to parse '{name}' date{cert_filename_suffix}: {exc}"
)
) from exc
def _decode_octets(octets_text: str) -> bytes:
@@ -118,7 +123,7 @@ class OpenSSLCLIBackend(CryptoBackend):
def __init__(
self, *, module: AnsibleModule, openssl_binary: str | None = None
) -> None:
super(OpenSSLCLIBackend, self).__init__(module=module, with_timezone=True)
super().__init__(module=module, with_timezone=True)
if openssl_binary is None:
openssl_binary = module.get_bin_path("openssl", True)
self.openssl_binary = openssl_binary
@@ -156,11 +161,11 @@ class OpenSSLCLIBackend(CryptoBackend):
raise KeyParsingError(
f"failed to create temporary content file: {err}",
exception=traceback.format_exc(),
)
) from err
f.close()
# Parse key
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:
m = re.match(
r"^\s*-{5,}BEGIN\s+(EC|RSA)\s+PRIVATE\s+KEY-{5,}\s*$", line
@@ -228,7 +233,7 @@ class OpenSSLCLIBackend(CryptoBackend):
},
"hash": "sha256",
}
elif account_key_type == "ec":
if account_key_type == "ec":
pub_data = re.search(
r"pub:\s*\n\s+04:([a-f0-9\:\s]+?)\nASN1 OID: (\S+)(?:\nNIST CURVE: (\S+))?",
out_text,

View File

@@ -77,14 +77,14 @@ def _parse_acme_timestamp(
"""
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
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:%S.%fZ",
"%Y-%m-%dT%H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S.%f%z",
):
try:
result = datetime.datetime.strptime(timestamp_str, format)
result = datetime.datetime.strptime(timestamp_str, time_format)
except ValueError:
pass
else:
@@ -117,7 +117,7 @@ class CryptoBackend(metaclass=abc.ABCMeta):
raise BackendException(f"Invalid value for {name}: {value!r}")
return result
except OpenSSLObjectError as exc:
raise BackendException(str(exc))
raise BackendException(str(exc)) from exc
def interpolate_timestamp(
self,

View File

@@ -166,11 +166,11 @@ class ACMECertificateClient:
continue
challenge_data = authz.get_challenge_data(client=self.client)
data.append(
dict(
identifier=authz.identifier,
identifier_type=authz.identifier_type,
challenges=challenge_data,
)
{
"identifier": authz.identifier,
"identifier_type": authz.identifier_type,
"challenges": challenge_data,
}
)
dns_challenge = challenge_data.get(dns_challenge_type)
if dns_challenge:
@@ -321,7 +321,7 @@ class ACMECertificateClient:
"""
if self.csr is None and self.csr_content is None:
raise ModuleFailException("No CSR has been provided")
for identifier, authz in order.authorizations.items():
for authz in order.authorizations.values():
if authz.status != "valid":
authz.raise_error(
error_msg=f'Status is {authz.status!r} and not "valid"',

View File

@@ -71,7 +71,7 @@ class CertificateChain:
process_links(
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
),
)

View File

@@ -295,10 +295,10 @@ class Authorization:
raise ACMEProtocolException(
module=module,
msg=f"Failed to validate challenge for {self.combined_identifier}: {error_msg}. {'; '.join(error_details)}",
extras=dict(
identifier=self.combined_identifier,
authorization=self.data,
),
extras={
"identifier": self.combined_identifier,
"authorization": self.data,
},
)
def find_challenge(self, *, challenge_type: str) -> Challenge | None:
@@ -374,7 +374,7 @@ class Authorization:
"""
authz = cls(url=url)
authz_deactivate = {"status": "deactivated"}
result, info = client.send_signed_request(
result, _info = client.send_signed_request(
url, authz_deactivate, fail_on_error=True
)
authz._setup(client=client, data=result)

View File

@@ -40,12 +40,12 @@ def format_error_problem(
subproblems = problem.get("subproblems")
if subproblems is not None:
msg = f"{msg} Subproblems:"
for index, problem in enumerate(subproblems):
for index, subproblem in enumerate(subproblems):
index_str = f"{subproblem_prefix}{index}"
problem_str = format_error_problem(
problem, subproblem_prefix=f"{index_str}."
subproblem_str = format_error_problem(
subproblem, subproblem_prefix=f"{index_str}."
)
msg = f"{msg}\n({index_str}) {problem_str}"
msg = f"{msg}\n({index_str}) {subproblem_str}"
return msg
@@ -55,7 +55,7 @@ class ModuleFailException(Exception):
"""
def __init__(self, msg: str, **args: t.Any) -> None:
super(ModuleFailException, self).__init__(self, msg)
super().__init__(self, msg)
self.msg = msg
self.module_fail_args = args
@@ -100,7 +100,7 @@ class ACMEProtocolException(ModuleFailException):
except Exception:
pass
extras = extras or dict()
extras = extras or {}
error_code = None
error_type = None
@@ -152,7 +152,7 @@ class ACMEProtocolException(ModuleFailException):
elif content is not None:
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.subproblems: list[dict[str, t.Any]] = []
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:
return f.read()
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
@@ -55,7 +55,7 @@ def write_file(
raise ModuleFailException(
f"failed to create temporary content file: {err}",
exception=traceback.format_exc(),
)
) from err
f.close()
checksum_src = None
checksum_dest = None
@@ -94,7 +94,7 @@ def write_file(
raise ModuleFailException(
f"failed to copy {tmpsrc} to {dest}: {err}",
exception=traceback.format_exc(),
)
) from err
os.remove(tmpsrc)
return changed

View File

@@ -60,13 +60,13 @@ def pem_to_der(
lines = pem_content.splitlines()
elif pem_filename is not None:
try:
with open(pem_filename, "rt") as f:
with open(pem_filename, "r", encoding="utf-8") as f:
lines = list(f)
except Exception as err:
raise ModuleFailException(
f"cannot load PEM file {pem_filename}: {err}",
exception=traceback.format_exc(),
)
) from err
else:
raise ModuleFailException(
"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()
NORMALIZE_NAMES: dict[str, str] = dict()
NORMALIZE_NAMES_SHORT: dict[str, str] = dict()
OID_LOOKUP: dict[str, str] = {}
NORMALIZE_NAMES: dict[str, str] = {}
NORMALIZE_NAMES_SHORT: dict[str, str] = {}
for dotted, names in OID_MAP.items():
for name in names:

View File

@@ -62,13 +62,13 @@ if HAS_CRYPTOGRAPHY:
"aa_compromise": x509.ReasonFlags.aa_compromise,
"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():
REVOCATION_REASON_MAP_INVERSE[v] = k
else:
REVOCATION_REASON_MAP = dict()
REVOCATION_REASON_MAP_INVERSE = dict()
REVOCATION_REASON_MAP = {}
REVOCATION_REASON_MAP_INVERSE = {}
def cryptography_decode_revoked_certificate(
@@ -145,7 +145,9 @@ def cryptography_get_signature_algorithm_oid_from_crl(
except AttributeError:
# Older cryptography versions do not have signature_algorithm_oid yet
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)

View File

@@ -113,17 +113,17 @@ if t.TYPE_CHECKING:
PKCS12KeyAndCertificates,
)
CertificatePrivateKeyTypes = (
CertificateIssuerPrivateKeyTypes
| cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey
| cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey
)
PublicKeyTypesWOEdwards = (
DHPublicKey | DSAPublicKey | EllipticCurvePublicKey | RSAPublicKey
)
PrivateKeyTypesWOEdwards = (
DHPrivateKey | DSAPrivateKey | EllipticCurvePrivateKey | RSAPrivateKey
)
CertificatePrivateKeyTypes = t.Union[
CertificateIssuerPrivateKeyTypes,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey,
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey,
]
PublicKeyTypesWOEdwards = t.Union[
DHPublicKey, DSAPublicKey, EllipticCurvePublicKey, RSAPublicKey
]
PrivateKeyTypesWOEdwards = t.Union[
DHPrivateKey, DSAPrivateKey, EllipticCurvePrivateKey, RSAPrivateKey
]
else:
PublicKeyTypesWOEdwards = None
PrivateKeyTypesWOEdwards = None
@@ -146,14 +146,14 @@ DOTTED_OID = re.compile(r"^\d+(?:\.\d+)+$")
def cryptography_get_extensions_from_cert(
cert: x509.Certificate,
) -> dict[str, dict[str, bool | str]]:
result = dict()
result = {}
if _CRYPTOGRAPHY_36_0_OR_NEWER:
for ext in cert.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()).decode("ascii"),
)
result[ext.oid.dotted_string] = {
"critical": ext.critical,
"value": base64.b64encode(ext.value.public_bytes()).decode("ascii"),
}
else:
# Since cryptography will not give us the DER value for an extension
# (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()
# We access a *lot* of internal APIs here, so let's disable that message...
# pylint: disable=protected-access
x509_obj = cert._x509 # type: ignore
# 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:
@@ -175,10 +178,10 @@ def cryptography_get_extensions_from_cert(
data = backend._lib.X509_EXTENSION_get_data(ext)
backend.openssl_assert(data != backend._ffi.NULL)
der = backend._ffi.buffer(data.data, data.length)[:]
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der).decode("ascii"),
)
entry = {
"critical": (crit == 1),
"value": base64.b64encode(der).decode("ascii"),
}
try:
oid = obj2txt(
backend._lib,
@@ -195,14 +198,14 @@ def cryptography_get_extensions_from_cert(
def cryptography_get_extensions_from_csr(
csr: x509.CertificateSigningRequest,
) -> dict[str, dict[str, bool | str]]:
result = dict()
result = {}
if _CRYPTOGRAPHY_36_0_OR_NEWER:
for ext in csr.extensions:
result[ext.oid.dotted_string] = dict(
critical=ext.critical,
value=base64.b64encode(ext.value.public_bytes()).decode("ascii"),
)
result[ext.oid.dotted_string] = {
"critical": ext.critical,
"value": base64.b64encode(ext.value.public_bytes()).decode("ascii"),
}
else:
# 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()
# 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._ffi.gc(
extensions,
@@ -235,10 +241,10 @@ def cryptography_get_extensions_from_csr(
data = backend._lib.X509_EXTENSION_get_data(ext)
backend.openssl_assert(data != backend._ffi.NULL)
der: bytes = backend._ffi.buffer(data.data, data.length)[:] # type: ignore
entry = dict(
critical=(crit == 1),
value=base64.b64encode(der).decode("ascii"),
)
entry = {
"critical": (crit == 1),
"value": base64.b64encode(der).decode("ascii"),
}
try:
oid = obj2txt(
backend._lib,
@@ -269,13 +275,15 @@ def cryptography_oid_to_name(
if names:
name = names[0]
else:
name = oid._name
if name == "Unknown OID":
try:
name = oid._name # pylint: disable=protected-access
if name == "Unknown OID":
name = dotted_string
except AttributeError:
name = dotted_string
if short:
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:
@@ -393,7 +401,7 @@ def _parse_dn(name: bytes) -> list[x509.NameAttribute]:
except OpenSSLObjectError as e:
raise OpenSSLObjectError(
f"Error while parsing distinguished name {to_text(original_name)!r}: {e}"
)
) from e
result.append(attribute)
if name:
if name[0:1] != sep or len(name) < 2:
@@ -414,7 +422,7 @@ def cryptography_parse_relative_distinguished_name(
except OpenSSLObjectError as e:
raise OpenSSLObjectError(
f"Error while parsing relative distinguished name {to_text(part)!r}: {e}"
)
) from e
return cryptography.x509.RelativeDistinguishedName(names)
@@ -468,7 +476,7 @@ def _adjust_idn(
raise OpenSSLObjectError(
f'Error while transforming part "{part}" of {what} DNS name "{value}" to {dest}.'
f' IDNA2008 transformation resulted in "{exc2008}", IDNA2003 transformation resulted in "{exc2003}".'
)
) from exc2003
return ".".join(parts)
@@ -561,7 +569,7 @@ def cryptography_get_name(
x509.Name(reversed(_parse_dn(to_bytes(name[8:]))))
)
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:
raise OpenSSLObjectError(
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().
Raises an OpenSSLObjectError if an identifier is unknown.
"""
params = dict(
digital_signature=False,
content_commitment=False,
key_encipherment=False,
data_encipherment=False,
key_agreement=False,
key_cert_sign=False,
crl_sign=False,
encipher_only=False,
decipher_only=False,
)
params = {
"digital_signature": False,
"content_commitment": False,
"key_encipherment": False,
"data_encipherment": False,
"key_agreement": False,
"key_cert_sign": False,
"crl_sign": False,
"encipher_only": False,
"decipher_only": False,
}
for usage in usages:
params[_cryptography_get_keyusage(usage)] = True
return params
@@ -699,7 +707,7 @@ def cryptography_get_basic_constraints(
except Exception as e:
raise OpenSSLObjectError(
f'Cannot parse path length constraint "{v}" ({e})'
)
) from e
else:
raise OpenSSLObjectError(f'Unknown basic constraint "{constraint}"')
return ca, path_length
@@ -901,6 +909,9 @@ def _parse_pkcs12_35_0_0(
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.
# Since load_key_and_certificates succeeded, it should not fail.
pkcs12 = backend._ffi.gc(
@@ -944,6 +955,9 @@ def _parse_pkcs12_legacy(
pkcs12_bytes, passphrase
)
# We access a *lot* of internal APIs here, so let's disable that message...
# pylint: disable=protected-access
friendly_name = None
if certificate:
# 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
return result
except Exception:
return dict(can_parse_certificate=False)
return {"can_parse_certificate": False}
@abc.abstractmethod
def generate_certificate(self) -> None:
@@ -143,7 +143,7 @@ class CertificateBackend(metaclass=abc.ABCMeta):
passphrase=self.privatekey_passphrase,
)
except OpenSSLBadPassphraseError as exc:
raise CertificateError(exc)
raise CertificateError(exc) from exc
def _ensure_csr_loaded(self) -> None:
"""Load the CSR into self.csr."""
@@ -380,25 +380,28 @@ def select_backend(
def get_certificate_argument_spec() -> ArgumentSpec:
return ArgumentSpec(
argument_spec=dict(
provider=dict(
type="str", choices=[]
), # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py
force=dict(
type="bool",
default=False,
),
csr_path=dict(type="path"),
csr_content=dict(type="str"),
ignore_timestamps=dict(type="bool", default=True),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
argument_spec={
"provider": {
"type": "str",
"choices": [],
}, # choices will be filled by add_XXX_provider_to_argument_spec() in certificate_xxx.py
"force": {
"type": "bool",
"default": False,
},
"csr_path": {"type": "path"},
"csr_content": {"type": "str"},
"ignore_timestamps": {"type": "bool", "default": True},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
# General properties of a certificate
privatekey_path=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
privatekey_passphrase=dict(type="str", no_log=True),
),
"privatekey_path": {"type": "path"},
"privatekey_content": {"type": "str", "no_log": True},
"privatekey_passphrase": {"type": "str", "no_log": True},
},
mutually_exclusive=[
["csr_path", "csr_content"],
["privatekey_path", "privatekey_content"],

View File

@@ -30,7 +30,7 @@ if t.TYPE_CHECKING:
class AcmeCertificateBackend(CertificateBackend):
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.challenge_path: str = module.params["acme_challenge_path"]
self.use_chain: bool = module.params["acme_chain"]
@@ -94,7 +94,7 @@ class AcmeCertificateBackend(CertificateBackend):
self.module.run_command(command, check_rc=True)[1]
)
except OSError as exc:
raise CertificateError(exc)
raise CertificateError(exc) from exc
def get_certificate_data(self) -> bytes:
"""Return bytes for self.cert."""
@@ -103,9 +103,7 @@ class AcmeCertificateBackend(CertificateBackend):
return self.cert_bytes
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
result = super(AcmeCertificateBackend, self).dump(
include_certificate=include_certificate
)
result = super().dump(include_certificate=include_certificate)
result["accountkey"] = self.accountkey_path
return result
@@ -128,14 +126,15 @@ class AcmeCertificateProvider(CertificateProvider):
def add_acme_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("acme")
argument_spec.argument_spec.update(
dict(
acme_accountkey_path=dict(type="path"),
acme_challenge_path=dict(type="path"),
acme_chain=dict(type="bool", default=False),
acme_directory=dict(
type="str", default="https://acme-v02.api.letsencrypt.org/directory"
),
)
{
"acme_accountkey_path": {"type": "path"},
"acme_challenge_path": {"type": "path"},
"acme_chain": {"type": "bool", "default": False},
"acme_directory": {
"type": "str",
"default": "https://acme-v02.api.letsencrypt.org/directory",
},
}
)

View File

@@ -51,13 +51,14 @@ except ImportError:
class EntrustCertificateBackend(CertificateBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(EntrustCertificateBackend, self).__init__(module=module)
super().__init__(module=module)
self.trackingId = None
self.notAfter = get_relative_time_option(
module.params["entrust_not_after"],
input_name="entrust_not_after",
with_timezone=CRYPTOGRAPHY_TIMEZONE,
)
self.cert_bytes: bytes | None = None
if self.csr_content is None:
if self.csr_path is None:
@@ -119,7 +120,7 @@ class EntrustCertificateBackend(CertificateBackend):
body["csr"] = to_native(self.csr_content)
else:
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["certType"] = self.module.params["entrust_cert_type"]
@@ -157,6 +158,8 @@ class EntrustCertificateBackend(CertificateBackend):
def get_certificate_data(self) -> bytes:
"""Return bytes for self.cert."""
if self.cert_bytes is None:
raise AssertionError("Contract violation: cert_bytes not set")
return self.cert_bytes
def needs_regeneration(
@@ -165,7 +168,7 @@ class EntrustCertificateBackend(CertificateBackend):
not_before: datetime.datetime | None = None,
not_after: datetime.datetime | None = None,
) -> bool:
parent_check = super(EntrustCertificateBackend, self).needs_regeneration()
parent_check = super().needs_regeneration()
try:
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
status = cert_details.get("status", False)
if status == "EXPIRED" or status == "SUSPENDED" or status == "REVOKED":
if status in ("EXPIRED", "SUSPENDED", "REVOKED"):
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
@@ -239,11 +242,11 @@ class EntrustCertificateProvider(CertificateProvider):
def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("entrust")
argument_spec.argument_spec.update(
dict(
entrust_cert_type=dict(
type="str",
default="STANDARD_SSL",
choices=[
{
"entrust_cert_type": {
"type": "str",
"default": "STANDARD_SSL",
"choices": [
"STANDARD_SSL",
"ADVANTAGE_SSL",
"UC_SSL",
@@ -255,20 +258,20 @@ def add_entrust_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
"CDS_ENT_PRO",
"SMIME_ENT",
],
),
entrust_requester_email=dict(type="str"),
entrust_requester_name=dict(type="str"),
entrust_requester_phone=dict(type="str"),
entrust_api_user=dict(type="str"),
entrust_api_key=dict(type="str", no_log=True),
entrust_api_client_cert_path=dict(type="path"),
entrust_api_client_cert_key_path=dict(type="path", no_log=True),
entrust_api_specification_path=dict(
type="path",
default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
),
entrust_not_after=dict(type="str", default="+365d"),
)
},
"entrust_requester_email": {"type": "str"},
"entrust_requester_name": {"type": "str"},
"entrust_requester_phone": {"type": "str"},
"entrust_api_user": {"type": "str"},
"entrust_api_key": {"type": "str", "no_log": True},
"entrust_api_client_cert_path": {"type": "path"},
"entrust_api_client_cert_key_path": {"type": "path", "no_log": True},
"entrust_api_specification_path": {
"type": "path",
"default": "https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
},
"entrust_not_after": {"type": "str", "default": "+365d"},
}
)
argument_spec.required_if.append(
(

View File

@@ -70,6 +70,8 @@ TIMESTAMP_FORMAT = "%Y%m%d%H%M%SZ"
class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
cert: x509.Certificate
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
# content must be a bytes string
self.module = module
@@ -169,11 +171,11 @@ class CertificateInfoRetrieval(metaclass=abc.ABCMeta):
result["signature_algorithm"] = self._get_signature_algorithm()
subject = self._get_subject_ordered()
issuer = self._get_issuer_ordered()
result["subject"] = dict()
result["subject"] = {}
for k, v in subject:
result["subject"][k] = v
result["subject_ordered"] = subject
result["issuer"] = dict()
result["issuer"] = {}
for k, v in issuer:
result["issuer"][k] = v
result["issuer_ordered"] = issuer
@@ -249,9 +251,7 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
"""Validate the supplied cert, using the cryptography backend"""
def __init__(self, *, module: GeneralAnsibleModule, content: bytes) -> None:
super(CertificateInfoRetrievalCryptography, self).__init__(
module=module, content=content
)
super().__init__(module=module, content=content)
self.name_encoding = module.params.get("name_encoding", "ignore")
def _get_der_bytes(self) -> bytes:
@@ -289,36 +289,36 @@ class CertificateInfoRetrievalCryptography(CertificateInfoRetrieval):
x509.KeyUsage
)
current_key_usage = current_key_ext.value
key_usage = dict(
digital_signature=current_key_usage.digital_signature,
content_commitment=current_key_usage.content_commitment,
key_encipherment=current_key_usage.key_encipherment,
data_encipherment=current_key_usage.data_encipherment,
key_agreement=current_key_usage.key_agreement,
key_cert_sign=current_key_usage.key_cert_sign,
crl_sign=current_key_usage.crl_sign,
encipher_only=False,
decipher_only=False,
)
key_usage = {
"digital_signature": current_key_usage.digital_signature,
"content_commitment": current_key_usage.content_commitment,
"key_encipherment": current_key_usage.key_encipherment,
"data_encipherment": current_key_usage.data_encipherment,
"key_agreement": current_key_usage.key_agreement,
"key_cert_sign": current_key_usage.key_cert_sign,
"crl_sign": current_key_usage.crl_sign,
"encipher_only": False,
"decipher_only": False,
}
if key_usage["key_agreement"]:
key_usage.update(
dict(
encipher_only=current_key_usage.encipher_only,
decipher_only=current_key_usage.decipher_only,
)
{
"encipher_only": current_key_usage.encipher_only,
"decipher_only": current_key_usage.decipher_only,
}
)
key_usage_names = dict(
digital_signature="Digital Signature",
content_commitment="Non Repudiation",
key_encipherment="Key Encipherment",
data_encipherment="Data Encipherment",
key_agreement="Key Agreement",
key_cert_sign="Certificate Sign",
crl_sign="CRL Sign",
encipher_only="Encipher Only",
decipher_only="Decipher Only",
)
key_usage_names = {
"digital_signature": "Digital Signature",
"content_commitment": "Non Repudiation",
"key_encipherment": "Key Encipherment",
"data_encipherment": "Data Encipherment",
"key_agreement": "Key Agreement",
"key_cert_sign": "Certificate Sign",
"crl_sign": "CRL Sign",
"encipher_only": "Encipher Only",
"decipher_only": "Decipher Only",
}
return (
sorted(
[

View File

@@ -63,7 +63,7 @@ except ImportError:
class OwnCACertificateBackendCryptography(CertificateBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(OwnCACertificateBackendCryptography, self).__init__(module=module)
super().__init__(module=module)
self.create_subject_key_identifier: t.Literal[
"create_if_not_provided", "always_create", "never_create"
@@ -223,7 +223,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
not_before: datetime.datetime | None = None,
not_after: datetime.datetime | None = None,
) -> bool:
if super(OwnCACertificateBackendCryptography, self).needs_regeneration(
if super().needs_regeneration(
not_before=self.notBefore, not_after=self.notAfter
):
return True
@@ -272,9 +272,7 @@ class OwnCACertificateBackendCryptography(CertificateBackend):
return False
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
result = super(OwnCACertificateBackendCryptography, self).dump(
include_certificate=include_certificate
)
result = super().dump(include_certificate=include_certificate)
result.update(
{
"ca_cert": self.ca_cert_path,
@@ -343,23 +341,23 @@ class OwnCACertificateProvider(CertificateProvider):
def add_ownca_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("ownca")
argument_spec.argument_spec.update(
dict(
ownca_path=dict(type="path"),
ownca_content=dict(type="str"),
ownca_privatekey_path=dict(type="path"),
ownca_privatekey_content=dict(type="str", no_log=True),
ownca_privatekey_passphrase=dict(type="str", no_log=True),
ownca_digest=dict(type="str", default="sha256"),
ownca_version=dict(type="int", default=3, choices=[3]), # not used
ownca_not_before=dict(type="str", default="+0s"),
ownca_not_after=dict(type="str", default="+3650d"),
ownca_create_subject_key_identifier=dict(
type="str",
default="create_if_not_provided",
choices=["create_if_not_provided", "always_create", "never_create"],
),
ownca_create_authority_key_identifier=dict(type="bool", default=True),
)
{
"ownca_path": {"type": "path"},
"ownca_content": {"type": "str"},
"ownca_privatekey_path": {"type": "path"},
"ownca_privatekey_content": {"type": "str", "no_log": True},
"ownca_privatekey_passphrase": {"type": "str", "no_log": True},
"ownca_digest": {"type": "str", "default": "sha256"},
"ownca_version": {"type": "int", "default": 3, "choices": [3]}, # not used
"ownca_not_before": {"type": "str", "default": "+0s"},
"ownca_not_after": {"type": "str", "default": "+3650d"},
"ownca_create_subject_key_identifier": {
"type": "str",
"default": "create_if_not_provided",
"choices": ["create_if_not_provided", "always_create", "never_create"],
},
"ownca_create_authority_key_identifier": {"type": "bool", "default": True},
}
)
argument_spec.mutually_exclusive.extend(
[

View File

@@ -59,7 +59,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
privatekey: CertificateIssuerPrivateKeyTypes
def __init__(self, *, module: AnsibleModule) -> None:
super(SelfSignedCertificateBackendCryptography, self).__init__(module=module)
super().__init__(module=module)
self.create_subject_key_identifier: t.Literal[
"create_if_not_provided", "always_create", "never_create"
@@ -144,7 +144,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
critical=False,
)
except ValueError as e:
raise CertificateError(str(e))
raise CertificateError(str(e)) from e
certificate = cert_builder.sign(
private_key=self.privatekey,
@@ -167,7 +167,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
) -> bool:
assert self.privatekey is not None
if super(SelfSignedCertificateBackendCryptography, self).needs_regeneration(
if super().needs_regeneration(
not_before=self.notBefore, not_after=self.notAfter
):
return True
@@ -185,9 +185,7 @@ class SelfSignedCertificateBackendCryptography(CertificateBackend):
return False
def dump(self, *, include_certificate: bool) -> dict[str, t.Any]:
result = super(SelfSignedCertificateBackendCryptography, self).dump(
include_certificate=include_certificate
)
result = super().dump(include_certificate=include_certificate)
if self.module.check_mode:
result.update(
@@ -243,21 +241,29 @@ class SelfSignedCertificateProvider(CertificateProvider):
def add_selfsigned_provider_to_argument_spec(argument_spec: ArgumentSpec) -> None:
argument_spec.argument_spec["provider"]["choices"].append("selfsigned")
argument_spec.argument_spec.update(
dict(
selfsigned_version=dict(type="int", default=3, choices=[3]), # not used
selfsigned_digest=dict(type="str", default="sha256"),
selfsigned_not_before=dict(
type="str", default="+0s", aliases=["selfsigned_notBefore"]
),
selfsigned_not_after=dict(
type="str", default="+3650d", aliases=["selfsigned_notAfter"]
),
selfsigned_create_subject_key_identifier=dict(
type="str",
default="create_if_not_provided",
choices=["create_if_not_provided", "always_create", "never_create"],
),
)
{
"selfsigned_version": {
"type": "int",
"default": 3,
"choices": [3],
}, # not used
"selfsigned_digest": {"type": "str", "default": "sha256"},
"selfsigned_not_before": {
"type": "str",
"default": "+0s",
"aliases": ["selfsigned_notBefore"],
},
"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")
def get_info(self) -> dict[str, t.Any]:
self.crl_pem = identify_pem_format(self.content)
crl_pem = identify_pem_format(self.content)
try:
if self.crl_pem:
self.crl = x509.load_pem_x509_crl(self.content)
if crl_pem:
crl = x509.load_pem_x509_crl(self.content)
else:
self.crl = x509.load_der_x509_crl(self.content)
crl = x509.load_der_x509_crl(self.content)
except ValueError as e:
self.module.fail_json(msg=f"Error while decoding CRL: {e}")
result: dict[str, t.Any] = {
"changed": False,
"format": "pem" if self.crl_pem else "der",
"format": "pem" if crl_pem else "der",
"last_update": None,
"next_update": None,
"digest": None,
@@ -86,25 +86,24 @@ class CRLInfoRetrieval:
"issuer": None,
}
result["last_update"] = self.crl.last_update.strftime(TIMESTAMP_FORMAT)
result["last_update"] = crl.last_update.strftime(TIMESTAMP_FORMAT)
result["next_update"] = (
self.crl.next_update.strftime(TIMESTAMP_FORMAT)
if self.crl.next_update
else None
crl.next_update.strftime(TIMESTAMP_FORMAT) if crl.next_update else None
)
result["digest"] = cryptography_oid_to_name(
cryptography_get_signature_algorithm_oid_from_crl(self.crl)
cryptography_get_signature_algorithm_oid_from_crl(crl)
)
issuer = []
for attribute in self.crl.issuer:
for attribute in crl.issuer:
issuer.append([cryptography_oid_to_name(attribute.oid), attribute.value])
result["issuer_ordered"] = issuer
result["issuer"] = {}
issuer_dict = {}
for k, v in issuer:
result["issuer"][k] = v
issuer_dict[k] = v
result["issuer"] = issuer_dict
if self.list_revoked_certificates:
result["revoked_certificates"] = []
for cert in self.crl:
for cert in crl:
entry = cryptography_decode_revoked_certificate(cert)
result["revoked_certificates"].append(
cryptography_dump_revoked(entry, idn_rewrite=self.name_encoding)

View File

@@ -170,7 +170,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
)
self.ordered_subject = True
except ValueError as exc:
raise CertificateSigningRequestError(str(exc))
raise CertificateSigningRequestError(str(exc)) from exc
self.using_common_name_for_san = False
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:
raise CertificateSigningRequestError(
f"Cannot parse subject_key_identifier: {e}"
)
) from e
self.authority_key_identifier: bytes | None = None
if authority_key_identifier is not None:
@@ -200,7 +200,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
except Exception as e:
raise CertificateSigningRequestError(
f"Cannot parse authority_key_identifier: {e}"
)
) from e
self.existing_csr: cryptography.x509.CertificateSigningRequest | None = None
self.existing_csr_bytes: bytes | None = None
@@ -221,7 +221,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
result["can_parse_csr"] = True
return result
except Exception:
return dict(can_parse_csr=False)
return {"can_parse_csr": False}
@abc.abstractmethod
def generate_csr(self) -> None:
@@ -253,7 +253,7 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
passphrase=self.privatekey_passphrase,
)
except OpenSSLBadPassphraseError as exc:
raise CertificateSigningRequestError(exc)
raise CertificateSigningRequestError(exc) from exc
@abc.abstractmethod
def _check_csr(self) -> bool:
@@ -294,10 +294,10 @@ class CertificateSigningRequestBackend(metaclass=abc.ABCMeta):
# Store result
result["csr"] = csr_bytes.decode("utf-8") if csr_bytes else None
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
result["diff"] = {
"before": self.diff_before,
"after": self.diff_after,
}
return result
@@ -347,16 +347,14 @@ def parse_crl_distribution_points(
except (OpenSSLObjectError, ValueError) as e:
raise OpenSSLObjectError(
f"Error while parsing CRL distribution point #{index}: {e}"
)
) from e
return result
# Implementation with using cryptography
class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(CertificateSigningRequestCryptographyBackend, self).__init__(
module=module
)
super().__init__(module=module)
if self.version != 1:
module.warn(
"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:
raise CertificateSigningRequestError(e)
raise CertificateSigningRequestError(e) from e
if self.subjectAltName:
csr = csr.add_extension(
@@ -451,7 +449,9 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
critical=self.name_constraints_critical,
)
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 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]
if self.ordered_subject:
return subject == current_subject
else:
return set(subject) == set(current_subject)
return set(subject) == set(current_subject)
def _find_extension(
extensions: cryptography.x509.Extensions, exttype: type[_ET]
@@ -596,15 +595,14 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
)
if not self.keyUsage:
return current_keyusage_ext is None
elif current_keyusage_ext is None:
if current_keyusage_ext is None:
return False
params = cryptography_parse_key_usage_params(self.keyUsage)
for param in params:
if getattr(current_keyusage_ext.value, "_" + param) != params[param]:
for param, value in params.items():
# TODO: check whether getattr() with '_' prepended is really needed
if getattr(current_keyusage_ext.value, "_" + param) != value:
return False
if current_keyusage_ext.critical != self.keyUsage_critical:
return False
return True
return current_keyusage_ext.critical == self.keyUsage_critical
def _check_extenededKeyUsage(extensions: cryptography.x509.Extensions) -> bool:
current_usages_ext = _find_extension(
@@ -647,8 +645,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
bc_ext is not None
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:
tlsfeature_ext = _find_extension(extensions, cryptography.x509.TLSFeature)
@@ -662,8 +659,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
cryptography.x509.TLSFeatureType.status_request
in tlsfeature_ext.value
)
else:
return tlsfeature_ext is None
return tlsfeature_ext is None
def _check_nameConstraints(extensions: cryptography.x509.Extensions) -> bool:
current_nc_ext = _find_extension(
@@ -722,10 +718,8 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
self.privatekey.public_key()
).digest
return ext.value.digest == digest
else:
return ext.value.digest == self.subject_key_identifier
else:
return ext is None
return ext.value.digest == self.subject_key_identifier
return ext is None
def _check_authority_key_identifier(
extensions: cryptography.x509.Extensions,
@@ -753,8 +747,7 @@ class CertificateSigningRequestCryptographyBackend(CertificateSigningRequestBack
and ext.value.authority_cert_serial_number
== self.authority_cert_serial_number
)
else:
return ext is None
return ext is None
def _check_crl_distribution_points(
extensions: cryptography.x509.Extensions,
@@ -814,77 +807,97 @@ def select_backend(
def get_csr_argument_spec() -> ArgumentSpec:
return ArgumentSpec(
argument_spec=dict(
digest=dict(type="str", default="sha256"),
privatekey_path=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
privatekey_passphrase=dict(type="str", no_log=True),
version=dict(type="int", default=1, choices=[1]),
subject=dict(type="dict"),
subject_ordered=dict(type="list", elements="dict"),
country_name=dict(type="str", aliases=["C", "countryName"]),
state_or_province_name=dict(
type="str", aliases=["ST", "stateOrProvinceName"]
),
locality_name=dict(type="str", aliases=["L", "localityName"]),
organization_name=dict(type="str", aliases=["O", "organizationName"]),
organizational_unit_name=dict(
type="str", aliases=["OU", "organizationalUnitName"]
),
common_name=dict(type="str", aliases=["CN", "commonName"]),
email_address=dict(type="str", aliases=["E", "emailAddress"]),
subject_alt_name=dict(
type="list", elements="str", aliases=["subjectAltName"]
),
subject_alt_name_critical=dict(
type="bool", default=False, aliases=["subjectAltName_critical"]
),
use_common_name_for_san=dict(
type="bool", default=True, aliases=["useCommonNameForSAN"]
),
key_usage=dict(type="list", elements="str", aliases=["keyUsage"]),
key_usage_critical=dict(
type="bool", default=False, aliases=["keyUsage_critical"]
),
extended_key_usage=dict(
type="list", elements="str", aliases=["extKeyUsage", "extendedKeyUsage"]
),
extended_key_usage_critical=dict(
type="bool",
default=False,
aliases=["extKeyUsage_critical", "extendedKeyUsage_critical"],
),
basic_constraints=dict(
type="list", elements="str", aliases=["basicConstraints"]
),
basic_constraints_critical=dict(
type="bool", default=False, aliases=["basicConstraints_critical"]
),
ocsp_must_staple=dict(
type="bool", default=False, aliases=["ocspMustStaple"]
),
ocsp_must_staple_critical=dict(
type="bool", default=False, aliases=["ocspMustStaple_critical"]
),
name_constraints_permitted=dict(type="list", elements="str"),
name_constraints_excluded=dict(type="list", elements="str"),
name_constraints_critical=dict(type="bool", default=False),
create_subject_key_identifier=dict(type="bool", default=False),
subject_key_identifier=dict(type="str"),
authority_key_identifier=dict(type="str"),
authority_cert_issuer=dict(type="list", elements="str"),
authority_cert_serial_number=dict(type="int"),
crl_distribution_points=dict(
type="list",
elements="dict",
options=dict(
full_name=dict(type="list", elements="str"),
relative_name=dict(type="list", elements="str"),
crl_issuer=dict(type="list", elements="str"),
reasons=dict(
type="list",
elements="str",
choices=[
argument_spec={
"digest": {"type": "str", "default": "sha256"},
"privatekey_path": {"type": "path"},
"privatekey_content": {"type": "str", "no_log": True},
"privatekey_passphrase": {"type": "str", "no_log": True},
"version": {"type": "int", "default": 1, "choices": [1]},
"subject": {"type": "dict"},
"subject_ordered": {"type": "list", "elements": "dict"},
"country_name": {"type": "str", "aliases": ["C", "countryName"]},
"state_or_province_name": {
"type": "str",
"aliases": ["ST", "stateOrProvinceName"],
},
"locality_name": {"type": "str", "aliases": ["L", "localityName"]},
"organization_name": {"type": "str", "aliases": ["O", "organizationName"]},
"organizational_unit_name": {
"type": "str",
"aliases": ["OU", "organizationalUnitName"],
},
"common_name": {"type": "str", "aliases": ["CN", "commonName"]},
"email_address": {"type": "str", "aliases": ["E", "emailAddress"]},
"subject_alt_name": {
"type": "list",
"elements": "str",
"aliases": ["subjectAltName"],
},
"subject_alt_name_critical": {
"type": "bool",
"default": False,
"aliases": ["subjectAltName_critical"],
},
"use_common_name_for_san": {
"type": "bool",
"default": True,
"aliases": ["useCommonNameForSAN"],
},
"key_usage": {"type": "list", "elements": "str", "aliases": ["keyUsage"]},
"key_usage_critical": {
"type": "bool",
"default": False,
"aliases": ["keyUsage_critical"],
},
"extended_key_usage": {
"type": "list",
"elements": "str",
"aliases": ["extKeyUsage", "extendedKeyUsage"],
},
"extended_key_usage_critical": {
"type": "bool",
"default": False,
"aliases": ["extKeyUsage_critical", "extendedKeyUsage_critical"],
},
"basic_constraints": {
"type": "list",
"elements": "str",
"aliases": ["basicConstraints"],
},
"basic_constraints_critical": {
"type": "bool",
"default": False,
"aliases": ["basicConstraints_critical"],
},
"ocsp_must_staple": {
"type": "bool",
"default": False,
"aliases": ["ocspMustStaple"],
},
"ocsp_must_staple_critical": {
"type": "bool",
"default": False,
"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",
"ca_compromise",
"affiliation_changed",
@@ -894,15 +907,17 @@ def get_csr_argument_spec() -> ArgumentSpec:
"privilege_withdrawn",
"aa_compromise",
],
),
),
mutually_exclusive=[("full_name", "relative_name")],
required_one_of=[("full_name", "relative_name", "crl_issuer")],
),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
),
},
},
"mutually_exclusive": [("full_name", "relative_name")],
"required_one_of": [("full_name", "relative_name", "crl_issuer")],
},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
},
required_together=[
["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):
csr: x509.CertificateSigningRequest
def __init__(
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
) -> None:
@@ -129,7 +131,7 @@ class CSRInfoRetrieval(metaclass=abc.ABCMeta):
)
subject = self._get_subject_ordered()
result["subject"] = dict()
result["subject"] = {}
for k, v in subject:
result["subject"][k] = v
result["subject_ordered"] = subject
@@ -197,7 +199,7 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
def __init__(
self, *, module: GeneralAnsibleModule, content: bytes, validate_signature: bool
) -> None:
super(CSRInfoRetrievalCryptography, self).__init__(
super().__init__(
module=module, content=content, validate_signature=validate_signature
)
self.name_encoding: t.Literal["ignore", "idna", "unicode"] = module.params.get(
@@ -216,36 +218,36 @@ class CSRInfoRetrievalCryptography(CSRInfoRetrieval):
try:
current_key_ext = self.csr.extensions.get_extension_for_class(x509.KeyUsage)
current_key_usage = current_key_ext.value
key_usage = dict(
digital_signature=current_key_usage.digital_signature,
content_commitment=current_key_usage.content_commitment,
key_encipherment=current_key_usage.key_encipherment,
data_encipherment=current_key_usage.data_encipherment,
key_agreement=current_key_usage.key_agreement,
key_cert_sign=current_key_usage.key_cert_sign,
crl_sign=current_key_usage.crl_sign,
encipher_only=False,
decipher_only=False,
)
key_usage = {
"digital_signature": current_key_usage.digital_signature,
"content_commitment": current_key_usage.content_commitment,
"key_encipherment": current_key_usage.key_encipherment,
"data_encipherment": current_key_usage.data_encipherment,
"key_agreement": current_key_usage.key_agreement,
"key_cert_sign": current_key_usage.key_cert_sign,
"crl_sign": current_key_usage.crl_sign,
"encipher_only": False,
"decipher_only": False,
}
if key_usage["key_agreement"]:
key_usage.update(
dict(
encipher_only=current_key_usage.encipher_only,
decipher_only=current_key_usage.decipher_only,
)
{
"encipher_only": current_key_usage.encipher_only,
"decipher_only": current_key_usage.decipher_only,
}
)
key_usage_names = dict(
digital_signature="Digital Signature",
content_commitment="Non Repudiation",
key_encipherment="Key Encipherment",
data_encipherment="Data Encipherment",
key_agreement="Key Agreement",
key_cert_sign="Certificate Sign",
crl_sign="CRL Sign",
encipher_only="Encipher Only",
decipher_only="Decipher Only",
)
key_usage_names = {
"digital_signature": "Digital Signature",
"content_commitment": "Non Repudiation",
"key_encipherment": "Key Encipherment",
"data_encipherment": "Data Encipherment",
"key_agreement": "Key Agreement",
"key_cert_sign": "Certificate Sign",
"crl_sign": "CRL Sign",
"encipher_only": "Encipher Only",
"decipher_only": "Decipher Only",
}
return (
sorted(
[

View File

@@ -265,10 +265,10 @@ class PrivateKeyBackend(metaclass=abc.ABCMeta):
else:
result["privatekey"] = None
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
result["diff"] = {
"before": self.diff_before,
"after": self.diff_after,
}
return result
@@ -323,7 +323,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
self.curves[name] = _Curve(name=name, ectype=ectype, deprecated=deprecated)
def __init__(self, module: GeneralAnsibleModule) -> None:
super(PrivateKeyCryptographyBackend, self).__init__(module=module)
super().__init__(module=module)
self.curves: dict[str, _Curve] = {}
self._add_curve("secp224r1", "SECP224R1")
@@ -351,8 +351,7 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
return self.format # type: ignore
if self.type in ("X25519", "X448", "Ed25519", "Ed448"):
return "pkcs8"
else:
return "pkcs1"
return "pkcs1"
def generate_private_key(self) -> None:
"""(Re-)Generate private key."""
@@ -427,6 +426,9 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
export_encoding = (
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:
self.module.fail_json(
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")
try:
# Interpret bytes depending on format.
format = identify_private_key_format(data)
if format == "raw":
key_format = identify_private_key_format(data)
if key_format == "raw":
if len(data) == 56:
return cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
data
@@ -497,15 +499,13 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
data
)
raise PrivateKeyError("Cannot load raw key")
else:
return (
cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if self.passphrase is None else to_bytes(self.passphrase),
)
)
return cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if self.passphrase is None else to_bytes(self.passphrase),
)
except Exception as e:
raise PrivateKeyError(e)
raise PrivateKeyError(e) from e
def _ensure_existing_private_key_loaded(self) -> None:
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:
raise AssertionError("existing_private_key_bytes not set")
try:
format = identify_private_key_format(self.existing_private_key_bytes)
if format == "raw":
key_format = identify_private_key_format(self.existing_private_key_bytes)
if key_format == "raw":
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
# actually load the key (and return False when this fails).
self._load_privatekey()
# Loading the key succeeded. Only return True when no passphrase was
# provided.
return self.passphrase is None
else:
return bool(
cryptography.hazmat.primitives.serialization.load_pem_private_key(
self.existing_private_key_bytes,
None if self.passphrase is None else to_bytes(self.passphrase),
)
return bool(
cryptography.hazmat.primitives.serialization.load_pem_private_key(
self.existing_private_key_bytes,
None if self.passphrase is None else to_bytes(self.passphrase),
)
)
except Exception:
return False
@@ -588,8 +587,8 @@ class PrivateKeyCryptographyBackend(PrivateKeyBackend):
if self.format == "auto_ignore":
return True
try:
format = identify_private_key_format(self.existing_private_key_bytes)
return format == self._get_wanted_format()
key_format = identify_private_key_format(self.existing_private_key_bytes)
return key_format == self._get_wanted_format()
except Exception:
return False
@@ -603,16 +602,16 @@ def select_backend(module: GeneralAnsibleModule) -> PrivateKeyBackend:
def get_privatekey_argument_spec() -> ArgumentSpec:
return ArgumentSpec(
argument_spec=dict(
size=dict(type="int", default=4096),
type=dict(
type="str",
default="RSA",
choices=["DSA", "ECC", "Ed25519", "Ed448", "RSA", "X25519", "X448"],
),
curve=dict(
type="str",
choices=[
argument_spec={
"size": {"type": "int", "default": 4096},
"type": {
"type": "str",
"default": "RSA",
"choices": ["DSA", "ECC", "Ed25519", "Ed448", "RSA", "X25519", "X448"],
},
"curve": {
"type": "str",
"choices": [
"secp224r1",
"secp256k1",
"secp256r1",
@@ -633,32 +632,36 @@ def get_privatekey_argument_spec() -> ArgumentSpec:
"sect571k1",
"sect571r1",
],
),
passphrase=dict(type="str", no_log=True),
cipher=dict(type="str", default="auto"),
format=dict(
type="str",
default="auto_ignore",
choices=["pkcs1", "pkcs8", "raw", "auto", "auto_ignore"],
),
format_mismatch=dict(
type="str", default="regenerate", choices=["regenerate", "convert"]
),
select_crypto_backend=dict(
type="str", choices=["auto", "cryptography"], default="auto"
),
regenerate=dict(
type="str",
default="full_idempotence",
choices=[
},
"passphrase": {"type": "str", "no_log": True},
"cipher": {"type": "str", "default": "auto"},
"format": {
"type": "str",
"default": "auto_ignore",
"choices": ["pkcs1", "pkcs8", "raw", "auto", "auto_ignore"],
},
"format_mismatch": {
"type": "str",
"default": "regenerate",
"choices": ["regenerate", "convert"],
},
"select_crypto_backend": {
"type": "str",
"choices": ["auto", "cryptography"],
"default": "auto",
},
"regenerate": {
"type": "str",
"default": "full_idempotence",
"choices": [
"never",
"fail",
"partial_idempotence",
"full_idempotence",
"always",
],
),
),
},
},
required_if=[
("type", "ECC", ["curve"]),
],

View File

@@ -121,7 +121,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
assert self.dest_private_key_bytes is not None
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,
passphrase=self.dest_passphrase,
current_hint=self.src_private_key,
@@ -129,7 +129,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
except Exception:
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
)
@@ -141,7 +141,7 @@ class PrivateKeyConvertBackend(metaclass=abc.ABCMeta):
# Implementation with using cryptography
class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(PrivateKeyConvertCryptographyBackend, self).__init__(module=module)
super().__init__(module=module)
def get_private_key_data(self) -> bytes:
"""Return bytes for self.src_private_key in output format"""
@@ -166,6 +166,9 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
export_encoding = (
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:
self.module.fail_json(
msg=f'Cryptography backend does not support the selected output format "{self.format}"'
@@ -208,20 +211,20 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
) -> tuple[str, PrivateKeyTypes]:
try:
# Interpret bytes depending on format.
format = identify_private_key_format(data)
if format == "raw":
key_format = identify_private_key_format(data)
if key_format == "raw":
if passphrase is not None:
raise PrivateKeyError("Cannot load raw key with passphrase")
if len(data) == 56:
return (
format,
key_format,
cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey.from_private_bytes(
data
),
)
if len(data) == 57:
return (
format,
key_format,
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.from_private_bytes(
data
),
@@ -233,14 +236,14 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
):
try:
return (
format,
key_format,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
data
),
)
except Exception:
return (
format,
key_format,
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
data
),
@@ -248,29 +251,29 @@ class PrivateKeyConvertCryptographyBackend(PrivateKeyConvertBackend):
else:
try:
return (
format,
key_format,
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.from_private_bytes(
data
),
)
except Exception:
return (
format,
key_format,
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
data
),
)
raise PrivateKeyError("Cannot load raw key")
else:
return (
format,
cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if passphrase is None else to_bytes(passphrase),
),
)
return (
key_format,
cryptography.hazmat.primitives.serialization.load_pem_private_key(
data,
None if passphrase is None else to_bytes(passphrase),
),
)
except Exception as e:
raise PrivateKeyError(e)
raise PrivateKeyError(e) from e
def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
@@ -282,13 +285,17 @@ def select_backend(module: AnsibleModule) -> PrivateKeyConvertBackend:
def get_privatekey_argument_spec() -> ArgumentSpec:
return ArgumentSpec(
argument_spec=dict(
src_path=dict(type="path"),
src_content=dict(type="str"),
src_passphrase=dict(type="str", no_log=True),
dest_passphrase=dict(type="str", no_log=True),
format=dict(type="str", required=True, choices=["pkcs1", "pkcs8", "raw"]),
),
argument_spec={
"src_path": {"type": "path"},
"src_content": {"type": "str"},
"src_passphrase": {"type": "str", "no_log": True},
"dest_passphrase": {"type": "str", "no_log": True},
"format": {
"type": "str",
"required": True,
"choices": ["pkcs1", "pkcs8", "raw"],
},
},
mutually_exclusive=[
["src_path", "src_content"],
],

View File

@@ -134,7 +134,7 @@ def _is_cryptography_key_consistent(
# key._backend was removed in cryptography 42.0.0
backend = getattr(key, "_backend", 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):
result = _check_dsa_consistency(
key_public_data=key_public_data, key_private_data=key_private_data
@@ -195,19 +195,21 @@ def _is_cryptography_key_consistent(
class PrivateKeyConsistencyError(OpenSSLObjectError):
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
super(PrivateKeyConsistencyError, self).__init__(msg)
super().__init__(msg)
self.error_message = msg
self.result = result
class PrivateKeyParseError(OpenSSLObjectError):
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
super(PrivateKeyParseError, self).__init__(msg)
super().__init__(msg)
self.error_message = msg
self.result = result
class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
key: PrivateKeyTypes
def __init__(
self,
*,
@@ -257,14 +259,14 @@ class PrivateKeyInfoRetrieval(metaclass=abc.ABCMeta):
)
result["can_parse_key"] = True
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))
pk = self._get_public_key(binary=True)
result["public_key_fingerprints"] = (
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
if pk is not None
else dict()
else {}
)
key_type, key_public_data, key_private_data = self._get_key_info(
@@ -295,9 +297,7 @@ class PrivateKeyInfoRetrievalCryptography(PrivateKeyInfoRetrieval):
def __init__(
self, *, module: GeneralAnsibleModule, content: bytes, **kwargs
) -> None:
super(PrivateKeyInfoRetrievalCryptography, self).__init__(
module=module, content=content, **kwargs
)
super().__init__(module=module, content=content, **kwargs)
def _get_public_key(self, *, binary: bool) -> bytes:
return self.key.public_key().public_bytes(

View File

@@ -100,7 +100,7 @@ def _get_cryptography_public_key_info(
class PublicKeyParseError(OpenSSLObjectError):
def __init__(self, msg: str, *, result: dict[str, t.Any]) -> None:
super(PublicKeyParseError, self).__init__(msg)
super().__init__(msg)
self.error_message = msg
self.result = result
@@ -132,13 +132,13 @@ class PublicKeyInfoRetrieval(metaclass=abc.ABCMeta):
try:
self.key = load_publickey(content=self.content)
except OpenSSLObjectError as e:
raise PublicKeyParseError(str(e), result={})
raise PublicKeyParseError(str(e), result={}) from e
pk = self._get_public_key(binary=True)
result["fingerprints"] = (
get_fingerprint_of_bytes(pk, prefer_one=prefer_one_fingerprint)
if pk is not None
else dict()
else {}
)
key_type, key_public_data = self._get_key_info()
@@ -157,9 +157,7 @@ class PublicKeyInfoRetrievalCryptography(PublicKeyInfoRetrieval):
content: bytes | None = None,
key: PublicKeyTypes | None = None,
) -> None:
super(PublicKeyInfoRetrievalCryptography, self).__init__(
module=module, content=content, key=key
)
super().__init__(module=module, content=content, key=key)
def _get_public_key(self, binary: bool) -> bytes:
if self.key is None:

View File

@@ -161,19 +161,21 @@ def load_privatekey(
else:
priv_key_detail = content
except (IOError, OSError) as exc:
raise OpenSSLObjectError(exc)
raise OpenSSLObjectError(exc) from exc
try:
return load_pem_private_key(
priv_key_detail,
None if passphrase is None else to_bytes(passphrase),
)
except TypeError:
except TypeError as exc:
raise OpenSSLBadPassphraseError(
"Wrong or empty passphrase provided for private key"
)
except ValueError:
raise OpenSSLBadPassphraseError("Wrong passphrase provided for private key")
) from exc
except ValueError as exc:
raise OpenSSLBadPassphraseError(
"Wrong passphrase provided for private key"
) from exc
def load_certificate_privatekey(
@@ -232,12 +234,12 @@ def load_publickey(
with open(path, "rb") as b_priv_key_fh:
content = b_priv_key_fh.read()
except (IOError, OSError) as exc:
raise OpenSSLObjectError(exc)
raise OpenSSLObjectError(exc) from exc
try:
return serialization.load_pem_public_key(content)
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(
@@ -257,17 +259,17 @@ def load_certificate(
else:
cert_content = content
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):
try:
return x509.load_pem_x509_certificate(cert_content)
except ValueError as exc:
raise OpenSSLObjectError(exc)
raise OpenSSLObjectError(exc) from exc
elif der_support_enabled:
try:
return x509.load_der_x509_certificate(cert_content)
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(
@@ -283,11 +285,11 @@ def load_certificate_request(
else:
csr_content = content
except (IOError, OSError) as exc:
raise OpenSSLObjectError(exc)
raise OpenSSLObjectError(exc) from exc
try:
return x509.load_pem_x509_csr(csr_content)
except ValueError as exc:
raise OpenSSLObjectError(exc)
raise OpenSSLObjectError(exc) from exc
def parse_name_field(
@@ -344,7 +346,7 @@ def parse_ordered_name_field(
except (TypeError, ValueError) as exc:
raise ValueError(
f"Error while processing entry #{index + 1} in {name_field_name}: {exc}"
)
) from exc
return result
@@ -425,9 +427,7 @@ class OpenSSLObject(metaclass=abc.ABCMeta):
self.changed = True
except OSError as exc:
if exc.errno != errno.ENOENT:
raise OpenSSLObjectError(exc)
else:
pass
raise OpenSSLObjectError(exc) from exc
__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]:
return dict(
entrust_api_user=dict(type="str", required=True),
entrust_api_key=dict(type="str", required=True, no_log=True),
entrust_api_client_cert_path=dict(type="path", required=True),
entrust_api_client_cert_key_path=dict(type="path", required=True, no_log=True),
entrust_api_specification_path=dict(
type="path",
default="https://cloud.entrust.net/EntrustCloud/documentation/cms-api-2.1.0.yaml",
),
)
return {
"entrust_api_user": {"type": "str", "required": True},
"entrust_api_key": {"type": "str", "required": True, "no_log": True},
"entrust_api_client_cert_path": {"type": "path", "required": True},
"entrust_api_client_cert_key_path": {
"type": "path",
"required": True,
"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):
@@ -182,8 +186,7 @@ class RestOperation:
if result or result == {}:
if result_code and result_code < 400:
return result
else:
raise RestOperationException(result)
raise RestOperationException(result)
# Raise a generic RestOperationException if this fails
raise RestOperationException(
@@ -323,9 +326,9 @@ class ECSSession:
except HTTPError as e:
raise SessionConfigurationException(
f"Error downloading specification from address '{entrust_api_specification_path}', received error code '{e.getcode()}'"
)
) from e
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:
self._spec = json.load(f)
elif (

View File

@@ -45,8 +45,7 @@ def restore_on_failure(
if backup_file is not None:
module.atomic_move(os.path.abspath(backup_file), os.path.abspath(path))
raise
else:
module.add_cleanup_file(backup_file)
module.add_cleanup_file(backup_file)
return backup_and_restore
@@ -91,9 +90,8 @@ def _restore_all_on_failure(
os.path.abspath(backup), os.path.abspath(destination)
)
raise
else:
for destination, backup in backups:
self.module.add_cleanup_file(backup)
for destination, backup in backups:
self.module.add_cleanup_file(backup)
return backup_and_restore
@@ -126,7 +124,7 @@ class OpensshModule(metaclass=abc.ABCMeta):
result["changed"] = self.changed
if self.module._diff:
if self.module._diff: # pylint: disable=protected-access
result["diff"] = self.diff
return result
@@ -219,7 +217,7 @@ class KeygenCommand:
serial_number: int | None,
signature_algorithm: str | None,
signing_key_path: str,
type: t.Literal["host", "user"] | None,
cert_type: t.Literal["host", "user"] | None,
time_parameters: OpensshCertificateTimeParameters,
use_agent: bool,
**kwargs,
@@ -235,7 +233,7 @@ class KeygenCommand:
args.extend(["-n", ",".join(principals)])
if serial_number is not None:
args.extend(["-z", str(serial_number)])
if type == "host":
if cert_type == "host":
args.extend(["-h"])
if use_agent:
args.extend(["-U"])
@@ -252,7 +250,7 @@ class KeygenCommand:
*,
private_key_path: str,
size: int,
type: str,
key_type: str,
comment: str | None,
**kwargs,
) -> tuple[int, str, str]:
@@ -264,7 +262,7 @@ class KeygenCommand:
"-b",
str(size),
"-t",
type,
key_type,
"-f",
private_key_path,
"-C",
@@ -313,7 +311,7 @@ class KeygenCommand:
except (IOError, OSError) as e:
raise ValueError(
f"The private key at {private_key_path} is not writeable preventing a comment update ({e})"
)
) from e
command = [self._bin_path, "-q"]
if force_new_format:
@@ -327,12 +325,12 @@ _PrivateKey = t.TypeVar("_PrivateKey", bound="PrivateKey")
class PrivateKey:
def __init__(
self, *, size: int, key_type: str, fingerprint: str, format: str = ""
self, *, size: int, key_type: str, fingerprint: str, key_format: str = ""
) -> None:
self._size = size
self._type = key_type
self._fingerprint = fingerprint
self._format = format
self._format = key_format
@property
def size(self) -> int:
@@ -428,11 +426,8 @@ class PublicKey:
@classmethod
def load(cls: t.Type[_PublicKey], path: str | os.PathLike) -> _PublicKey | None:
try:
with open(path, "r") as f:
properties = f.read().strip(" \n").split(" ", 2)
except (IOError, OSError):
raise
with open(path, "r", encoding="utf-8") as f:
properties = f.read().strip(" \n").split(" ", 2)
if len(properties) < 2:
return None
@@ -454,14 +449,14 @@ def parse_private_key_format(
*,
path: str | os.PathLike,
) -> t.Literal["SSH", "PKCS8", "PKCS1", ""]:
with open(path, "r") as file:
with open(path, "r", encoding="utf-8") as file:
header = file.readline().strip()
if header == "-----BEGIN OPENSSH PRIVATE KEY-----":
return "SSH"
elif header == "-----BEGIN PRIVATE KEY-----":
if header == "-----BEGIN PRIVATE KEY-----":
return "PKCS8"
elif header == "-----BEGIN RSA PRIVATE KEY-----":
if header == "-----BEGIN RSA PRIVATE KEY-----":
return "PKCS1"
return ""

View File

@@ -54,7 +54,7 @@ if t.TYPE_CHECKING:
class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
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.private_key_path: str = self.module.params["path"]
@@ -189,9 +189,9 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
def _should_generate(self) -> bool:
if self.original_private_key is None:
return True
elif self.regenerate == "never":
if self.regenerate == "never":
return False
elif self.regenerate == "fail":
if self.regenerate == "fail":
if not self._private_key_valid():
self.module.fail_json(
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`."
)
return False
elif self.regenerate in ("partial_idempotence", "full_idempotence"):
if self.regenerate in ("partial_idempotence", "full_idempotence"):
return not self._private_key_valid()
else:
return True
return True
def _private_key_valid(self) -> bool:
if self.original_private_key is None:
@@ -358,7 +357,7 @@ class KeypairBackend(OpensshModule, metaclass=abc.ABCMeta):
class KeypairBackendOpensshBin(KeypairBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(KeypairBackendOpensshBin, self).__init__(module=module)
super().__init__(module=module)
if self.module.params["private_key_format"] != "auto":
self.module.fail_json(
@@ -371,7 +370,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
self.ssh_keygen.generate_keypair(
private_key_path=private_key_path,
size=self.size,
type=self.type,
key_type=self.type,
comment=self.comment,
check_rc=True,
)
@@ -391,7 +390,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
return PublicKey.from_string(public_key_content)
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
)
return not (
@@ -425,7 +424,7 @@ class KeypairBackendOpensshBin(KeypairBackend):
class KeypairBackendCryptography(KeypairBackend):
def __init__(self, *, module: AnsibleModule) -> None:
super(KeypairBackendCryptography, self).__init__(module=module)
super().__init__(module=module)
if self.type == "rsa1":
self.module.fail_json(
@@ -489,7 +488,7 @@ class KeypairBackendCryptography(KeypairBackend):
size=keypair.size,
key_type=keypair.key_type,
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[""]:
@@ -522,10 +521,9 @@ class KeypairBackendCryptography(KeypairBackend):
OpensshKeypair.load(
path=self.private_key_path, passphrase=None, no_public_key=True
)
return False
except (InvalidPrivateKeyFileError, InvalidPassphraseError):
return True
else:
return False
return True

View File

@@ -124,11 +124,9 @@ class OpensshCertificateTimeParameters:
def __eq__(self, other: object) -> bool:
if not isinstance(other, type(self)):
return NotImplemented
else:
return (
self._valid_from == other._valid_from
and self._valid_to == other._valid_to
)
return (
self._valid_from == other._valid_from and self._valid_to == other._valid_to
)
def __ne__(self, other: object) -> bool:
return not self == other
@@ -188,12 +186,11 @@ class OpensshCertificateTimeParameters:
return "always"
if dt == _FOREVER:
return "forever"
else:
return (
dt.isoformat().replace("+00:00", "")
if date_format == "human_readable"
else dt.strftime("%Y%m%d%H%M%S")
)
return (
dt.isoformat().replace("+00:00", "")
if date_format == "human_readable"
else dt.strftime("%Y%m%d%H%M%S")
)
if date_format == "timestamp":
td = dt - _ALWAYS
return int(
@@ -203,22 +200,17 @@ class OpensshCertificateTimeParameters:
@staticmethod
def to_datetime(time_string_or_timestamp: str | bytes | int) -> datetime:
try:
if isinstance(time_string_or_timestamp, (str, bytes)):
result = OpensshCertificateTimeParameters._time_string_to_datetime(
to_text(time_string_or_timestamp.strip())
)
elif isinstance(time_string_or_timestamp, int):
result = OpensshCertificateTimeParameters._timestamp_to_datetime(
time_string_or_timestamp
)
else:
raise ValueError(
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
)
except ValueError:
raise
return result
if isinstance(time_string_or_timestamp, (str, bytes)):
return OpensshCertificateTimeParameters._time_string_to_datetime(
to_text(time_string_or_timestamp.strip())
)
if isinstance(time_string_or_timestamp, int):
return OpensshCertificateTimeParameters._timestamp_to_datetime(
time_string_or_timestamp
)
raise ValueError(
f"Value must be of type (str, unicode, int) not {type(time_string_or_timestamp)}"
)
@staticmethod
def _timestamp_to_datetime(timestamp: int) -> datetime:
@@ -228,8 +220,8 @@ class OpensshCertificateTimeParameters:
return _FOREVER
try:
return datetime.fromtimestamp(timestamp, tz=_datetime.timezone.utc)
except OverflowError:
raise ValueError
except OverflowError as e:
raise ValueError from e
@staticmethod
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", ""]:
if self._cert_type == _USER_TYPE:
return "user"
elif self._cert_type == _HOST_TYPE:
if self._cert_type == _HOST_TYPE:
return "host"
else:
return ""
return ""
@cert_type.setter
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
elif cert_type == "host" or cert_type == _HOST_TYPE:
elif cert_type in ("host", _HOST_TYPE):
self._cert_type = _HOST_TYPE
else:
raise ValueError(f"{cert_type} is not a valid certificate type")
@@ -412,7 +403,7 @@ class OpensshCertificateInfo(metaclass=abc.ABCMeta):
class OpensshRSACertificateInfo(OpensshCertificateInfo):
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.e = e
self.n = n
@@ -444,7 +435,7 @@ class OpensshDSACertificateInfo(OpensshCertificateInfo):
y: int | None = None,
**kwargs,
) -> None:
super(OpensshDSACertificateInfo, self).__init__(**kwargs)
super().__init__(**kwargs)
self.type_string = _SSH_TYPE_STRINGS["dsa"] + _CERT_SUFFIX_V01
self.p = p
self.q = q
@@ -476,7 +467,7 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo):
def __init__(
self, *, curve: bytes | None = None, public_key: bytes | None = None, **kwargs
):
super(OpensshECDSACertificateInfo, self).__init__(**kwargs)
super().__init__(**kwargs)
self._curve = None
if curve is not None:
self.curve = curve
@@ -519,7 +510,7 @@ class OpensshECDSACertificateInfo(OpensshCertificateInfo):
class OpensshED25519CertificateInfo(OpensshCertificateInfo):
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.pk = pk
@@ -559,13 +550,13 @@ class OpensshCertificate:
with open(path, "rb") as cert_file:
data = cert_file.read()
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:
format_identifier, b64_cert = data.split(b" ")[:2]
cert = binascii.a2b_base64(b64_cert)
except (binascii.Error, ValueError):
raise ValueError("Certificate not in OpenSSH format")
except (binascii.Error, ValueError) as e:
raise ValueError("Certificate not in OpenSSH format") from e
for key_type, string in _SSH_TYPE_STRINGS.items():
if format_identifier == string + _CERT_SUFFIX_V01:
@@ -585,7 +576,7 @@ class OpensshCertificate:
cert_info = cls._parse_cert_info(pub_key_type, parser)
signature = parser.string()
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():
raise ValueError(
@@ -751,10 +742,9 @@ def apply_directives(directives: t.Iterable[str]) -> list[OpensshCertificateOpti
if "clear" in directives:
return []
else:
return list(
set(default_options()) - set(directive_to_option[d] for d in directives)
)
return list(
set(default_options()) - set(directive_to_option[d] for d in directives)
)
def default_options() -> list[OpensshCertificateOption]:

View File

@@ -19,6 +19,7 @@ try:
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
from cryptography.hazmat.primitives import hashes, serialization
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 (
Ed25519PrivateKey,
Ed25519PublicKey,
@@ -68,6 +69,10 @@ except ImportError:
CRYPTOGRAPHY_VERSION = "0.0"
_ALGORITHM_PARAMETERS = {}
from ansible_collections.community.crypto.plugins.module_utils._crypto.cryptography_support import (
is_potential_certificate_issuer_private_key,
)
if t.TYPE_CHECKING:
KeyFormat = t.Literal["SSH", "PKCS8", "PKCS1"]
@@ -309,10 +314,10 @@ class AsymmetricKeypair:
try:
self.verify(signature=self.sign(b"message"), data=b"message")
except InvalidSignatureError:
except InvalidSignatureError as e:
raise InvalidPublicKeyFileError(
"The private key and public key of this keypair do not match"
)
) from e
def __eq__(self, other: object) -> bool:
if not isinstance(other, AsymmetricKeypair):
@@ -368,7 +373,7 @@ class AsymmetricKeypair:
data, **_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"] # type: ignore
)
except TypeError as e:
raise InvalidDataError(e)
raise InvalidDataError(e) from e
def verify(self, *, signature: bytes, data: bytes) -> None:
"""Verifies that the signature associated with the provided data was signed
@@ -383,8 +388,8 @@ class AsymmetricKeypair:
data,
**_ALGORITHM_PARAMETERS[self.__keytype]["signer_params"], # type: ignore
)
except InvalidSignature:
raise InvalidSignatureError
except InvalidSignature as e:
raise InvalidSignatureError from e
def update_passphrase(self, passphrase: bytes | None = None) -> None:
"""Updates the encryption algorithm of this key pair
@@ -661,10 +666,10 @@ def load_privatekey(
try:
privatekey_loader = privatekey_loaders[key_format]
except KeyError:
except KeyError as e:
raise InvalidKeyFormatError(
f"{key_format} is not a valid key format ({','.join(privatekey_loaders)})"
)
) from e
if not os.path.exists(path):
raise InvalidPrivateKeyFileError(f"No file was found at {path}")
@@ -673,32 +678,33 @@ def load_privatekey(
with open(path, "rb") as f:
content = f.read()
privatekey = privatekey_loader( # type: ignore
try:
privatekey = privatekey_loader(
data=content,
password=passphrase,
)
except ValueError as exc:
# Revert to PEM if key could not be loaded in SSH format
if key_format == "SSH":
try:
privatekey = privatekey_loaders["PEM"]( # type: ignore
except ValueError as exc:
# Revert to PEM if key could not be loaded in SSH format
if key_format == "SSH":
privatekey = privatekey_loaders["PEM"](
data=content,
password=passphrase,
)
except ValueError as e:
raise InvalidPrivateKeyFileError(e)
except TypeError as e:
raise InvalidPassphraseError(e)
except UnsupportedAlgorithm as e:
raise InvalidAlgorithmError(e)
else:
raise InvalidPrivateKeyFileError(exc)
else:
raise InvalidPrivateKeyFileError(exc) from exc
except ValueError as e:
raise InvalidPrivateKeyFileError(e) from e
except TypeError as e:
raise InvalidPassphraseError(e)
raise InvalidPassphraseError(e) from 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
@@ -713,10 +719,10 @@ def load_publickey(
try:
publickey_loader = publickey_loaders[key_format]
except KeyError:
except KeyError as e:
raise InvalidKeyFormatError(
f"{key_format} is not a valid key format ({','.join(publickey_loaders)})"
)
) from e
if not os.path.exists(path):
raise InvalidPublicKeyFileError(f"No file was found at {path}")
@@ -729,9 +735,9 @@ def load_publickey(
data=content,
)
except ValueError as e:
raise InvalidPublicKeyFileError(e)
raise InvalidPublicKeyFileError(e) from e
except UnsupportedAlgorithm as e:
raise InvalidAlgorithmError(e)
raise InvalidAlgorithmError(e) from e
return publickey
@@ -749,8 +755,7 @@ def compare_publickeys(pk1: PublicKeyTypes, pk2: PublicKeyTypes) -> bool:
serialization.Encoding.Raw, serialization.PublicFormat.Raw
)
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(
@@ -761,12 +766,11 @@ def compare_encryption_algorithms(
ea2, serialization.NoEncryption
):
return True
elif isinstance(ea1, serialization.BestAvailableEncryption) and isinstance(
if isinstance(ea1, serialization.BestAvailableEncryption) and isinstance(
ea2, serialization.BestAvailableEncryption
):
return ea1.password == ea2.password
else:
return False
return False
def get_encryption_algorithm(
@@ -775,7 +779,7 @@ def get_encryption_algorithm(
try:
return serialization.BestAvailableEncryption(passphrase)
except ValueError as e:
raise InvalidPassphraseError(e)
raise InvalidPassphraseError(e) from e
def validate_comment(comment: str) -> None:
@@ -796,7 +800,7 @@ def extract_comment(path: str | os.PathLike) -> str:
else:
comment = ""
except (IOError, OSError) as e:
raise InvalidPublicKeyFileError(e)
raise InvalidPublicKeyFileError(e) from e
return comment

View File

@@ -177,10 +177,9 @@ class OpensshParser:
def _check_position(self, offset: int) -> int:
if self._pos + offset > len(self._data):
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.")
else:
return self._pos + offset
return self._pos + offset
@classmethod
def signature_data(cls, *, signature_string: bytes) -> dict[str, bytes | int]:
@@ -306,7 +305,9 @@ class _OpensshWriter:
try:
self.string(",".join(value).encode("ASCII"))
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

View File

@@ -41,7 +41,7 @@ def parse_serial(value: str | bytes) -> int:
except ValueError as exc:
raise ValueError(
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
return result

View File

@@ -102,8 +102,7 @@ def convert_relative_to_datetime(
if parsed_result.group("prefix") == "+":
return now + offset
else:
return now - offset
return now - offset
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:
argument_spec = create_default_argspec()
argument_spec.update_argspec(
terms_agreed=dict(type="bool", default=False),
state=dict(
type="str", required=True, choices=["absent", "present", "changed_key"]
),
allow_creation=dict(type="bool", default=True),
contact=dict(type="list", elements="str", default=[]),
new_account_key_src=dict(type="path"),
new_account_key_content=dict(type="str", no_log=True),
new_account_key_passphrase=dict(type="str", no_log=True),
external_account_binding=dict(
type="dict",
options=dict(
kid=dict(type="str", required=True),
alg=dict(
type="str", required=True, choices=["HS256", "HS384", "HS512"]
),
key=dict(type="str", required=True, no_log=True),
),
),
terms_agreed={"type": "bool", "default": False},
state={
"type": "str",
"required": True,
"choices": ["absent", "present", "changed_key"],
},
allow_creation={"type": "bool", "default": True},
contact={"type": "list", "elements": "str", "default": []},
new_account_key_src={"type": "path"},
new_account_key_content={"type": "str", "no_log": True},
new_account_key_passphrase={"type": "str", "no_log": True},
external_account_binding={
"type": "dict",
"options": {
"kid": {"type": "str", "required": True},
"alg": {
"type": "str",
"required": True,
"choices": ["HS256", "HS384", "HS512"],
},
"key": {"type": "str", "required": True, "no_log": True},
},
},
)
argument_spec.update(
mutually_exclusive=[("new_account_key_src", "new_account_key_content")],
@@ -254,7 +258,7 @@ def main() -> t.NoReturn:
if not module.check_mode:
# Deactivate it
deactivate_payload = {"status": "deactivated"}
result, info = client.send_signed_request(
result, _info = client.send_signed_request(
t.cast(str, client.account_uri),
deactivate_payload,
error_msg="Failed to deactivate account",
@@ -302,7 +306,7 @@ def main() -> t.NoReturn:
except KeyParsingError as e:
raise ModuleFailException(
f"Error while parsing new account key: {e.msg}"
)
) from e
# Verify that the account exists and has not been deactivated
created, account_data = account.setup_account(allow_creation=False)
if created:
@@ -335,18 +339,18 @@ def main() -> t.NoReturn:
key_data=new_key_data,
)
# Send request and verify result
result, info = client.send_signed_request(
result, _info = client.send_signed_request(
url,
data,
error_msg="Failed to rollover account key",
expected_status_codes=[200],
)
if module._diff:
if module._diff: # pylint: disable=protected-access
client.account_key_data = new_key_data
if client.account_jws_header:
client.account_jws_header["alg"] = new_key_data["alg"]
diff_after = account.get_account_data() or {}
elif module._diff:
elif module._diff: # pylint: disable=protected-access
# Kind of fake diff_after
diff_after = dict(diff_before)
diff_after["public_account_key"] = new_key_data["jwk"]
@@ -355,7 +359,7 @@ def main() -> t.NoReturn:
"changed": changed,
"account_uri": client.account_uri,
}
if module._diff:
if module._diff: # pylint: disable=protected-access
result["diff"] = {
"before": diff_before,
"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:
argument_spec = create_default_argspec()
argument_spec.update_argspec(
retrieve_orders=dict(
type="str", default="ignore", choices=["ignore", "url_list", "object_list"]
),
retrieve_orders={
"type": "str",
"default": "ignore",
"choices": ["ignore", "url_list", "object_list"],
},
)
module = argument_spec.create_ansible_module(supports_check_mode=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:
argument_spec = create_default_argspec(with_account=False)
argument_spec.update_argspec(
certificate_path=dict(type="path"),
certificate_content=dict(type="str"),
certificate_path={"type": "path"},
certificate_content={"type": "str"},
)
argument_spec.update(
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 (
combine_identifier,
normalize_combined_identifier,
split_identifier,
wait_for_validation,
)
from ansible_collections.community.crypto.plugins.module_utils._acme.errors import (
@@ -760,7 +759,6 @@ class ACMECertificateClient:
data: dict[str, t.Any] = {}
data_dns: dict[str, list[str]] = {}
for type_identifier, authz in self.authorizations.items():
identifier_type, identifier = split_identifier(type_identifier)
# Skip valid authentications: their challenges are already valid
# and do not need to be returned
if authz.status == "valid":
@@ -802,7 +800,7 @@ class ACMECertificateClient:
# Step 2: validate pending challenges
authzs_to_wait_for = []
for type_identifier, authz in self.authorizations.items():
for authz in self.authorizations.values():
if authz.status == "pending":
if self.challenge is not None:
authz.call_validate(
@@ -951,52 +949,54 @@ def main() -> t.NoReturn:
argument_spec = create_default_argspec(with_certificate=True)
argument_spec.argument_spec["csr"]["aliases"] = ["src"]
argument_spec.update_argspec(
modify_account=dict(type="bool", default=True),
account_email=dict(type="str"),
agreement=dict(
type="str",
removed_in_version="4.0.0",
removed_from_collection="community.crypto",
),
terms_agreed=dict(type="bool", default=False),
challenge=dict(
type="str",
default="http-01",
choices=["http-01", "dns-01", "tls-alpn-01", NO_CHALLENGE],
),
data=dict(type="dict"),
dest=dict(type="path", aliases=["cert"]),
fullchain_dest=dict(type="path", aliases=["fullchain"]),
chain_dest=dict(type="path", aliases=["chain"]),
remaining_days=dict(type="int", default=10),
deactivate_authzs=dict(type="bool", default=False),
force=dict(type="bool", default=False),
retrieve_all_alternates=dict(type="bool", default=False),
select_chain=dict(
type="list",
elements="dict",
options=dict(
test_certificates=dict(
type="str", default="all", choices=["first", "last", "all"]
),
issuer=dict(type="dict"),
subject=dict(type="dict"),
subject_key_identifier=dict(type="str"),
authority_key_identifier=dict(type="str"),
),
),
include_renewal_cert_id=dict(
type="str",
choices=["never", "when_ari_supported", "always"],
default="never",
),
profile=dict(type="str"),
order_creation_error_strategy=dict(
type="str",
default="auto",
choices=["auto", "always", "fail", "retry_without_replaces_cert_id"],
),
order_creation_max_retries=dict(type="int", default=3),
modify_account={"type": "bool", "default": True},
account_email={"type": "str"},
agreement={
"type": "str",
"removed_in_version": "4.0.0",
"removed_from_collection": "community.crypto",
},
terms_agreed={"type": "bool", "default": False},
challenge={
"type": "str",
"default": "http-01",
"choices": ["http-01", "dns-01", "tls-alpn-01", NO_CHALLENGE],
},
data={"type": "dict"},
dest={"type": "path", "aliases": ["cert"]},
fullchain_dest={"type": "path", "aliases": ["fullchain"]},
chain_dest={"type": "path", "aliases": ["chain"]},
remaining_days={"type": "int", "default": 10},
deactivate_authzs={"type": "bool", "default": False},
force={"type": "bool", "default": False},
retrieve_all_alternates={"type": "bool", "default": False},
select_chain={
"type": "list",
"elements": "dict",
"options": {
"test_certificates": {
"type": "str",
"default": "all",
"choices": ["first", "last", "all"],
},
"issuer": {"type": "dict"},
"subject": {"type": "dict"},
"subject_key_identifier": {"type": "str"},
"authority_key_identifier": {"type": "str"},
},
},
include_renewal_cert_id={
"type": "str",
"choices": ["never", "when_ari_supported", "always"],
"default": "never",
},
profile={"type": "str"},
order_creation_error_strategy={
"type": "str",
"default": "auto",
"choices": ["auto", "always", "fail", "retry_without_replaces_cert_id"],
},
order_creation_max_retries={"type": "int", "default": 3},
)
argument_spec.update(
required_one_of=[
@@ -1045,9 +1045,9 @@ def main() -> t.NoReturn:
if module.params["deactivate_authzs"]:
client.deactivate_authzs()
data, data_dns = client.get_challenges_data(first_step=is_first_step)
auths = dict()
auths = {}
assert client.authorizations is not None
for k, v in client.authorizations.items():
for v in client.authorizations.values():
# Remove "type:" from key
auths[v.identifier] = v.to_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:
argument_spec = create_default_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)

View File

@@ -388,15 +388,15 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
def main() -> t.NoReturn:
argument_spec = create_default_argspec(with_certificate=True)
argument_spec.update_argspec(
deactivate_authzs=dict(type="bool", default=True),
replaces_cert_id=dict(type="str"),
profile=dict(type="str"),
order_creation_error_strategy=dict(
type="str",
default="auto",
choices=["auto", "always", "fail", "retry_without_replaces_cert_id"],
),
order_creation_max_retries=dict(type="int", default=3),
deactivate_authzs={"type": "bool", "default": True},
replaces_cert_id={"type": "str"},
profile={"type": "str"},
order_creation_error_strategy={
"type": "str",
"default": "auto",
"choices": ["auto", "always", "fail", "retry_without_replaces_cert_id"],
},
order_creation_max_retries={"type": "int", "default": 3},
)
module = argument_spec.create_ansible_module()

View File

@@ -340,29 +340,31 @@ if t.TYPE_CHECKING:
def main() -> t.NoReturn:
argument_spec = create_default_argspec(with_certificate=True)
argument_spec.update_argspec(
order_uri=dict(type="str", required=True),
cert_dest=dict(type="path"),
fullchain_dest=dict(type="path"),
chain_dest=dict(type="path"),
deactivate_authzs=dict(
type="str",
default="always",
choices=["never", "always", "on_error", "on_success"],
),
retrieve_all_alternates=dict(type="bool", default=False),
select_chain=dict(
type="list",
elements="dict",
options=dict(
test_certificates=dict(
type="str", default="all", choices=["first", "last", "all"]
),
issuer=dict(type="dict"),
subject=dict(type="dict"),
subject_key_identifier=dict(type="str"),
authority_key_identifier=dict(type="str"),
),
),
order_uri={"type": "str", "required": True},
cert_dest={"type": "path"},
fullchain_dest={"type": "path"},
chain_dest={"type": "path"},
deactivate_authzs={
"type": "str",
"default": "always",
"choices": ["never", "always", "on_error", "on_success"],
},
retrieve_all_alternates={"type": "bool", "default": False},
select_chain={
"type": "list",
"elements": "dict",
"options": {
"test_certificates": {
"type": "str",
"default": "all",
"choices": ["first", "last", "all"],
},
"issuer": {"type": "dict"},
"subject": {"type": "dict"},
"subject_key_identifier": {"type": "str"},
"authority_key_identifier": {"type": "str"},
},
},
)
module = argument_spec.create_ansible_module()
@@ -371,7 +373,7 @@ def main() -> t.NoReturn:
try:
client = ACMECertificateClient(module=module, backend=backend)
select_chain_matcher = client.parse_select_chain(module.params["select_chain"])
other = dict()
other = {}
done = False
order = None
try:

View File

@@ -374,7 +374,7 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
def main() -> t.NoReturn:
argument_spec = create_default_argspec(with_certificate=False)
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)

View File

@@ -252,9 +252,9 @@ if t.TYPE_CHECKING:
def main() -> t.NoReturn:
argument_spec = create_default_argspec(with_certificate=False)
argument_spec.update_argspec(
order_uri=dict(type="str", required=True),
challenge=dict(type="str", choices=["http-01", "dns-01", "tls-alpn-01"]),
deactivate_authzs=dict(type="bool", default=True),
order_uri={"type": "str", "required": True},
challenge={"type": "str", "choices": ["http-01", "dns-01", "tls-alpn-01"]},
deactivate_authzs={"type": "bool", "default": True},
)
module = argument_spec.create_ansible_module()
@@ -330,13 +330,13 @@ def main() -> t.NoReturn:
changed=len(authzs_with_challenges_to_wait_for) > 0,
account_uri=client.client.account_uri,
validating_challenges=[
dict(
identifier=authz.identifier,
identifier_type=authz.identifier_type,
authz_url=authz.url,
challenge_type=challenge_type,
challenge_url=challenge.url if challenge else None,
)
{
"identifier": authz.identifier,
"identifier_type": authz.identifier_type,
"authz_url": authz.url,
"challenge_type": challenge_type,
"challenge_url": challenge.url if challenge else None,
}
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:
argument_spec = create_default_argspec(with_account=False)
argument_spec.update_argspec(
certificate_path=dict(type="path"),
certificate_content=dict(type="str"),
use_ari=dict(type="bool", default=True),
ari_algorithm=dict(
type="str", choices=["standard", "start"], default="standard"
),
remaining_days=dict(type="int"),
remaining_percentage=dict(type="float"),
now=dict(type="str"),
treat_parsing_error_as_non_existing=dict(type="bool", default=False),
certificate_path={"type": "path"},
certificate_content={"type": "str"},
use_ari={"type": "bool", "default": True},
ari_algorithm={
"type": "str",
"choices": ["standard", "start"],
"default": "standard",
},
remaining_days={"type": "int"},
remaining_percentage={"type": "float"},
now={"type": "str"},
treat_parsing_error_as_non_existing={"type": "bool", "default": False},
)
argument_spec.update(
mutually_exclusive=[("certificate_path", "certificate_content")],
@@ -196,13 +198,13 @@ def main() -> t.NoReturn:
module = argument_spec.create_ansible_module(supports_check_mode=True)
backend = create_backend(module, needs_acme_v2=True)
result = dict(
changed=False,
msg="The certificate is still valid and no condition was reached",
exists=False,
parsable=False,
supports_ari=False,
)
result = {
"changed": False,
"msg": "The certificate is still valid and no condition was reached",
"exists": False,
"parsable": False,
"supports_ari": False,
}
def complete(should_renew: bool, **kwargs) -> t.NoReturn:
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:
argument_spec = create_default_argspec(require_account_key=False)
argument_spec.update_argspec(
private_key_src=dict(type="path"),
private_key_content=dict(type="str", no_log=True),
private_key_passphrase=dict(type="str", no_log=True),
certificate=dict(type="path", required=True),
revoke_reason=dict(type="int"),
private_key_src={"type": "path"},
private_key_content={"type": "str", "no_log": True},
private_key_passphrase={"type": "str", "no_log": True},
certificate={"type": "path", "required": True},
revoke_reason={"type": "int"},
)
argument_spec.update(
required_one_of=[
@@ -186,7 +186,9 @@ def main() -> t.NoReturn:
passphrase=passphrase,
)
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
jws_header = {
"alg": private_key_data["alg"],

View File

@@ -200,13 +200,13 @@ def encode_octet_string(octet_string: bytes) -> bytes:
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
challenge=dict(type="str", required=True, choices=["tls-alpn-01"]),
challenge_data=dict(type="dict", required=True),
private_key_src=dict(type="path"),
private_key_content=dict(type="str", no_log=True),
private_key_passphrase=dict(type="str", no_log=True),
),
argument_spec={
"challenge": {"type": "str", "required": True, "choices": ["tls-alpn-01"]},
"challenge_data": {"type": "dict", "required": True},
"private_key_src": {"type": "path"},
"private_key_content": {"type": "str", "no_log": True},
"private_key_passphrase": {"type": "str", "no_log": True},
},
required_one_of=(["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:
raise ModuleFailException(f"Error while loading private key: {e}")
raise ModuleFailException(f"Error while loading private key: {e}") from e
if isinstance(
private_key,
(
@@ -317,6 +317,8 @@ def main() -> t.NoReturn:
private_key,
cryptography.hazmat.primitives.hashes.SHA256(),
)
else:
raise AssertionError("Can never be reached") # pragma: no cover
module.exit_json(
changed=True,

View File

@@ -240,12 +240,14 @@ from ansible_collections.community.crypto.plugins.module_utils._acme.errors impo
def main() -> t.NoReturn:
argument_spec = create_default_argspec(require_account_key=False)
argument_spec.update_argspec(
url=dict(type="str"),
method=dict(
type="str", choices=["get", "post", "directory-only"], default="get"
),
content=dict(type="str"),
fail_on_acme_error=dict(type="bool", default=True),
url={"type": "str"},
method={
"type": "str",
"choices": ["get", "post", "directory-only"],
"default": "get",
},
content={"type": "str"},
fail_on_acme_error={"type": "bool", "default": True},
)
argument_spec.update(
required_if=[
@@ -263,7 +265,7 @@ def main() -> t.NoReturn:
try:
# Get hold of ACMEClient and ACMEAccount objects (includes directory)
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
# Do we have to do more requests?
if method != "directory-only":
@@ -283,12 +285,14 @@ def main() -> t.NoReturn:
encode_payload=False,
fail_on_error=False,
)
else:
raise AssertionError("Can never be reached") # pragma: no cover
# Update results
result.update(
dict(
headers=info,
output_text=to_native(data),
)
{
"headers": info,
"output_text": to_native(data),
}
)
# See if we can parse the result as JSON
try:

View File

@@ -328,12 +328,12 @@ def format_cert(cert: Certificate) -> str:
def check_cycle(
module: AnsibleModule,
occured_certificates: set[cryptography.x509.Certificate],
next: Certificate,
next_certificate: Certificate,
) -> 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:
module.fail_json(msg="Found cycle while building certificate chain")
occured_certificates.add(next_cert)
@@ -341,11 +341,15 @@ def check_cycle(
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
input_chain=dict(type="str", required=True),
root_certificates=dict(type="list", required=True, elements="path"),
intermediate_certificates=dict(type="list", default=[], elements="path"),
),
argument_spec={
"input_chain": {"type": "str", "required": True},
"root_certificates": {"type": "list", "required": True, "elements": "path"},
"intermediate_certificates": {
"type": "list",
"default": [],
"elements": "path",
},
},
supports_check_mode=True,
)
@@ -382,7 +386,7 @@ def main() -> t.NoReturn:
# Try to complete chain
current: Certificate | None = chain[-1]
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:
# Do not try to complete the chain when it is already ending with a root certificate
current = None

View File

@@ -161,6 +161,7 @@ CRYPTOGRAPHY_VERSION: str | None
CRYPTOGRAPHY_IMP_ERR: str | None
try:
import cryptography
import cryptography.hazmat.primitives.asymmetric
from cryptography.exceptions import UnsupportedAlgorithm
try:
@@ -216,13 +217,12 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
has_dsa_sign = False
try:
# 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
try:
# added later in 1.5
# pylint: disable-next=pointless-statement
cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign
dsa.DSAPrivateKey.sign # pylint: disable=pointless-statement
has_dsa_sign = True
except AttributeError:
pass
@@ -234,13 +234,12 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
has_rsa_sign = False
try:
# 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
try:
# added later in 1.4
# pylint: disable-next=pointless-statement
cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign
rsa.RSAPrivateKey.sign # pylint: disable=pointless-statement
has_rsa_sign = True
except AttributeError:
pass
@@ -252,21 +251,17 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
has_ed25519_sign = False
try:
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed25519/
import cryptography.hazmat.primitives.asymmetric.ed25519
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
from cryptography.hazmat.primitives.asymmetric import ed25519
try:
Ed25519PrivateKey.from_private_bytes(b"")
ed25519.Ed25519PrivateKey.from_private_bytes(b"")
except ValueError:
pass
has_ed25519 = True
try:
# added with the primitive in 2.6
# pylint: disable-next=pointless-statement
cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey.sign
ed25519.Ed25519PrivateKey.sign # pylint: disable=pointless-statement
has_ed25519_sign = True
except AttributeError:
pass
@@ -278,19 +273,17 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
has_ed448_sign = False
try:
# added in 2.6 - https://cryptography.io/en/latest/hazmat/primitives/asymmetric/ed448/
import cryptography.hazmat.primitives.asymmetric.ed448
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
from cryptography.hazmat.primitives.asymmetric import ed448
try:
Ed448PrivateKey.from_private_bytes(b"")
ed448.Ed448PrivateKey.from_private_bytes(b"")
except ValueError:
pass
has_ed448 = True
try:
# added with the primitive in 2.6
# pylint: disable-next=pointless-statement
cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey.sign
ed448.Ed448PrivateKey.sign # pylint: disable=pointless-statement
has_ed448_sign = True
except AttributeError:
pass
@@ -302,24 +295,21 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
has_x25519_full = False
try:
# 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:
# added later in 2.5
# pylint: disable-next=pointless-statement
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.private_bytes
x25519.X25519PrivateKey.private_bytes # pylint: disable=pointless-statement
full = True
except AttributeError:
full = False
try:
if full:
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.from_private_bytes(
b""
)
x25519.X25519PrivateKey.from_private_bytes(b"")
else:
# Some versions do not support serialization and deserialization - use generate() instead
cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey.generate()
x25519.X25519PrivateKey.generate()
except ValueError:
pass
@@ -332,12 +322,10 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
has_x448 = False
try:
# 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:
from cryptography.hazmat.primitives.asymmetric.x448 import X448PrivateKey
X448PrivateKey.from_private_bytes(b"")
x448.X448PrivateKey.from_private_bytes(b"")
except ValueError:
pass
@@ -351,13 +339,12 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
curves = []
try:
# 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
try:
# added later in 1.5
# pylint: disable-next=pointless-statement
cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign
ec.EllipticCurvePrivateKey.sign # pylint: disable=pointless-statement
has_ec_sign = True
except AttributeError:
pass
@@ -365,14 +352,10 @@ def add_crypto_information(module: AnsibleModule) -> dict[str, t.Any]:
pass
else:
for curve_name, constructor_name in CURVES:
ecclass = cryptography.hazmat.primitives.asymmetric.ec.__dict__.get(
constructor_name
)
ecclass = ec.__dict__.get(constructor_name)
if ecclass:
try:
cryptography.hazmat.primitives.asymmetric.ec.generate_private_key(
curve=ecclass()
)
ec.generate_private_key(curve=ecclass())
curves.append(curve_name)
except UnsupportedAlgorithm:
pass
@@ -419,7 +402,7 @@ def add_openssl_information(module: AnsibleModule) -> dict[str, t.Any]:
}
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:
openssl_result["version_output"] = out
parts = out.split(None, 2)

View File

@@ -786,11 +786,7 @@ class EcsCertificate:
self.set_cert_details(module)
assert self.cert_details is not None
if (
self.cert_status == "EXPIRED"
or self.cert_status == "SUSPENDED"
or self.cert_status == "REVOKED"
):
if self.cert_status in ("EXPIRED", "SUSPENDED", "REVOKED"):
return False
if self.cert_days < module.params["remaining_days"]:
return False
@@ -803,7 +799,7 @@ class EcsCertificate:
# Read the CSR contents
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()
# 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
trackingId=self.tracking_id, Body=body
)
else:
raise AssertionError("Can never be reached") # pragma: no cover
self.tracking_id = result.get("trackingId")
self.set_cert_details(module)
assert self.cert_details is not None
@@ -914,61 +912,61 @@ class EcsCertificate:
def custom_fields_spec() -> dict[str, dict[str, str]]:
return dict(
text1=dict(type="str"),
text2=dict(type="str"),
text3=dict(type="str"),
text4=dict(type="str"),
text5=dict(type="str"),
text6=dict(type="str"),
text7=dict(type="str"),
text8=dict(type="str"),
text9=dict(type="str"),
text10=dict(type="str"),
text11=dict(type="str"),
text12=dict(type="str"),
text13=dict(type="str"),
text14=dict(type="str"),
text15=dict(type="str"),
number1=dict(type="float"),
number2=dict(type="float"),
number3=dict(type="float"),
number4=dict(type="float"),
number5=dict(type="float"),
date1=dict(type="str"),
date2=dict(type="str"),
date3=dict(type="str"),
date4=dict(type="str"),
date5=dict(type="str"),
email1=dict(type="str"),
email2=dict(type="str"),
email3=dict(type="str"),
email4=dict(type="str"),
email5=dict(type="str"),
dropdown1=dict(type="str"),
dropdown2=dict(type="str"),
dropdown3=dict(type="str"),
dropdown4=dict(type="str"),
dropdown5=dict(type="str"),
)
return {
"text1": {"type": "str"},
"text2": {"type": "str"},
"text3": {"type": "str"},
"text4": {"type": "str"},
"text5": {"type": "str"},
"text6": {"type": "str"},
"text7": {"type": "str"},
"text8": {"type": "str"},
"text9": {"type": "str"},
"text10": {"type": "str"},
"text11": {"type": "str"},
"text12": {"type": "str"},
"text13": {"type": "str"},
"text14": {"type": "str"},
"text15": {"type": "str"},
"number1": {"type": "float"},
"number2": {"type": "float"},
"number3": {"type": "float"},
"number4": {"type": "float"},
"number5": {"type": "float"},
"date1": {"type": "str"},
"date2": {"type": "str"},
"date3": {"type": "str"},
"date4": {"type": "str"},
"date5": {"type": "str"},
"email1": {"type": "str"},
"email2": {"type": "str"},
"email3": {"type": "str"},
"email4": {"type": "str"},
"email5": {"type": "str"},
"dropdown1": {"type": "str"},
"dropdown2": {"type": "str"},
"dropdown3": {"type": "str"},
"dropdown4": {"type": "str"},
"dropdown5": {"type": "str"},
}
def ecs_certificate_argument_spec() -> dict[str, dict[str, t.Any]]:
return dict(
backup=dict(type="bool", default=False),
force=dict(type="bool", default=False),
path=dict(type="path", required=True),
full_chain_path=dict(type="path"),
tracking_id=dict(type="int"),
remaining_days=dict(type="int", default=30),
request_type=dict(
type="str",
default="new",
choices=["new", "renew", "reissue", "validate_only"],
),
cert_type=dict(
type="str",
choices=[
return {
"backup": {"type": "bool", "default": False},
"force": {"type": "bool", "default": False},
"path": {"type": "path", "required": True},
"full_chain_path": {"type": "path"},
"tracking_id": {"type": "int"},
"remaining_days": {"type": "int", "default": 30},
"request_type": {
"type": "str",
"default": "new",
"choices": ["new", "renew", "reissue", "validate_only"],
},
"cert_type": {
"type": "str",
"choices": [
"STANDARD_SSL",
"ADVANTAGE_SSL",
"UC_SSL",
@@ -984,26 +982,27 @@ def ecs_certificate_argument_spec() -> dict[str, dict[str, t.Any]]:
"CDS_ENT_PRO",
"SMIME_ENT",
],
),
csr=dict(type="str"),
subject_alt_name=dict(type="list", elements="str"),
eku=dict(
type="str", choices=["SERVER_AUTH", "CLIENT_AUTH", "SERVER_AND_CLIENT_AUTH"]
),
ct_log=dict(type="bool"),
client_id=dict(type="int", default=1),
org=dict(type="str"),
ou=dict(type="list", elements="str"),
end_user_key_storage_agreement=dict(type="bool"),
tracking_info=dict(type="str"),
requester_name=dict(type="str", required=True),
requester_email=dict(type="str", required=True),
requester_phone=dict(type="str", required=True),
additional_emails=dict(type="list", elements="str"),
custom_fields=dict(type="dict", default=None, options=custom_fields_spec()),
cert_expiry=dict(type="str"),
cert_lifetime=dict(type="str", choices=["P1Y", "P2Y", "P3Y"]),
)
},
"csr": {"type": "str"},
"subject_alt_name": {"type": "list", "elements": "str"},
"eku": {
"type": "str",
"choices": ["SERVER_AUTH", "CLIENT_AUTH", "SERVER_AND_CLIENT_AUTH"],
},
"ct_log": {"type": "bool"},
"client_id": {"type": "int", "default": 1},
"org": {"type": "str"},
"ou": {"type": "list", "elements": "str"},
"end_user_key_storage_agreement": {"type": "bool"},
"tracking_info": {"type": "str"},
"requester_name": {"type": "str", "required": True},
"requester_email": {"type": "str", "required": True},
"requester_phone": {"type": "str", "required": True},
"additional_emails": {"type": "list", "elements": "str"},
"custom_fields": {"type": "dict", "options": custom_fields_spec()},
"cert_expiry": {"type": "str"},
"cert_lifetime": {"type": "str", "choices": ["P1Y", "P2Y", "P3Y"]},
}
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
# one, in case the verification method has changed.
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
try:
@@ -321,18 +322,15 @@ class EcsDomain:
clientId=module.params["client_id"], domain=module.params["domain_name"]
)
self.set_domain_details(domain_details)
if (
self.domain_status != "APPROVED"
and self.domain_status != "INITIAL_VERIFICATION"
and self.domain_status != "RE_VERIFICATION"
if self.domain_status not in (
"APPROVED",
"INITIAL_VERIFICATION",
"RE_VERIFICATION",
):
return False
# If domain verification is in process, we want to return the random values and treat it as a valid.
if (
self.domain_status == "INITIAL_VERIFICATION"
or self.domain_status == "RE_VERIFICATION"
):
if self.domain_status in ("INITIAL_VERIFICATION", "RE_VERIFICATION"):
# Unless the verification method has changed, in which case we need to do a reverify request.
if self.verification_method != module.params["verification_method"]:
return False
@@ -383,7 +381,7 @@ class EcsDomain:
module.params["verification_method"] == "dns"
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'
if module.params["verification_method"] == "dns":
if (
@@ -445,14 +443,16 @@ class EcsDomain:
def ecs_domain_argument_spec() -> dict[str, dict[str, t.Any]]:
return dict(
client_id=dict(type="int", default=1),
domain_name=dict(type="str", required=True),
verification_method=dict(
type="str", required=True, choices=["dns", "email", "manual", "web_server"]
),
verification_email=dict(type="str"),
)
return {
"client_id": {"type": "int", "default": 1},
"domain_name": {"type": "str", "required": True},
"verification_method": {
"type": "str",
"required": True,
"choices": ["dns", "email", "manual", "web_server"],
},
"verification_email": {"type": "str"},
}
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:
module = AnsibleModule(
argument_spec=dict(
ca_cert=dict(type="path"),
host=dict(type="str", required=True),
port=dict(type="int", required=True),
proxy_host=dict(type="str"),
proxy_port=dict(type="int", default=8080),
server_name=dict(type="str"),
timeout=dict(type="int", default=10),
select_crypto_backend=dict(
type="str", choices=["auto", "cryptography"], default="auto"
),
starttls=dict(type="str", choices=["mysql"]),
ciphers=dict(type="list", elements="str"),
asn1_base64=dict(type="bool", default=True),
tls_ctx_options=dict(type="list", elements="raw"),
get_certificate_chain=dict(type="bool", default=False),
),
argument_spec={
"ca_cert": {"type": "path"},
"host": {"type": "str", "required": True},
"port": {"type": "int", "required": True},
"proxy_host": {"type": "str"},
"proxy_port": {"type": "int", "default": 8080},
"server_name": {"type": "str"},
"timeout": {"type": "int", "default": 10},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
"starttls": {"type": "str", "choices": ["mysql"]},
"ciphers": {"type": "list", "elements": "str"},
"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")
@@ -444,7 +446,8 @@ def main() -> t.NoReturn:
try:
# 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:
module.fail_json(
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):
if not chain:
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())
unverified_der_chain = _convert_chain(ssl_obj.get_unverified_chain())
else:
@@ -482,7 +492,9 @@ def main() -> t.NoReturn:
(
c
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
]

View File

@@ -500,21 +500,21 @@ class Handler:
def get_device_by_uuid(self, uuid: str | None) -> str | None:
"""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:
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:
return None
return stdout.strip()
def get_device_by_label(self, label: str) -> str | None:
"""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"]
if label is 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:
return None
return stdout.strip()
@@ -536,7 +536,7 @@ class Handler:
class CryptHandler(Handler):
def __init__(self, module: AnsibleModule) -> None:
super(CryptHandler, self).__init__(module)
super().__init__(module)
self._cryptsetup_bin = self._module.get_bin_path("cryptsetup", True)
def get_container_name_by_device(self, device: str) -> str | None:
@@ -722,7 +722,7 @@ class CryptHandler(Handler):
except Exception as exc:
raise ValueError(
f"Error while wiping LUKS container signatures for {device}: {exc}"
)
) from exc
def run_luks_add_key(
self,
@@ -869,7 +869,7 @@ class CryptHandler(Handler):
class ConditionsHandler(Handler):
def __init__(self, module: AnsibleModule, crypthandler: CryptHandler) -> None:
super(ConditionsHandler, self).__init__(module)
super().__init__(module)
self._crypthandler = crypthandler
self.device = self.get_device_name()
@@ -953,6 +953,7 @@ class ConditionsHandler(Handler):
) or self._module.params["state"] != "closed":
# conditions for close not fulfilled
return False
luks_is_open = False
if self.device is not None:
name = self._crypthandler.get_container_name_by_device(self.device)
@@ -1084,52 +1085,58 @@ class ConditionsHandler(Handler):
def run_module() -> t.NoReturn:
# available arguments/parameters that a user can pass
module_args = dict(
state=dict(
type="str",
default="present",
choices=["present", "absent", "opened", "closed"],
),
device=dict(type="str"),
name=dict(type="str"),
keyfile=dict(type="path"),
new_keyfile=dict(type="path"),
remove_keyfile=dict(type="path"),
passphrase=dict(type="str", no_log=True),
new_passphrase=dict(type="str", no_log=True),
remove_passphrase=dict(type="str", no_log=True),
passphrase_encoding=dict(
type="str", default="text", choices=["text", "base64"], no_log=False
),
keyslot=dict(type="int", no_log=False),
new_keyslot=dict(type="int", no_log=False),
remove_keyslot=dict(type="int", no_log=False),
force_remove_last_key=dict(type="bool", default=False),
keysize=dict(type="int"),
label=dict(type="str"),
uuid=dict(type="str"),
type=dict(type="str", choices=["luks1", "luks2"]),
cipher=dict(type="str"),
hash=dict(type="str"),
pbkdf=dict(
type="dict",
options=dict(
iteration_time=dict(type="float"),
iteration_count=dict(type="int"),
algorithm=dict(type="str", choices=["argon2i", "argon2id", "pbkdf2"]),
memory=dict(type="int"),
parallel=dict(type="int"),
),
mutually_exclusive=[("iteration_time", "iteration_count")],
),
sector_size=dict(type="int"),
perf_same_cpu_crypt=dict(type="bool", default=False),
perf_submit_from_crypt_cpus=dict(type="bool", default=False),
perf_no_read_workqueue=dict(type="bool", default=False),
perf_no_write_workqueue=dict(type="bool", default=False),
persistent=dict(type="bool", default=False),
allow_discards=dict(type="bool", default=False),
)
module_args = {
"state": {
"type": "str",
"default": "present",
"choices": ["present", "absent", "opened", "closed"],
},
"device": {"type": "str"},
"name": {"type": "str"},
"keyfile": {"type": "path"},
"new_keyfile": {"type": "path"},
"remove_keyfile": {"type": "path"},
"passphrase": {"type": "str", "no_log": True},
"new_passphrase": {"type": "str", "no_log": True},
"remove_passphrase": {"type": "str", "no_log": True},
"passphrase_encoding": {
"type": "str",
"default": "text",
"choices": ["text", "base64"],
"no_log": False,
},
"keyslot": {"type": "int", "no_log": False},
"new_keyslot": {"type": "int", "no_log": False},
"remove_keyslot": {"type": "int", "no_log": False},
"force_remove_last_key": {"type": "bool", "default": False},
"keysize": {"type": "int"},
"label": {"type": "str"},
"uuid": {"type": "str"},
"type": {"type": "str", "choices": ["luks1", "luks2"]},
"cipher": {"type": "str"},
"hash": {"type": "str"},
"pbkdf": {
"type": "dict",
"options": {
"iteration_time": {"type": "float"},
"iteration_count": {"type": "int"},
"algorithm": {
"type": "str",
"choices": ["argon2i", "argon2id", "pbkdf2"],
},
"memory": {"type": "int"},
"parallel": {"type": "int"},
},
"mutually_exclusive": [("iteration_time", "iteration_count")],
},
"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 = [
("keyfile", "passphrase"),
@@ -1145,9 +1152,12 @@ def run_module() -> t.NoReturn:
supports_check_mode=True,
mutually_exclusive=mutually_exclusive,
)
module.run_command_environ_update = dict(
LANG="C", LC_ALL="C", LC_MESSAGES="C", LC_CTYPE="C"
)
module.run_command_environ_update = {
"LANG": "C",
"LC_ALL": "C",
"LC_MESSAGES": "C",
"LC_CTYPE": "C",
}
if module.params["device"] is not None:
try:

View File

@@ -304,7 +304,7 @@ from ansible_collections.community.crypto.plugins.module_utils._version import (
class Certificate(OpensshModule):
def __init__(self, module: AnsibleModule) -> None:
super(Certificate, self).__init__(module=module)
super().__init__(module=module)
self.ssh_keygen = KeygenCommand(self.module)
self.identifier: str = self.module.params["identifier"] or ""
@@ -406,19 +406,18 @@ class Certificate(OpensshModule):
def _should_generate(self) -> bool:
if self.regenerate == "never":
return self.original_data is None
elif self.regenerate == "fail":
if self.regenerate == "fail":
if self.original_data and not self._is_fully_valid():
self.module.fail_json(
msg="Certificate does not match the provided options.",
cert=get_cert_dict(self.original_data),
)
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()
elif self.regenerate == "full_idempotence":
if self.regenerate == "full_idempotence":
return self.original_data is None or not self._is_fully_valid()
else:
return True
return True
def _is_fully_valid(self) -> bool:
if self.original_data is None:
@@ -542,10 +541,10 @@ class Certificate(OpensshModule):
serial_number=self.serial_number,
signature_algorithm=self.signature_algorithm,
signing_key_path=self.signing_key,
type=self.type,
cert_type=self.type,
time_parameters=self.time_parameters,
use_agent=self.use_agent,
environ_update=dict(TZ="UTC"),
environ_update={"TZ": "UTC"},
check_rc=True,
)
@@ -625,38 +624,43 @@ def get_cert_dict(data: OpensshCertificate | None) -> dict[str, t.Any]:
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
force=dict(type="bool", default=False),
identifier=dict(type="str"),
options=dict(type="list", elements="str"),
path=dict(type="path", required=True),
pkcs11_provider=dict(type="str"),
principals=dict(type="list", elements="str"),
public_key=dict(type="path"),
regenerate=dict(
type="str",
default="partial_idempotence",
choices=[
argument_spec={
"force": {"type": "bool", "default": False},
"identifier": {"type": "str"},
"options": {"type": "list", "elements": "str"},
"path": {"type": "path", "required": True},
"pkcs11_provider": {"type": "str"},
"principals": {"type": "list", "elements": "str"},
"public_key": {"type": "path"},
"regenerate": {
"type": "str",
"default": "partial_idempotence",
"choices": [
"never",
"fail",
"partial_idempotence",
"full_idempotence",
"always",
],
),
signature_algorithm=dict(
type="str", choices=["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"]
),
signing_key=dict(type="path"),
serial_number=dict(type="int"),
state=dict(type="str", default="present", choices=["absent", "present"]),
type=dict(type="str", choices=["host", "user"]),
use_agent=dict(type="bool", default=False),
valid_at=dict(type="str"),
valid_from=dict(type="str"),
valid_to=dict(type="str"),
ignore_timestamps=dict(type="bool", default=False),
),
},
"signature_algorithm": {
"type": "str",
"choices": ["ssh-rsa", "rsa-sha2-256", "rsa-sha2-512"],
},
"signing_key": {"type": "path"},
"serial_number": {"type": "int"},
"state": {
"type": "str",
"default": "present",
"choices": ["absent", "present"],
},
"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,
add_file_common_args=True,
required_if=[

View File

@@ -209,41 +209,45 @@ from ansible_collections.community.crypto.plugins.module_utils._openssh.backends
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
size=dict(type="int"),
type=dict(
type="str",
default="rsa",
choices=["rsa", "dsa", "rsa1", "ecdsa", "ed25519"],
),
force=dict(type="bool", default=False),
path=dict(type="path", required=True),
comment=dict(type="str"),
regenerate=dict(
type="str",
default="partial_idempotence",
choices=[
argument_spec={
"state": {
"type": "str",
"default": "present",
"choices": ["present", "absent"],
},
"size": {"type": "int"},
"type": {
"type": "str",
"default": "rsa",
"choices": ["rsa", "dsa", "rsa1", "ecdsa", "ed25519"],
},
"force": {"type": "bool", "default": False},
"path": {"type": "path", "required": True},
"comment": {"type": "str"},
"regenerate": {
"type": "str",
"default": "partial_idempotence",
"choices": [
"never",
"fail",
"partial_idempotence",
"full_idempotence",
"always",
],
),
passphrase=dict(type="str", no_log=True),
private_key_format=dict(
type="str",
default="auto",
no_log=False,
choices=["auto", "pkcs1", "pkcs8", "ssh"],
),
backend=dict(
type="str",
default="auto",
choices=["auto", "cryptography", "opensshbin"],
),
),
},
"passphrase": {"type": "str", "no_log": True},
"private_key_format": {
"type": "str",
"default": "auto",
"no_log": False,
"choices": ["auto", "pkcs1", "pkcs8", "ssh"],
},
"backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography", "opensshbin"],
},
},
supports_check_mode=True,
add_file_common_args=True,
)

View File

@@ -269,7 +269,7 @@ class CertificateSigningRequestModule(OpenSSLObject):
def __init__(
self, module: AnsibleModule, module_backend: CertificateSigningRequestBackend
) -> None:
super(CertificateSigningRequestModule, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -308,7 +308,7 @@ class CertificateSigningRequestModule(OpenSSLObject):
self.module_backend.set_existing(csr_bytes=None)
if self.backup and not self.check_mode:
self.backup_file = module.backup_local(self.path)
super(CertificateSigningRequestModule, self).remove(module)
super().remove(module)
def dump(self) -> dict[str, t.Any]:
"""Serialize the object into a dictionary."""
@@ -327,13 +327,17 @@ class CertificateSigningRequestModule(OpenSSLObject):
def main() -> t.NoReturn:
argument_spec = get_csr_argument_spec()
argument_spec.argument_spec.update(
dict(
state=dict(type="str", default="present", choices=["absent", "present"]),
force=dict(type="bool", default=False),
path=dict(type="path", required=True),
backup=dict(type="bool", default=False),
return_content=dict(type="bool", default=False),
)
{
"state": {
"type": "str",
"default": "present",
"choices": ["absent", "present"],
},
"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(
[("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:
module = AnsibleModule(
argument_spec=dict(
path=dict(type="path"),
content=dict(type="str"),
name_encoding=dict(
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
),
argument_spec={
"path": {"type": "path"},
"content": {"type": "str"},
"name_encoding": {
"type": "str",
"default": "ignore",
"choices": ["ignore", "idna", "unicode"],
},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
},
required_one_of=(["path", "content"],),
mutually_exclusive=(["path", "content"],),
supports_check_mode=True,

View File

@@ -178,9 +178,9 @@ class CertificateSigningRequestModule:
def main() -> t.NoReturn:
argument_spec = get_csr_argument_spec()
argument_spec.argument_spec.update(
dict(
content=dict(type="str"),
)
{
"content": {"type": "str"},
}
)
module = argument_spec.create_ansible_module(
supports_check_mode=True,

View File

@@ -252,7 +252,7 @@ class DHParameterBase:
class DHParameterAbsent(DHParameterBase):
def __init__(self, module: AnsibleModule) -> None:
super(DHParameterAbsent, self).__init__(module)
super().__init__(module)
def _do_generate(self, module: AnsibleModule) -> None:
"""Actually generate the DH params."""
@@ -265,7 +265,7 @@ class DHParameterAbsent(DHParameterBase):
class DHParameterOpenSSL(DHParameterBase):
def __init__(self, module: AnsibleModule) -> None:
super(DHParameterOpenSSL, self).__init__(module)
super().__init__(module)
self.openssl_bin = module.get_bin_path("openssl", True)
def _do_generate(self, module: AnsibleModule) -> None:
@@ -320,7 +320,7 @@ class DHParameterOpenSSL(DHParameterBase):
class DHParameterCryptography(DHParameterBase):
def __init__(self, module: AnsibleModule) -> None:
super(DHParameterCryptography, self).__init__(module)
super().__init__(module)
def _do_generate(self, module: AnsibleModule) -> None:
"""Actually generate the DH params."""
@@ -359,17 +359,23 @@ def main() -> t.NoReturn:
"""Main function"""
module = AnsibleModule(
argument_spec=dict(
state=dict(type="str", default="present", choices=["absent", "present"]),
size=dict(type="int", default=4096),
force=dict(type="bool", default=False),
path=dict(type="path", required=True),
backup=dict(type="bool", default=False),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography", "openssl"]
),
return_content=dict(type="bool", default=False),
),
argument_spec={
"state": {
"type": "str",
"default": "present",
"choices": ["absent", "present"],
},
"size": {"type": "int", "default": 4096},
"force": {"type": "bool", "default": False},
"path": {"type": "path", "required": True},
"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,
add_file_common_args=True,
)

View File

@@ -368,7 +368,7 @@ class Pkcs(OpenSSLObject):
path: str
def __init__(self, module: AnsibleModule, iter_size_default: int = 2048) -> None:
super(Pkcs, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -413,7 +413,7 @@ class Pkcs(OpenSSLObject):
with open(self.certificate_path, "rb") as fh:
self.certificate_content = fh.read()
except (IOError, OSError) as exc:
raise PkcsError(exc)
raise PkcsError(exc) from exc
elif certificate_content is not None:
self.certificate_content = to_bytes(certificate_content)
@@ -423,7 +423,7 @@ class Pkcs(OpenSSLObject):
with open(self.privatekey_path, "rb") as fh:
self.privatekey_content = fh.read()
except (IOError, OSError) as exc:
raise PkcsError(exc)
raise PkcsError(exc) from exc
elif privatekey_content is not None:
self.privatekey_content = to_bytes(privatekey_content)
@@ -480,11 +480,9 @@ class Pkcs(OpenSSLObject):
def _get_friendly_name(self, pkcs12: PKCS12) -> bytes | None:
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."""
state_and_perms = super(Pkcs, self).check(
module=module, perms_required=perms_required
)
state_and_perms = super().check(module=module, perms_required=perms_required)
def _check_pkey_passphrase() -> bool:
if self.privatekey_passphrase:
@@ -599,7 +597,7 @@ class Pkcs(OpenSSLObject):
def remove(self, module: AnsibleModule) -> None:
if self.backup:
self.backup_file = module.backup_local(self.path)
super(Pkcs, self).remove(module)
super().remove(module)
def parse(self) -> tuple[
bytes | None,
@@ -616,7 +614,7 @@ class Pkcs(OpenSSLObject):
pkcs12_content = pkcs12_fh.read()
return self.parse_bytes(pkcs12_content)
except IOError as exc:
raise PkcsError(exc)
raise PkcsError(exc) from exc
def generate(self, module: AnsibleModule) -> None:
# Empty method because OpenSSLObject wants this
@@ -635,7 +633,7 @@ class Pkcs(OpenSSLObject):
class PkcsCryptography(Pkcs):
def __init__(self, module: AnsibleModule) -> None:
super(PkcsCryptography, self).__init__(module, iter_size_default=50000)
super().__init__(module, iter_size_default=50000)
if (
self.encryption_level == "compatibility2022"
and not CRYPTOGRAPHY_HAS_COMPATIBILITY2022
@@ -656,7 +654,7 @@ class PkcsCryptography(Pkcs):
passphrase=self.privatekey_passphrase,
)
except OpenSSLBadPassphraseError as exc:
raise PkcsError(exc)
raise PkcsError(exc) from exc
cert = None
if self.certificate_content:
@@ -725,7 +723,7 @@ class PkcsCryptography(Pkcs):
return (pkey, crt, other_certs, friendly_name)
except ValueError as exc:
raise PkcsError(exc)
raise PkcsError(exc) from exc
def _dump_privatekey(self, pkcs12: PKCS12) -> bytes | None:
return (
@@ -759,39 +757,49 @@ def select_backend(module: AnsibleModule) -> Pkcs:
def main() -> t.NoReturn:
argument_spec = dict(
action=dict(type="str", default="export", choices=["export", "parse"]),
other_certificates=dict(
type="list", elements="path", aliases=["ca_certificates"]
),
other_certificates_parse_all=dict(type="bool", default=False),
other_certificates_content=dict(type="list", elements="str"),
certificate_path=dict(type="path"),
certificate_content=dict(type="str"),
force=dict(type="bool", default=False),
friendly_name=dict(type="str", aliases=["name"]),
encryption_level=dict(
type="str", choices=["auto", "compatibility2022"], default="auto"
),
iter_size=dict(type="int"),
maciter_size=dict(
type="int",
removed_in_version="4.0.0",
removed_from_collection="community.crypto",
),
passphrase=dict(type="str", no_log=True),
path=dict(type="path", required=True),
privatekey_passphrase=dict(type="str", no_log=True),
privatekey_path=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
state=dict(type="str", default="present", choices=["absent", "present"]),
src=dict(type="path"),
backup=dict(type="bool", default=False),
return_content=dict(type="bool", default=False),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
)
argument_spec = {
"action": {"type": "str", "default": "export", "choices": ["export", "parse"]},
"other_certificates": {
"type": "list",
"elements": "path",
"aliases": ["ca_certificates"],
},
"other_certificates_parse_all": {"type": "bool", "default": False},
"other_certificates_content": {"type": "list", "elements": "str"},
"certificate_path": {"type": "path"},
"certificate_content": {"type": "str"},
"force": {"type": "bool", "default": False},
"friendly_name": {"type": "str", "aliases": ["name"]},
"encryption_level": {
"type": "str",
"choices": ["auto", "compatibility2022"],
"default": "auto",
},
"iter_size": {"type": "int"},
"maciter_size": {
"type": "int",
"removed_in_version": "4.0.0",
"removed_from_collection": "community.crypto",
},
"passphrase": {"type": "str", "no_log": True},
"path": {"type": "path", "required": True},
"privatekey_passphrase": {"type": "str", "no_log": True},
"privatekey_path": {"type": "path"},
"privatekey_content": {"type": "str", "no_log": True},
"state": {
"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 = [
["action", "parse", ["src"]],
@@ -837,7 +845,7 @@ def main() -> t.NoReturn:
pkcs12.write(module, pkcs12_content, 0o600)
changed = True
else:
pkey, cert, other_certs, friendly_name = pkcs12.parse()
pkey, cert, other_certs, _friendly_name = pkcs12.parse()
dump_content = "".join(
[
to_native(pem)

View File

@@ -185,7 +185,7 @@ class PrivateKeyModule(OpenSSLObject):
def __init__(
self, module: AnsibleModule, module_backend: PrivateKeyBackend
) -> None:
super(PrivateKeyModule, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -216,8 +216,6 @@ class PrivateKeyModule(OpenSSLObject):
self.backup_file = module.backup_local(self.path)
self.module_backend.generate_private_key()
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)
self.changed = True
elif self.module_backend.needs_conversion():
@@ -227,8 +225,6 @@ class PrivateKeyModule(OpenSSLObject):
self.backup_file = module.backup_local(self.path)
self.module_backend.convert_private_key()
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)
self.changed = True
@@ -244,7 +240,7 @@ class PrivateKeyModule(OpenSSLObject):
self.module_backend.set_existing(privatekey_bytes=None)
if self.backup and not self.check_mode:
self.backup_file = module.backup_local(self.path)
super(PrivateKeyModule, self).remove(module)
super().remove(module)
def dump(self) -> dict[str, t.Any]:
"""Serialize the object into a dictionary."""
@@ -262,13 +258,17 @@ def main() -> t.NoReturn:
argument_spec = get_privatekey_argument_spec()
argument_spec.argument_spec.update(
dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
force=dict(type="bool", default=False),
path=dict(type="path", required=True),
backup=dict(type="bool", default=False),
return_content=dict(type="bool", default=False),
)
{
"state": {
"type": "str",
"default": "present",
"choices": ["present", "absent"],
},
"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(
supports_check_mode=True,

View File

@@ -89,7 +89,7 @@ class PrivateKeyConvertModule(OpenSSLObject):
def __init__(
self, module: AnsibleModule, module_backend: PrivateKeyConvertBackend
) -> None:
super(PrivateKeyConvertModule, self).__init__(
super().__init__(
path=module.params["dest_path"],
state="present",
force=False,
@@ -145,10 +145,10 @@ def main() -> t.NoReturn:
argument_spec = get_privatekey_argument_spec()
argument_spec.argument_spec.update(
dict(
dest_path=dict(type="path", required=True),
backup=dict(type="bool", default=False),
)
{
"dest_path": {"type": "path", "required": True},
"backup": {"type": "bool", "default": False},
}
)
module = argument_spec.create_ansible_module(
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:
module = AnsibleModule(
argument_spec=dict(
path=dict(type="path"),
content=dict(type="str", no_log=True),
passphrase=dict(type="str", no_log=True),
return_private_key_data=dict(type="bool", default=False),
check_consistency=dict(type="bool", default=False),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
),
argument_spec={
"path": {"type": "path"},
"content": {"type": "str", "no_log": True},
"passphrase": {"type": "str", "no_log": True},
"return_private_key_data": {"type": "bool", "default": False},
"check_consistency": {"type": "bool", "default": False},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
},
required_one_of=(["path", "content"],),
mutually_exclusive=(["path", "content"],),
supports_check_mode=True,
)
result = dict(
can_load_key=False,
can_parse_key=False,
key_is_consistent=None,
)
result = {
"can_load_key": False,
"can_parse_key": False,
"key_is_consistent": None,
}
if module.params["content"] is not None:
data = module.params["content"].encode("utf-8")

View File

@@ -233,7 +233,7 @@ class PublicKeyError(OpenSSLObjectError):
class PublicKey(OpenSSLObject):
def __init__(self, module: AnsibleModule) -> None:
super(PublicKey, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -287,11 +287,10 @@ class PublicKey(OpenSSLObject):
crypto_serialization.Encoding.OpenSSH,
crypto_serialization.PublicFormat.OpenSSH,
)
else:
return self.privatekey.public_key().public_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
)
return self.privatekey.public_key().public_bytes(
crypto_serialization.Encoding.PEM,
crypto_serialization.PublicFormat.SubjectPublicKeyInfo,
)
def generate(self, module: AnsibleModule) -> None:
"""Generate the public key."""
@@ -316,9 +315,9 @@ class PublicKey(OpenSSLObject):
self.changed = True
except OpenSSLBadPassphraseError as exc:
raise PublicKeyError(exc)
raise PublicKeyError(exc) from exc
except (IOError, OSError) as exc:
raise PublicKeyError(exc)
raise PublicKeyError(exc) from exc
self.fingerprint = get_fingerprint(
path=self.privatekey_path,
@@ -331,12 +330,10 @@ class PublicKey(OpenSSLObject):
elif module.set_fs_attributes_if_different(file_args, False):
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."""
state_and_perms = super(PublicKey, self).check(
module=module, perms_required=perms_required
)
state_and_perms = super().check(module=module, perms_required=perms_required)
def _check_privatekey() -> bool:
if self.privatekey_path is not None and not os.path.exists(
@@ -374,7 +371,7 @@ class PublicKey(OpenSSLObject):
try:
desired_publickey = self._create_publickey(module)
except OpenSSLBadPassphraseError as exc:
raise PublicKeyError(exc)
raise PublicKeyError(exc) from exc
return publickey_content == desired_publickey
@@ -386,7 +383,7 @@ class PublicKey(OpenSSLObject):
def remove(self, module: AnsibleModule) -> None:
if self.backup:
self.backup_file = module.backup_local(self.path)
super(PublicKey, self).remove(module)
super().remove(module)
def dump(self) -> dict[str, t.Any]:
"""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
)
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
result["diff"] = {
"before": self.diff_before,
"after": self.diff_after,
}
return result
@@ -420,20 +417,26 @@ class PublicKey(OpenSSLObject):
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
force=dict(type="bool", default=False),
path=dict(type="path", required=True),
privatekey_path=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
format=dict(type="str", default="PEM", choices=["OpenSSH", "PEM"]),
privatekey_passphrase=dict(type="str", no_log=True),
backup=dict(type="bool", default=False),
select_crypto_backend=dict(
type="str", choices=["auto", "cryptography"], default="auto"
),
return_content=dict(type="bool", default=False),
),
argument_spec={
"state": {
"type": "str",
"default": "present",
"choices": ["present", "absent"],
},
"force": {"type": "bool", "default": False},
"path": {"type": "path", "required": True},
"privatekey_path": {"type": "path"},
"privatekey_content": {"type": "str", "no_log": True},
"format": {"type": "str", "default": "PEM", "choices": ["OpenSSH", "PEM"]},
"privatekey_passphrase": {"type": "str", "no_log": True},
"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,
add_file_common_args=True,
required_if=[

View File

@@ -166,23 +166,25 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.module_ba
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
path=dict(type="path"),
content=dict(type="str", no_log=True),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
),
argument_spec={
"path": {"type": "path"},
"content": {"type": "str", "no_log": True},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
},
required_one_of=(["path", "content"],),
mutually_exclusive=(["path", "content"],),
supports_check_mode=True,
)
result = dict(
can_load_key=False,
can_parse_key=False,
key_is_consistent=None,
)
result = {
"can_load_key": False,
"can_parse_key": False,
"key_is_consistent": None,
}
if module.params["content"] is not None:
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):
def __init__(self, module: AnsibleModule) -> None:
super(SignatureBase, self).__init__(
super().__init__(
path=module.params["path"],
state="present",
force=False,
@@ -163,7 +163,7 @@ class SignatureBase(OpenSSLObject):
class SignatureCryptography(SignatureBase):
def __init__(self, module: AnsibleModule) -> None:
super(SignatureCryptography, self).__init__(module)
super().__init__(module)
def run(self) -> dict[str, t.Any]:
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
@@ -224,20 +224,22 @@ class SignatureCryptography(SignatureBase):
return result
except Exception as e:
raise OpenSSLObjectError(e)
raise OpenSSLObjectError(e) from e
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
privatekey_path=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
privatekey_passphrase=dict(type="str", no_log=True),
path=dict(type="path", required=True),
select_crypto_backend=dict(
type="str", choices=["auto", "cryptography"], default="auto"
),
),
argument_spec={
"privatekey_path": {"type": "path"},
"privatekey_content": {"type": "str", "no_log": True},
"privatekey_passphrase": {"type": "str", "no_log": True},
"path": {"type": "path", "required": True},
"select_crypto_backend": {
"type": "str",
"choices": ["auto", "cryptography"],
"default": "auto",
},
},
mutually_exclusive=(["privatekey_path", "privatekey_content"],),
required_one_of=(["privatekey_path", "privatekey_content"],),
supports_check_mode=True,

View File

@@ -123,7 +123,7 @@ from ansible_collections.community.crypto.plugins.module_utils._crypto.support i
class SignatureInfoBase(OpenSSLObject):
def __init__(self, module: AnsibleModule) -> None:
super(SignatureInfoBase, self).__init__(
super().__init__(
path=module.params["path"],
state="present",
force=False,
@@ -152,7 +152,7 @@ class SignatureInfoBase(OpenSSLObject):
class SignatureInfoCryptography(SignatureInfoBase):
def __init__(self, module: AnsibleModule) -> None:
super(SignatureInfoCryptography, self).__init__(module)
super().__init__(module)
def run(self) -> dict[str, t.Any]:
_padding = cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15()
@@ -229,20 +229,22 @@ class SignatureInfoCryptography(SignatureInfoBase):
return result
except Exception as e:
raise OpenSSLObjectError(e)
raise OpenSSLObjectError(e) from e
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
certificate_path=dict(type="path"),
certificate_content=dict(type="str"),
path=dict(type="path", required=True),
signature=dict(type="str", required=True),
select_crypto_backend=dict(
type="str", choices=["auto", "cryptography"], default="auto"
),
),
argument_spec={
"certificate_path": {"type": "path"},
"certificate_content": {"type": "str"},
"path": {"type": "path", "required": True},
"signature": {"type": "str", "required": True},
"select_crypto_backend": {
"type": "str",
"choices": ["auto", "cryptography"],
"default": "auto",
},
},
mutually_exclusive=(["certificate_path", "certificate_content"],),
required_one_of=(["certificate_path", "certificate_content"],),
supports_check_mode=True,

View File

@@ -267,7 +267,7 @@ if t.TYPE_CHECKING:
class CertificateAbsent(OpenSSLObject):
def __init__(self, module: AnsibleModule) -> None:
super(CertificateAbsent, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -284,7 +284,7 @@ class CertificateAbsent(OpenSSLObject):
def remove(self, module: AnsibleModule) -> None:
if self.backup:
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]:
result = {
@@ -305,7 +305,7 @@ class GenericCertificate(OpenSSLObject):
"""Retrieve a certificate using the given module backend."""
def __init__(self, module: AnsibleModule, module_backend: CertificateBackend):
super(GenericCertificate, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -339,12 +339,10 @@ class GenericCertificate(OpenSSLObject):
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."""
return (
super(GenericCertificate, self).check(
module=module, perms_required=perms_required
)
super().check(module=module, perms_required=perms_required)
and not self.module_backend.needs_regeneration()
)
@@ -368,12 +366,16 @@ def main() -> t.NoReturn:
add_ownca_provider_to_argument_spec(argument_spec)
add_selfsigned_provider_to_argument_spec(argument_spec)
argument_spec.argument_spec.update(
dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
path=dict(type="path", required=True),
backup=dict(type="bool", default=False),
return_content=dict(type="bool", default=False),
)
{
"state": {
"type": "str",
"default": "present",
"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"]))
module = argument_spec.create_ansible_module(

View File

@@ -143,13 +143,13 @@ except ImportError:
def parse_certificate(
input: bytes, strict: bool = False
certificate_content: bytes, strict: bool = False
) -> tuple[bytes, t.Literal["pem", "der"], str | None]:
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":
pems = split_pem_list(to_text(input))
pems = split_pem_list(to_text(certificate_content))
if len(pems) > 1 and strict:
raise ValueError(
f"The input contains {len(pems)} PEM objects, expecting only one since strict=true"
@@ -159,15 +159,15 @@ def parse_certificate(
raise ValueError(
f"type is {pem_header_type!r}, expecting CERTIFICATE or X509 CERTIFICATE"
)
input = base64.b64decode(content)
certificate_content = base64.b64decode(content)
else:
pem_header_type = None
return input, input_format, pem_header_type
return certificate_content, input_format, pem_header_type
class X509CertificateConvertModule(OpenSSLObject):
def __init__(self, module: AnsibleModule) -> None:
super(X509CertificateConvertModule, self).__init__(
super().__init__(
path=module.params["dest_path"],
state="present",
force=False,
@@ -287,16 +287,16 @@ class X509CertificateConvertModule(OpenSSLObject):
def main() -> t.NoReturn:
argument_spec = dict(
src_path=dict(type="path"),
src_content=dict(type="str"),
src_content_base64=dict(type="bool", default=False),
format=dict(type="str", required=True, choices=["pem", "der"]),
strict=dict(type="bool", default=False),
dest_path=dict(type="path", required=True),
backup=dict(type="bool", default=False),
verify_cert_parsable=dict(type="bool", default=False),
)
argument_spec = {
"src_path": {"type": "path"},
"src_content": {"type": "str"},
"src_content_base64": {"type": "bool", "default": False},
"format": {"type": "str", "required": True, "choices": ["pem", "der"]},
"strict": {"type": "bool", "default": False},
"dest_path": {"type": "path", "required": True},
"backup": {"type": "bool", "default": False},
"verify_cert_parsable": {"type": "bool", "default": False},
}
module = AnsibleModule(
argument_spec,
supports_check_mode=True,

View File

@@ -410,17 +410,21 @@ from ansible_collections.community.crypto.plugins.module_utils._time import (
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
path=dict(type="path"),
content=dict(type="str"),
valid_at=dict(type="dict"),
name_encoding=dict(
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
),
select_crypto_backend=dict(
type="str", default="auto", choices=["auto", "cryptography"]
),
),
argument_spec={
"path": {"type": "path"},
"content": {"type": "str"},
"valid_at": {"type": "dict"},
"name_encoding": {
"type": "str",
"default": "ignore",
"choices": ["ignore", "idna", "unicode"],
},
"select_crypto_backend": {
"type": "str",
"default": "auto",
"choices": ["auto", "cryptography"],
},
},
required_one_of=(["path", "content"],),
mutually_exclusive=(["path", "content"],),
supports_check_mode=True,
@@ -460,7 +464,7 @@ def main() -> t.NoReturn:
not_before = module_backend.get_not_before()
not_after = module_backend.get_not_after()
result["valid_at"] = dict()
result["valid_at"] = {}
if valid_at:
for k, v in valid_at.items():
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_selfsigned_provider_to_argument_spec(argument_spec)
argument_spec.argument_spec.update(
dict(
content=dict(type="str"),
)
{
"content": {"type": "str"},
}
)
module = argument_spec.create_ansible_module(
supports_check_mode=True,

View File

@@ -507,7 +507,7 @@ class CRLError(OpenSSLObjectError):
class CRL(OpenSSLObject):
def __init__(self, module: AnsibleModule) -> None:
super(CRL, self).__init__(
super().__init__(
path=module.params["path"],
state=module.params["state"],
force=module.params["force"],
@@ -650,7 +650,7 @@ class CRL(OpenSSLObject):
passphrase=self.privatekey_passphrase,
)
except OpenSSLBadPassphraseError as exc:
raise CRLError(exc)
raise CRLError(exc) from exc
self.crl = None
try:
@@ -704,7 +704,7 @@ class CRL(OpenSSLObject):
def remove(self, module: AnsibleModule) -> None:
if self.backup:
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]) -> (
tuple[
@@ -750,27 +750,27 @@ class CRL(OpenSSLObject):
entry["invalidity_date"],
entry["invalidity_date_critical"],
)
else:
return (
entry["serial_number"],
entry["revocation_date"],
issuer,
entry["issuer_critical"],
entry["reason"],
entry["reason_critical"],
entry["invalidity_date"],
entry["invalidity_date_critical"],
)
return (
entry["serial_number"],
entry["revocation_date"],
issuer,
entry["issuer_critical"],
entry["reason"],
entry["reason_critical"],
entry["invalidity_date"],
entry["invalidity_date_critical"],
)
def check(
self,
module: AnsibleModule,
*,
perms_required: bool = True,
ignore_conversion: bool = True,
) -> bool:
"""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
)
@@ -843,16 +843,16 @@ class CRL(OpenSSLObject):
)
)
except ValueError as e:
raise CRLError(e)
raise CRLError(e) from e
crl = set_last_update(crl, value=self.last_update)
if self.next_update is not None:
crl = set_next_update(crl, value=self.next_update)
if self.update and self.crl:
new_entries = set(
[self._compress_entry(entry) for entry in self.revoked_certificates]
)
new_entries = {
self._compress_entry(entry) for entry in self.revoked_certificates
}
for entry in self.crl:
decoded_entry = self._compress_entry(
cryptography_decode_revoked_certificate(entry)
@@ -888,8 +888,7 @@ class CRL(OpenSSLObject):
self.crl = crl.sign(self.privatekey, digest)
if self.format == "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:
result = None
@@ -996,49 +995,53 @@ class CRL(OpenSSLObject):
if self.return_content:
result["crl"] = self.crl_content
result["diff"] = dict(
before=self.diff_before,
after=self.diff_after,
)
result["diff"] = {
"before": self.diff_before,
"after": self.diff_after,
}
return result
def main() -> t.NoReturn:
module = AnsibleModule(
argument_spec=dict(
state=dict(type="str", default="present", choices=["present", "absent"]),
crl_mode=dict(
type="str",
default="generate",
choices=["generate", "update"],
),
force=dict(type="bool", default=False),
backup=dict(type="bool", default=False),
path=dict(type="path", required=True),
format=dict(type="str", default="pem", choices=["pem", "der"]),
privatekey_path=dict(type="path"),
privatekey_content=dict(type="str", no_log=True),
privatekey_passphrase=dict(type="str", no_log=True),
issuer=dict(type="dict"),
issuer_ordered=dict(type="list", elements="dict"),
last_update=dict(type="str", default="+0s"),
next_update=dict(type="str"),
digest=dict(type="str", default="sha256"),
ignore_timestamps=dict(type="bool", default=False),
return_content=dict(type="bool", default=False),
revoked_certificates=dict(
type="list",
elements="dict",
options=dict(
path=dict(type="path"),
content=dict(type="str"),
serial_number=dict(type="raw"),
revocation_date=dict(type="str", default="+0s"),
issuer=dict(type="list", elements="str"),
issuer_critical=dict(type="bool", default=False),
reason=dict(
type="str",
choices=[
argument_spec={
"state": {
"type": "str",
"default": "present",
"choices": ["present", "absent"],
},
"crl_mode": {
"type": "str",
"default": "generate",
"choices": ["generate", "update"],
},
"force": {"type": "bool", "default": False},
"backup": {"type": "bool", "default": False},
"path": {"type": "path", "required": True},
"format": {"type": "str", "default": "pem", "choices": ["pem", "der"]},
"privatekey_path": {"type": "path"},
"privatekey_content": {"type": "str", "no_log": True},
"privatekey_passphrase": {"type": "str", "no_log": True},
"issuer": {"type": "dict"},
"issuer_ordered": {"type": "list", "elements": "dict"},
"last_update": {"type": "str", "default": "+0s"},
"next_update": {"type": "str"},
"digest": {"type": "str", "default": "sha256"},
"ignore_timestamps": {"type": "bool", "default": False},
"return_content": {"type": "bool", "default": False},
"revoked_certificates": {
"type": "list",
"elements": "dict",
"options": {
"path": {"type": "path"},
"content": {"type": "str"},
"serial_number": {"type": "raw"},
"revocation_date": {"type": "str", "default": "+0s"},
"issuer": {"type": "list", "elements": "str"},
"issuer_critical": {"type": "bool", "default": False},
"reason": {
"type": "str",
"choices": [
"unspecified",
"key_compromise",
"ca_compromise",
@@ -1050,21 +1053,25 @@ def main() -> t.NoReturn:
"aa_compromise",
"remove_from_crl",
],
),
reason_critical=dict(type="bool", default=False),
invalidity_date=dict(type="str"),
invalidity_date_critical=dict(type="bool", default=False),
),
required_one_of=[["path", "content", "serial_number"]],
mutually_exclusive=[["path", "content", "serial_number"]],
),
name_encoding=dict(
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
),
serial_numbers=dict(
type="str", default="integer", choices=["integer", "hex-octets"]
),
),
},
"reason_critical": {"type": "bool", "default": False},
"invalidity_date": {"type": "str"},
"invalidity_date_critical": {"type": "bool", "default": False},
},
"required_one_of": [["path", "content", "serial_number"]],
"mutually_exclusive": [["path", "content", "serial_number"]],
},
"name_encoding": {
"type": "str",
"default": "ignore",
"choices": ["ignore", "idna", "unicode"],
},
"serial_numbers": {
"type": "str",
"default": "integer",
"choices": ["integer", "hex-octets"],
},
},
required_if=[
("state", "present", ["privatekey_path", "privatekey_content"], 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:
module = AnsibleModule(
argument_spec=dict(
path=dict(type="path"),
content=dict(type="str"),
list_revoked_certificates=dict(type="bool", default=True),
name_encoding=dict(
type="str", default="ignore", choices=["ignore", "idna", "unicode"]
),
),
argument_spec={
"path": {"type": "path"},
"content": {"type": "str"},
"list_revoked_certificates": {"type": "bool", "default": True},
"name_encoding": {
"type": "str",
"default": "ignore",
"choices": ["ignore", "idna", "unicode"],
},
},
required_one_of=(["path", "content"],),
mutually_exclusive=(["path", "content"],),
supports_check_mode=True,

View File

@@ -36,7 +36,7 @@ if t.TYPE_CHECKING:
class _ModuleExitException(Exception):
def __init__(self, result: dict[str, t.Any]) -> None:
super(_ModuleExitException, self).__init__()
super().__init__()
self.result = result
@@ -187,10 +187,12 @@ class AnsibleActionModule:
collection_name=d.get("collection_name"),
)
else:
# pylint: disable-next=unknown-option-value
self.deprecate( # pylint: disable=ansible-deprecated-no-version
d
)
else:
# pylint: disable-next=unknown-option-value
self.deprecate( # pylint: disable=ansible-deprecated-no-version
kwargs["deprecations"]
)
@@ -226,9 +228,9 @@ class ActionModuleBase(ActionBase, metaclass=abc.ABCMeta):
def run(self, tmp=None, task_vars=None) -> dict[str, t.Any]:
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
try:

View File

@@ -25,8 +25,10 @@ class PluginGPGRunner(GPGRunner):
if executable is None:
try:
executable = get_bin_path("gpg")
except ValueError:
raise GPGError("Cannot find the `gpg` executable on the controller")
except ValueError as exc:
raise GPGError(
"Cannot find the `gpg` executable on the controller"
) from exc
self.executable = executable
self.cwd = cwd
@@ -45,17 +47,17 @@ class PluginGPGRunner(GPGRunner):
Raises a ``GPGError`` in case of errors.
"""
command = [self.executable] + command
p = Popen(
with Popen(
command, shell=False, cwd=self.cwd, stdin=PIPE, stdout=PIPE, stderr=PIPE
)
stdout, stderr = p.communicate(input=data)
stdout_n = to_native(stdout, errors="surrogate_or_replace")
stderr_n = to_native(stderr, errors="surrogate_or_replace")
if check_rc and p.returncode != 0:
raise GPGError(
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
) as p:
stdout, stderr = p.communicate(input=data)
stdout_n = to_native(stdout, errors="surrogate_or_replace")
stderr_n = to_native(stderr, errors="surrogate_or_replace")
if check_rc and p.returncode != 0:
raise GPGError(
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
__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/ecs_certificate.py no-assert
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/openssl_pkcs12.py no-assert
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",
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",
dict(
year=2024,
month=1,
day=1,
hour=0,
minute=11,
second=22,
microsecond=123000,
),
{
"year": 2024,
"month": 1,
"day": 1,
"hour": 0,
"minute": 11,
"second": 22,
"microsecond": 123000,
},
),
(
"2024-04-17T06:54:13.333333334Z",
dict(
year=2024,
month=4,
day=17,
hour=6,
minute=54,
second=13,
microsecond=333333,
),
{
"year": 2024,
"month": 4,
"day": 17,
"hour": 6,
"minute": 54,
"second": 13,
"microsecond": 333333,
},
),
(
"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",
dict(
year=2023,
month=12,
day=31,
hour=23,
minute=11,
second=22,
microsecond=123000,
),
{
"year": 2023,
"month": 12,
"day": 31,
"hour": 23,
"minute": 11,
"second": 22,
"microsecond": 123000,
},
),
],
)
@@ -192,22 +206,22 @@ TEST_INTERPOLATE_TIMESTAMP: list[
TIMEZONES,
[
(
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
0.0,
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
),
(
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
0.5,
dict(year=2024, month=1, day=1, hour=0, minute=30, second=0),
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 30, "second": 0},
),
(
dict(year=2024, month=1, day=1, hour=0, minute=0, second=0),
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
{"year": 2024, "month": 1, "day": 1, "hour": 0, "minute": 0, "second": 0},
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
1.0,
dict(year=2024, month=1, day=1, hour=1, minute=0, second=0),
{"year": 2024, "month": 1, "day": 1, "hour": 1, "minute": 0, "second": 0},
),
],
)
@@ -216,6 +230,7 @@ TEST_INTERPOLATE_TIMESTAMP: list[
class FakeBackend(CryptoBackend):
def parse_key(
self,
*,
key_file: str | os.PathLike | None = None,
key_content: str | None = None,
passphrase=None,
@@ -223,15 +238,16 @@ class FakeBackend(CryptoBackend):
raise BackendException("Not implemented in fake backend")
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:
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")
def get_ordered_csr_identifiers(
self,
*,
csr_filename: str | os.PathLike | None = None,
csr_content: str | bytes | None = None,
) -> t.NoReturn:
@@ -239,6 +255,7 @@ class FakeBackend(CryptoBackend):
def get_csr_identifiers(
self,
*,
csr_filename: str | os.PathLike | None = None,
csr_content: str | bytes | None = None,
) -> t.NoReturn:
@@ -246,17 +263,19 @@ class FakeBackend(CryptoBackend):
def get_cert_days(
self,
*,
cert_filename: str | os.PathLike | None = None,
cert_content: str | bytes | None = None,
now: datetime.datetime | None = None,
) -> t.NoReturn:
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")
def get_cert_information(
self,
*,
cert_filename: str | os.PathLike | None = None,
cert_content: str | bytes | None = None,
) -> 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)
@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(
timezone: datetime.timedelta, input: str, expected: dict[str, int]
timezone: datetime.timedelta, timestamp_str: str, expected: dict[str, int]
) -> None:
with freeze_time("2024-02-03 04:05:06 +00:00", tz_offset=timezone):
module = MagicMock()
backend = CryptographyBackend(module=module)
ts_expected = backend.get_utc_datetime(**expected)
timestamp = backend.parse_acme_timestamp(input)
timestamp = backend.parse_acme_timestamp(timestamp_str)
assert ts_expected == timestamp

View File

@@ -84,7 +84,7 @@ def test_csridentifiers_openssl(
def test_normalize_ip(ip: str, result: str) -> None:
module = MagicMock()
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)
@@ -142,15 +142,15 @@ def test_now(timezone: datetime.timedelta) -> None:
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(
timezone: datetime.timedelta, input: str, expected: dict[str, int]
timezone: datetime.timedelta, timestamp_str: str, expected: dict[str, int]
) -> None:
with freeze_time("2024-02-03 04:05:06", tz_offset=timezone):
module = MagicMock()
backend = OpenSSLCLIBackend(module=module, openssl_binary="openssl")
ts_expected = backend.get_utc_datetime(**expected)
timestamp = backend.parse_acme_timestamp(input)
timestamp = backend.parse_acme_timestamp(timestamp_str)
assert ts_expected == timestamp

View File

@@ -193,7 +193,7 @@ TEST_ACME_PROTOCOL_EXCEPTION: list[
},
"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'}",
{
"http_url": "https://ca.example.com/foo",
@@ -224,7 +224,7 @@ TEST_ACME_PROTOCOL_EXCEPTION: list[
},
"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'}",
{
"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(
input: dict[str, t.Any],
parameters: dict[str, t.Any],
from_json: t.Callable[[t.Any], t.NoReturn] | None,
msg: str,
args: dict[str, t.Any],
@@ -358,7 +360,7 @@ def test_acme_protocol_exception(
module = MagicMock()
module.from_json = from_json
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.module_fail_args)

View File

@@ -94,39 +94,51 @@ TEST_EPOCH_SECONDS: list[tuple[datetime.timedelta, float, dict[str, int]]] = (
[
(
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,
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,
dict(
year=1970,
day=1,
month=1,
hour=0,
minute=0,
second=0,
microsecond=1000,
),
{
"year": 1970,
"month": 1,
"day": 1,
"hour": 0,
"minute": 0,
"second": 0,
"microsecond": 1000,
},
),
(
3691.2,
dict(
year=1970,
day=1,
month=1,
hour=1,
minute=1,
second=31,
microsecond=200000,
),
{
"year": 1970,
"month": 1,
"day": 1,
"hour": 1,
"minute": 1,
"second": 31,
"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(
timezone: datetime.timedelta, input: datetime.datetime, expected: datetime.datetime
timezone: datetime.timedelta,
input_timestamp: datetime.datetime,
expected: datetime.datetime,
) -> None:
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
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
@pytest.mark.parametrize("timezone, input, expected", TEST_UTC_TIMEZONE)
@pytest.mark.parametrize("timezone, input_timestamp, expected", 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:
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
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

View File

@@ -13,7 +13,7 @@ from ansible_collections.community.crypto.plugins.modules import luks_device
class DummyModule:
# module to mock AnsibleModule class
def __init__(self):
self.params = dict()
self.params = {}
def fail_json(self, msg=""):
raise ValueError(msg)