mirror of
https://github.com/ansible-collections/community.crypto.git
synced 2026-03-26 21:33:25 +00:00
Make all module_utils and plugin_utils private (#887)
* Add leading underscore. Remove deprecated module utils. * Document module and plugin utils as private. Add changelog fragment. * Convert relative to absolute imports. * Remove unnecessary imports.
This commit is contained in:
219
plugins/module_utils/_acme/backends.py
Normal file
219
plugins/module_utils/_acme/backends.py
Normal file
@@ -0,0 +1,219 @@
|
||||
# Copyright (c) 2016 Michael Gruener <michael.gruener@chaosmoon.net>
|
||||
# Copyright (c) 2021 Felix Fontein <felix@fontein.de>
|
||||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
# Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time.
|
||||
# Do not use this from other collections or standalone plugins/modules!
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import abc
|
||||
import datetime
|
||||
import re
|
||||
import typing as t
|
||||
|
||||
from ansible_collections.community.crypto.plugins.module_utils._acme.errors import (
|
||||
BackendException,
|
||||
)
|
||||
from ansible_collections.community.crypto.plugins.module_utils._crypto.basic import (
|
||||
OpenSSLObjectError,
|
||||
)
|
||||
from ansible_collections.community.crypto.plugins.module_utils._time import (
|
||||
UTC,
|
||||
ensure_utc_timezone,
|
||||
from_epoch_seconds,
|
||||
get_epoch_seconds,
|
||||
get_now_datetime,
|
||||
get_relative_time_option,
|
||||
remove_timezone,
|
||||
)
|
||||
|
||||
|
||||
if t.TYPE_CHECKING:
|
||||
import os
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible_collections.community.crypto.plugins.module_utils._acme.certificates import (
|
||||
ChainMatcher,
|
||||
Criterium,
|
||||
)
|
||||
|
||||
|
||||
class CertificateInformation(t.NamedTuple):
|
||||
not_valid_after: datetime.datetime
|
||||
not_valid_before: datetime.datetime
|
||||
serial_number: int
|
||||
subject_key_identifier: bytes | None
|
||||
authority_key_identifier: bytes | None
|
||||
|
||||
|
||||
_FRACTIONAL_MATCHER = re.compile(
|
||||
r"^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})(|\.\d+)(Z|[+-]\d{2}:?\d{2}.*)$"
|
||||
)
|
||||
|
||||
|
||||
def _reduce_fractional_digits(timestamp_str: str) -> str:
|
||||
"""
|
||||
Given a RFC 3339 timestamp that includes too many digits for the fractional seconds part, reduces these to at most 6.
|
||||
"""
|
||||
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||
m = _FRACTIONAL_MATCHER.match(timestamp_str)
|
||||
if not m:
|
||||
raise BackendException(f"Cannot parse ISO 8601 timestamp {timestamp_str!r}")
|
||||
timestamp, fractional, timezone = m.groups()
|
||||
if len(fractional) > 7:
|
||||
# Python does not support anything smaller than microseconds
|
||||
# (Golang supports nanoseconds, Boulder often emits more fractional digits, which Python chokes on)
|
||||
fractional = fractional[:7]
|
||||
return f"{timestamp}{fractional}{timezone}"
|
||||
|
||||
|
||||
def _parse_acme_timestamp(timestamp_str: str, with_timezone: bool) -> datetime.datetime:
|
||||
"""
|
||||
Parses a RFC 3339 timestamp.
|
||||
"""
|
||||
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||
timestamp_str = _reduce_fractional_digits(timestamp_str)
|
||||
for 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)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
return (
|
||||
ensure_utc_timezone(result)
|
||||
if with_timezone
|
||||
else remove_timezone(result)
|
||||
)
|
||||
raise BackendException(f"Cannot parse ISO 8601 timestamp {timestamp_str!r}")
|
||||
|
||||
|
||||
class CryptoBackend(metaclass=abc.ABCMeta):
|
||||
def __init__(self, module: AnsibleModule, with_timezone: bool = False) -> None:
|
||||
self.module = module
|
||||
self._with_timezone = with_timezone
|
||||
|
||||
def get_now(self) -> datetime.datetime:
|
||||
return get_now_datetime(with_timezone=self._with_timezone)
|
||||
|
||||
def parse_acme_timestamp(self, timestamp_str: str) -> datetime.datetime:
|
||||
# RFC 3339 (https://www.rfc-editor.org/info/rfc3339)
|
||||
return _parse_acme_timestamp(timestamp_str, with_timezone=self._with_timezone)
|
||||
|
||||
def parse_module_parameter(self, value: str, name: str) -> datetime.datetime:
|
||||
try:
|
||||
result = get_relative_time_option(
|
||||
value, name, with_timezone=self._with_timezone
|
||||
)
|
||||
if result is None:
|
||||
raise BackendException(f"Invalid value for {name}: {value!r}")
|
||||
return result
|
||||
except OpenSSLObjectError as exc:
|
||||
raise BackendException(str(exc))
|
||||
|
||||
def interpolate_timestamp(
|
||||
self,
|
||||
timestamp_start: datetime.datetime,
|
||||
timestamp_end: datetime.datetime,
|
||||
percentage: float,
|
||||
) -> datetime.datetime:
|
||||
start = get_epoch_seconds(timestamp_start)
|
||||
end = get_epoch_seconds(timestamp_end)
|
||||
return from_epoch_seconds(
|
||||
start + percentage * (end - start), with_timezone=self._with_timezone
|
||||
)
|
||||
|
||||
def get_utc_datetime(self, *args, **kwargs) -> datetime.datetime:
|
||||
kwargs_ext: dict[str, t.Any] = dict(kwargs)
|
||||
if self._with_timezone and ("tzinfo" not in kwargs_ext and len(args) < 8):
|
||||
kwargs_ext["tzinfo"] = UTC
|
||||
result = datetime.datetime(*args, **kwargs_ext)
|
||||
if self._with_timezone and ("tzinfo" in kwargs or len(args) >= 8):
|
||||
result = ensure_utc_timezone(result)
|
||||
return result
|
||||
|
||||
@abc.abstractmethod
|
||||
def parse_key(
|
||||
self,
|
||||
key_file: str | os.PathLike | None = None,
|
||||
key_content: str | None = None,
|
||||
passphrase: str | None = None,
|
||||
) -> dict[str, t.Any]:
|
||||
"""
|
||||
Parses an RSA or Elliptic Curve key file in PEM format and returns key_data.
|
||||
Raises KeyParsingError in case of errors.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def sign(
|
||||
self, payload64: str, protected64: str, key_data: dict[str, t.Any]
|
||||
) -> dict[str, t.Any]:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_mac_key(self, alg: str, key: str) -> dict[str, t.Any]:
|
||||
"""Create a MAC key."""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_ordered_csr_identifiers(
|
||||
self,
|
||||
csr_filename: str | os.PathLike | None = None,
|
||||
csr_content: str | bytes | None = None,
|
||||
) -> list[tuple[str, str]]:
|
||||
"""
|
||||
Return a list of requested identifiers (CN and SANs) for the CSR.
|
||||
Each identifier is a pair (type, identifier), where type is either
|
||||
'dns' or 'ip'.
|
||||
|
||||
The list is deduplicated, and if a CNAME is present, it will be returned
|
||||
as the first element in the result.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_csr_identifiers(
|
||||
self,
|
||||
csr_filename: str | os.PathLike | None = None,
|
||||
csr_content: str | bytes | None = None,
|
||||
) -> set[tuple[str, str]]:
|
||||
"""
|
||||
Return a set of requested identifiers (CN and SANs) for the CSR.
|
||||
Each identifier is a pair (type, identifier), where type is either
|
||||
'dns' or 'ip'.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_cert_days(
|
||||
self,
|
||||
cert_filename: str | os.PathLike | None = None,
|
||||
cert_content: str | bytes | None = None,
|
||||
now: datetime.datetime | None = None,
|
||||
) -> int:
|
||||
"""
|
||||
Return the days the certificate in cert_filename remains valid and -1
|
||||
if the file was not found. If cert_filename contains more than one
|
||||
certificate, only the first one will be considered.
|
||||
|
||||
If now is not specified, datetime.datetime.now() is used.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_chain_matcher(self, criterium: Criterium) -> ChainMatcher:
|
||||
"""
|
||||
Given a Criterium object, creates a ChainMatcher object.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_cert_information(
|
||||
self,
|
||||
cert_filename: str | os.PathLike | None = None,
|
||||
cert_content: str | bytes | None = None,
|
||||
) -> CertificateInformation:
|
||||
"""
|
||||
Return some information on a X.509 certificate as a CertificateInformation object.
|
||||
"""
|
||||
Reference in New Issue
Block a user