From 6b1a3d6e685733c276342f376c3c9e34a978a7fc Mon Sep 17 00:00:00 2001 From: Felix Fontein Date: Sun, 18 Feb 2024 21:27:48 +0100 Subject: [PATCH] Add conversion filters for serial numbers (#713) * Refactoring. * Add parse_filter and to_filter plugins. * Mention filters when serial numbers are accepted or returned. --- README.md | 1 + plugins/doc_fragments/module_csr.py | 4 + plugins/filter/openssl_csr_info.py | 4 + plugins/filter/parse_serial.py | 66 ++++++++++++++++ plugins/filter/to_serial.py | 68 +++++++++++++++++ plugins/filter/x509_certificate_info.py | 9 ++- plugins/filter/x509_crl_info.py | 7 +- .../module_utils/acme/backend_cryptography.py | 52 +++---------- plugins/module_utils/crypto/math.py | 76 +++++++++++++++++++ plugins/module_utils/serial.py | 56 ++++++++++++++ plugins/modules/ecs_certificate.py | 7 +- plugins/modules/get_certificate.py | 23 ++++-- plugins/modules/openssh_cert.py | 6 ++ plugins/modules/openssl_csr_info.py | 4 + plugins/modules/x509_certificate_info.py | 9 ++- plugins/modules/x509_crl.py | 13 +++- plugins/modules/x509_crl_info.py | 7 +- .../targets/filter_parse_serial/aliases | 5 ++ .../filter_parse_serial/tasks/main.yml | 62 +++++++++++++++ .../targets/filter_to_serial/aliases | 5 ++ .../targets/filter_to_serial/tasks/main.yml | 35 +++++++++ tests/sanity/ignore-2.10.txt | 6 ++ tests/sanity/ignore-2.11.txt | 6 ++ tests/sanity/ignore-2.12.txt | 6 ++ tests/sanity/ignore-2.13.txt | 6 ++ tests/sanity/ignore-2.14.txt | 6 ++ tests/sanity/ignore-2.9.txt | 6 ++ 27 files changed, 500 insertions(+), 55 deletions(-) create mode 100644 plugins/filter/parse_serial.py create mode 100644 plugins/filter/to_serial.py create mode 100644 plugins/module_utils/serial.py create mode 100644 tests/integration/targets/filter_parse_serial/aliases create mode 100644 tests/integration/targets/filter_parse_serial/tasks/main.yml create mode 100644 tests/integration/targets/filter_to_serial/aliases create mode 100644 tests/integration/targets/filter_to_serial/tasks/main.yml diff --git a/README.md b/README.md index 19cd587f..64ffd4ea 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ If you use the Ansible package and do not update collections independently, use - crypto_info module - get_certificate module - luks_device module + - parse_serial and to_serial filters You can also find a list of all modules and plugins with documentation on the [Ansible docs site](https://docs.ansible.com/ansible/latest/collections/community/crypto/), or the [latest commit collection documentation](https://ansible-collections.github.io/community.crypto/branch/main/). diff --git a/plugins/doc_fragments/module_csr.py b/plugins/doc_fragments/module_csr.py index d08e05f4..e2a0bdd9 100644 --- a/plugins/doc_fragments/module_csr.py +++ b/plugins/doc_fragments/module_csr.py @@ -266,6 +266,8 @@ options: or for own CAs." - The C(AuthorityKeyIdentifier) extension will only be added if at least one of O(authority_key_identifier), O(authority_cert_issuer) and O(authority_cert_serial_number) is specified. + - This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings, + such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter). type: int crl_distribution_points: description: @@ -322,4 +324,6 @@ seealso: - module: community.crypto.openssl_privatekey_pipe - module: community.crypto.openssl_publickey - module: community.crypto.openssl_csr_info +- plugin: community.crypto.parse_serial + plugin_type: filter ''' diff --git a/plugins/filter/openssl_csr_info.py b/plugins/filter/openssl_csr_info.py index 0f3ba0d2..c66f44b3 100644 --- a/plugins/filter/openssl_csr_info.py +++ b/plugins/filter/openssl_csr_info.py @@ -26,6 +26,8 @@ extends_documentation_fragment: - community.crypto.name_encoding seealso: - module: community.crypto.openssl_csr_info + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = ''' @@ -268,6 +270,8 @@ _value: description: - The CSR's authority cert serial number. - Is V(none) if the C(AuthorityKeyIdentifier) extension is not present. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 12345 diff --git a/plugins/filter/parse_serial.py b/plugins/filter/parse_serial.py new file mode 100644 index 00000000..f78dc45d --- /dev/null +++ b/plugins/filter/parse_serial.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ +name: parse_serial +short_description: Convert a serial number as a colon-separated list of hex numbers to an integer +author: Felix Fontein (@felixfontein) +version_added: 2.18.0 +description: + - "Parses a colon-separated list of hex numbers of the form C(00:11:22:33) and returns the corresponding integer." +options: + _input: + description: + - A serial number represented as a colon-separated list of hex numbers between 0 and 255. + - These numbers are interpreted as the byte presentation of an unsigned integer in network byte order. + That is, C(01:00) is interpreted as the integer 256. + type: string + required: true +seealso: + - plugin: community.crypto.to_serial + plugin_type: filter +""" + +EXAMPLES = """ +- name: Parse serial number + ansible.builtin.debug: + msg: "{{ '11:22:33' | community.crypto.parse_serial }}" +""" + +RETURN = """ + _value: + description: + - The serial number as an integer. + type: int +""" + +from ansible.errors import AnsibleFilterError +from ansible.module_utils.common.text.converters import to_native +from ansible.module_utils.six import string_types + +from ansible_collections.community.crypto.plugins.module_utils.serial import parse_serial + + +def parse_serial_filter(input): + if not isinstance(input, string_types): + raise AnsibleFilterError( + 'The input for the community.crypto.parse_serial filter must be a string; got {type} instead'.format(type=type(input)) + ) + try: + return parse_serial(to_native(input)) + except ValueError as exc: + raise AnsibleFilterError(to_native(exc)) + + +class FilterModule(object): + '''Ansible jinja2 filters''' + + def filters(self): + return { + 'parse_serial': parse_serial_filter, + } diff --git a/plugins/filter/to_serial.py b/plugins/filter/to_serial.py new file mode 100644 index 00000000..78f337fd --- /dev/null +++ b/plugins/filter/to_serial.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2024, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ +name: to_serial +short_description: Convert an integer to a colon-separated list of hex numbers +author: Felix Fontein (@felixfontein) +version_added: 2.18.0 +description: + - "Converts an integer to a colon-separated list of hex numbers of the form C(00:11:22:33)." +options: + _input: + description: + - The non-negative integer to convert. + type: int + required: true +seealso: + - plugin: community.crypto.to_serial + plugin_type: filter +""" + +EXAMPLES = """ +- name: Convert integer to serial number + ansible.builtin.debug: + msg: "{{ 1234567 | community.crypto.to_serial }}" +""" + +RETURN = """ + _value: + description: + - A colon-separated list of hexadecimal numbers. + - Letters are upper-case, and all numbers have exactly two digits. + - The string is never empty. The representation of C(0) is C("00"). + type: string +""" + +from ansible.errors import AnsibleFilterError +from ansible.module_utils.common.text.converters import to_native +from ansible.module_utils.six import integer_types + +from ansible_collections.community.crypto.plugins.module_utils.serial import to_serial + + +def to_serial_filter(input): + if not isinstance(input, integer_types): + raise AnsibleFilterError( + 'The input for the community.crypto.to_serial filter must be an integer; got {type} instead'.format(type=type(input)) + ) + if input < 0: + raise AnsibleFilterError('The input for the community.crypto.to_serial filter must not be negative') + try: + return to_serial(input) + except ValueError as exc: + raise AnsibleFilterError(to_native(exc)) + + +class FilterModule(object): + '''Ansible jinja2 filters''' + + def filters(self): + return { + 'to_serial': to_serial_filter, + } diff --git a/plugins/filter/x509_certificate_info.py b/plugins/filter/x509_certificate_info.py index 4193de28..7a18c2c0 100644 --- a/plugins/filter/x509_certificate_info.py +++ b/plugins/filter/x509_certificate_info.py @@ -26,6 +26,8 @@ extends_documentation_fragment: - community.crypto.name_encoding seealso: - module: community.crypto.x509_certificate_info + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = ''' @@ -253,7 +255,10 @@ _value: type: str sample: sha256WithRSAEncryption serial_number: - description: The certificate's serial number. + description: + - The certificate's serial number. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 1234 @@ -291,6 +296,8 @@ _value: description: - The certificate's authority cert serial number. - Is V(none) if the C(AuthorityKeyIdentifier) extension is not present. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 12345 diff --git a/plugins/filter/x509_crl_info.py b/plugins/filter/x509_crl_info.py index d0644621..db1dc536 100644 --- a/plugins/filter/x509_crl_info.py +++ b/plugins/filter/x509_crl_info.py @@ -35,6 +35,8 @@ extends_documentation_fragment: - community.crypto.name_encoding seealso: - module: community.crypto.x509_crl_info + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = ''' @@ -100,7 +102,10 @@ _value: elements: dict contains: serial_number: - description: Serial number of the certificate. + description: + - Serial number of the certificate. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). type: int sample: 1234 revocation_date: diff --git a/plugins/module_utils/acme/backend_cryptography.py b/plugins/module_utils/acme/backend_cryptography.py index 207f743f..2e388980 100644 --- a/plugins/module_utils/acme/backend_cryptography.py +++ b/plugins/module_utils/acme/backend_cryptography.py @@ -13,7 +13,6 @@ import base64 import binascii import datetime import os -import sys import traceback from ansible.module_utils.common.text.converters import to_bytes, to_native, to_text @@ -37,6 +36,11 @@ from ansible_collections.community.crypto.plugins.module_utils.acme.io import re from ansible_collections.community.crypto.plugins.module_utils.acme.utils import nopad_b64 +from ansible_collections.community.crypto.plugins.module_utils.crypto.math import ( + convert_int_to_bytes, + convert_int_to_hex, +) + from ansible_collections.community.crypto.plugins.module_utils.crypto.support import ( parse_name_field, ) @@ -78,40 +82,6 @@ else: CRYPTOGRAPHY_ERROR = traceback.format_exc() -if sys.version_info[0] >= 3: - # Python 3 (and newer) - def _count_bytes(n): - return (n.bit_length() + 7) // 8 if n > 0 else 0 - - def _convert_int_to_bytes(count, no): - return no.to_bytes(count, byteorder='big') - - def _pad_hex(n, digits): - res = hex(n)[2:] - if len(res) < digits: - res = '0' * (digits - len(res)) + res - return res -else: - # Python 2 - def _count_bytes(n): - if n <= 0: - return 0 - h = '%x' % n - return (len(h) + 1) // 2 - - def _convert_int_to_bytes(count, n): - h = '%x' % n - if len(h) > 2 * count: - raise Exception('Number {1} needs more than {0} bytes!'.format(count, n)) - return ('0' * (2 * count - len(h)) + h).decode('hex') - - def _pad_hex(n, digits): - h = '%x' % n - if len(h) < digits: - h = '0' * (digits - len(h)) + h - return h - - class CryptographyChainMatcher(ChainMatcher): @staticmethod def _parse_key_identifier(key_identifier, name, criterium_idx, module): @@ -223,8 +193,8 @@ class CryptographyBackend(CryptoBackend): 'alg': 'RS256', 'jwk': { "kty": "RSA", - "e": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.e), pk.e)), - "n": nopad_b64(_convert_int_to_bytes(_count_bytes(pk.n), pk.n)), + "e": nopad_b64(convert_int_to_bytes(pk.e)), + "n": nopad_b64(convert_int_to_bytes(pk.n)), }, 'hash': 'sha256', } @@ -260,8 +230,8 @@ class CryptographyBackend(CryptoBackend): 'jwk': { "kty": "EC", "crv": curve, - "x": nopad_b64(_convert_int_to_bytes(num_bytes, pk.x)), - "y": nopad_b64(_convert_int_to_bytes(num_bytes, pk.y)), + "x": nopad_b64(convert_int_to_bytes(pk.x, count=num_bytes)), + "y": nopad_b64(convert_int_to_bytes(pk.y, count=num_bytes)), }, 'hash': hashalg, 'point_size': point_size, @@ -288,8 +258,8 @@ class CryptographyBackend(CryptoBackend): hashalg = cryptography.hazmat.primitives.hashes.SHA512 ecdsa = cryptography.hazmat.primitives.asymmetric.ec.ECDSA(hashalg()) r, s = cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature(key_data['key_obj'].sign(sign_payload, ecdsa)) - rr = _pad_hex(r, 2 * key_data['point_size']) - ss = _pad_hex(s, 2 * key_data['point_size']) + rr = convert_int_to_hex(r, 2 * key_data['point_size']) + ss = convert_int_to_hex(s, 2 * key_data['point_size']) signature = binascii.unhexlify(rr) + binascii.unhexlify(ss) return { diff --git a/plugins/module_utils/crypto/math.py b/plugins/module_utils/crypto/math.py index 1cfe38b9..f56f22d3 100644 --- a/plugins/module_utils/crypto/math.py +++ b/plugins/module_utils/crypto/math.py @@ -54,17 +54,93 @@ def quick_is_not_prime(n): python_version = (sys.version_info[0], sys.version_info[1]) if python_version >= (2, 7) or python_version >= (3, 1): # Ansible still supports Python 2.6 on remote nodes + + def count_bytes(no): + """ + Given an integer, compute the number of bytes necessary to store its absolute value. + """ + no = abs(no) + if no == 0: + return 0 + return (no.bit_length() + 7) // 8 + def count_bits(no): + """ + Given an integer, compute the number of bits necessary to store its absolute value. + """ no = abs(no) if no == 0: return 0 return no.bit_length() else: # Slow, but works + def count_bytes(no): + """ + Given an integer, compute the number of bytes necessary to store its absolute value. + """ + no = abs(no) + count = 0 + while no > 0: + no >>= 8 + count += 1 + return count + def count_bits(no): + """ + Given an integer, compute the number of bits necessary to store its absolute value. + """ no = abs(no) count = 0 while no > 0: no >>= 1 count += 1 return count + +if sys.version_info[0] >= 3: + # Python 3 (and newer) + def _convert_int_to_bytes(count, no): + return no.to_bytes(count, byteorder='big') + + def _to_hex(no): + return hex(no)[2:] +else: + # Python 2 + def _convert_int_to_bytes(count, n): + h = '%x' % n + if len(h) > 2 * count: + raise Exception('Number {1} needs more than {0} bytes!'.format(count, n)) + return ('0' * (2 * count - len(h)) + h).decode('hex') + + def _to_hex(no): + return '%x' % no + + +def convert_int_to_bytes(no, count=None): + """ + Convert the absolute value of an integer to a byte string in network byte order. + + If ``count`` is provided, it must be sufficiently large so that the integer's + absolute value can be represented with these number of bytes. The resulting byte + string will have length exactly ``count``. + + The value zero will be converted to an empty byte string if ``count`` is provided. + """ + no = abs(no) + if count is None: + count = count_bytes(no) + return _convert_int_to_bytes(count, no) + + +def convert_int_to_hex(no, digits=None): + """ + Convert the absolute value of an integer to a string of hexadecimal digits. + + If ``digits`` is provided, the string will be padded on the left with ``0``s so + that the returned value has length ``digits``. If ``digits`` is not sufficient, + the string will be longer. + """ + no = abs(no) + value = _to_hex(no) + if digits is not None and len(value) < digits: + value = '0' * (digits - len(value)) + value + return value diff --git a/plugins/module_utils/serial.py b/plugins/module_utils/serial.py new file mode 100644 index 00000000..dac554e3 --- /dev/null +++ b/plugins/module_utils/serial.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2024, Felix Fontein +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible.module_utils.common.text.converters import to_native + +from ansible_collections.community.crypto.plugins.module_utils.crypto.math import ( + convert_int_to_hex, +) + + +def th(number): + abs_number = abs(number) + mod_10 = abs_number % 10 + mod_100 = abs_number % 100 + if mod_100 not in (11, 12, 13): + if mod_10 == 1: + return 'st' + if mod_10 == 2: + return 'nd' + if mod_10 == 3: + return 'rd' + return 'th' + + +def parse_serial(value): + """ + Given a colon-separated string of hexadecimal byte values, converts it to an integer. + """ + value = to_native(value) + result = 0 + for i, part in enumerate(value.split(':')): + try: + part_value = int(part, 16) + if part_value < 0 or part_value > 255: + raise ValueError('the value is not in range [0, 255]') + except ValueError as exc: + raise ValueError("The {idx}{th} part {part!r} is not a hexadecimal number in range [0, 255]: {exc}".format( + idx=i + 1, th=th(i + 1), part=part, exc=exc)) + result = (result << 8) | part_value + return result + + +def to_serial(value): + """ + Given an integer, converts its absolute value to a colon-separated string of hexadecimal byte values. + """ + value = convert_int_to_hex(value).upper() + if len(value) % 2 != 0: + value = '0' + value + return ':'.join(value[i:i + 2] for i in range(0, len(value), 2)) diff --git a/plugins/modules/ecs_certificate.py b/plugins/modules/ecs_certificate.py index cb6bdca1..2c1238d4 100644 --- a/plugins/modules/ecs_certificate.py +++ b/plugins/modules/ecs_certificate.py @@ -350,6 +350,8 @@ seealso: description: Can be used to create private keys (both for certificates and accounts). - module: community.crypto.openssl_csr description: Can be used to create a Certificate Signing Request (CSR). + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = r''' @@ -490,7 +492,10 @@ tracking_id: type: int sample: 380079 serial_number: - description: The serial number of the issued certificate. + description: + - The serial number of the issued certificate. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 1235262234164342 diff --git a/plugins/modules/get_certificate.py b/plugins/modules/get_certificate.py index dfdc84c6..0f8abc90 100644 --- a/plugins/modules/get_certificate.py +++ b/plugins/modules/get_certificate.py @@ -106,6 +106,10 @@ notes: requirements: - "python >= 2.7 when using O(proxy_host)" - "cryptography >= 1.6" + +seealso: + - plugin: community.crypto.to_serial + plugin_type: filter ''' RETURN = ''' @@ -147,31 +151,34 @@ extensions: type: str description: The extension's name. issuer: - description: Information about the issuer of the cert + description: Information about the issuer of the cert. returned: success type: dict not_after: - description: Expiration date of the cert + description: Expiration date of the cert. returned: success type: str not_before: - description: Issue date of the cert + description: Issue date of the cert. returned: success type: str serial_number: - description: The serial number of the cert + description: + - The serial number of the cert. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success - type: str + type: int signature_algorithm: - description: The algorithm used to sign the cert + description: The algorithm used to sign the cert. returned: success type: str subject: - description: Information about the subject of the cert (OU, CN, etc) + description: Information about the subject of the cert (C(OU), C(CN), etc). returned: success type: dict version: - description: The version number of the certificate + description: The version number of the certificate. returned: success type: str ''' diff --git a/plugins/modules/openssh_cert.py b/plugins/modules/openssh_cert.py index b968b14c..8e864cd7 100644 --- a/plugins/modules/openssh_cert.py +++ b/plugins/modules/openssh_cert.py @@ -190,7 +190,13 @@ options: The certificate serial number may be used in a KeyRevocationList. The serial number may be omitted for checks, but must be specified again for a new certificate. Note: The default value set by ssh-keygen is 0." + - This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings, + such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter). type: int + +seealso: + - plugin: community.crypto.parse_serial + plugin_type: filter ''' EXAMPLES = ''' diff --git a/plugins/modules/openssl_csr_info.py b/plugins/modules/openssl_csr_info.py index 6e2dd5c9..fc7eaf18 100644 --- a/plugins/modules/openssl_csr_info.py +++ b/plugins/modules/openssl_csr_info.py @@ -55,6 +55,8 @@ seealso: - plugin: community.crypto.openssl_csr_info plugin_type: filter description: A filter variant of this module. + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = r''' @@ -301,6 +303,8 @@ authority_cert_serial_number: description: - The CSR's authority cert serial number. - Is V(none) if the C(AuthorityKeyIdentifier) extension is not present. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 12345 diff --git a/plugins/modules/x509_certificate_info.py b/plugins/modules/x509_certificate_info.py index 83fd993c..d89f610c 100644 --- a/plugins/modules/x509_certificate_info.py +++ b/plugins/modules/x509_certificate_info.py @@ -77,6 +77,8 @@ seealso: - plugin: community.crypto.x509_certificate_info plugin_type: filter description: A filter variant of this module. + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = r''' @@ -330,7 +332,10 @@ signature_algorithm: type: str sample: sha256WithRSAEncryption serial_number: - description: The certificate's serial number. + description: + - The certificate's serial number. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 1234 @@ -374,6 +379,8 @@ authority_cert_serial_number: description: - The certificate's authority cert serial number. - Is V(none) if the C(AuthorityKeyIdentifier) extension is not present. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). returned: success type: int sample: 12345 diff --git a/plugins/modules/x509_crl.py b/plugins/modules/x509_crl.py index f13b7273..4fbdf909 100644 --- a/plugins/modules/x509_crl.py +++ b/plugins/modules/x509_crl.py @@ -193,6 +193,8 @@ options: - Mutually exclusive with O(revoked_certificates[].path) and O(revoked_certificates[].content). One of these three options must be specified. + - This option accepts an B(integer). If you want to provide serial numbers as colon-separated hex strings, + such as C(11:22:33), you need to convert them to an integer with P(community.crypto.parse_serial#filter). type: int revocation_date: description: @@ -271,6 +273,12 @@ options: notes: - All ASN.1 TIME values should be specified following the YYYYMMDDHHMMSSZ pattern. - Date specified should be UTC. Minutes and seconds are mandatory. + +seealso: + - plugin: community.crypto.parse_serial + plugin_type: filter + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = r''' @@ -356,7 +364,10 @@ revoked_certificates: elements: dict contains: serial_number: - description: Serial number of the certificate. + description: + - Serial number of the certificate. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). type: int sample: 1234 revocation_date: diff --git a/plugins/modules/x509_crl_info.py b/plugins/modules/x509_crl_info.py index 1d864a17..2cf25f53 100644 --- a/plugins/modules/x509_crl_info.py +++ b/plugins/modules/x509_crl_info.py @@ -53,6 +53,8 @@ seealso: - plugin: community.crypto.x509_crl_info plugin_type: filter description: A filter variant of this module. + - plugin: community.crypto.to_serial + plugin_type: filter ''' EXAMPLES = r''' @@ -118,7 +120,10 @@ revoked_certificates: elements: dict contains: serial_number: - description: Serial number of the certificate. + description: + - Serial number of the certificate. + - This return value is an B(integer). If you need the serial numbers as a colon-separated hex string, + such as C(11:22:33), you need to convert it to that form with P(community.crypto.to_serial#filter). type: int sample: 1234 revocation_date: diff --git a/tests/integration/targets/filter_parse_serial/aliases b/tests/integration/targets/filter_parse_serial/aliases new file mode 100644 index 00000000..12d1d661 --- /dev/null +++ b/tests/integration/targets/filter_parse_serial/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/2 diff --git a/tests/integration/targets/filter_parse_serial/tasks/main.yml b/tests/integration/targets/filter_parse_serial/tasks/main.yml new file mode 100644 index 00000000..67175ac0 --- /dev/null +++ b/tests/integration/targets/filter_parse_serial/tasks/main.yml @@ -0,0 +1,62 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Test parse_serial filter + assert: + that: + - >- + '0' | community.crypto.parse_serial == 0 + - >- + '00' | community.crypto.parse_serial == 0 + - >- + '000' | community.crypto.parse_serial == 0 + - >- + 'ff' | community.crypto.parse_serial == 255 + - >- + '0ff' | community.crypto.parse_serial == 255 + - >- + '1:0' | community.crypto.parse_serial == 256 + - >- + '1:2:3' | community.crypto.parse_serial == 66051 + +- name: "Test error 1: empty string" + debug: + msg: >- + {{ '' | community.crypto.parse_serial }} + ignore_errors: true + register: error_1 + +- name: "Test error 2: invalid type" + debug: + msg: >- + {{ [] | community.crypto.parse_serial }} + ignore_errors: true + register: error_2 + +- name: "Test error 3: invalid values (range)" + debug: + msg: >- + {{ '100' | community.crypto.parse_serial }} + ignore_errors: true + register: error_3 + +- name: "Test error 4: invalid values (digits)" + debug: + msg: >- + {{ 'abcdefg' | community.crypto.parse_serial }} + ignore_errors: true + register: error_4 + +- name: Validate errors + assert: + that: + - >- + error_1 is failed and "The 1st part '' is not a hexadecimal number in range [0, 255]: invalid literal" in error_1.msg + - >- + error_2 is failed and "The input for the community.crypto.parse_serial filter must be a string; got " in error_2.msg + - >- + error_3 is failed and "The 1st part '100' is not a hexadecimal number in range [0, 255]: the value is not in range [0, 255]" in error_3.msg + - >- + error_4 is failed and "The 1st part 'abcdefg' is not a hexadecimal number in range [0, 255]: invalid literal" in error_4.msg diff --git a/tests/integration/targets/filter_to_serial/aliases b/tests/integration/targets/filter_to_serial/aliases new file mode 100644 index 00000000..12d1d661 --- /dev/null +++ b/tests/integration/targets/filter_to_serial/aliases @@ -0,0 +1,5 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +azp/posix/2 diff --git a/tests/integration/targets/filter_to_serial/tasks/main.yml b/tests/integration/targets/filter_to_serial/tasks/main.yml new file mode 100644 index 00000000..1b1f4385 --- /dev/null +++ b/tests/integration/targets/filter_to_serial/tasks/main.yml @@ -0,0 +1,35 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Test to_serial filter + assert: + that: + - 0 | community.crypto.to_serial == '00' + - 1 | community.crypto.to_serial == '01' + - 255 | community.crypto.to_serial == 'FF' + - 256 | community.crypto.to_serial == '01:00' + - 65536 | community.crypto.to_serial == '01:00:00' + +- name: "Test error 1: negative number" + debug: + msg: >- + {{ (-1) | community.crypto.to_serial }} + ignore_errors: true + register: error_1 + +- name: "Test error 2: invalid type" + debug: + msg: >- + {{ [] | community.crypto.to_serial }} + ignore_errors: true + register: error_2 + +- name: Validate error + assert: + that: + - >- + error_1 is failed and "The input for the community.crypto.to_serial filter must not be negative" in error_1.msg + - >- + error_2 is failed and "The input for the community.crypto.to_serial filter must be an integer; got " in error_2.msg diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index b49b5bc8..81d34f18 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -7,10 +7,16 @@ docs/docsite/rst/guide_selfsigned.rst rstcheck plugins/modules/acme_account_info.py validate-modules:return-syntax-error plugins/modules/acme_challenge_cert_helper.py validate-modules:return-syntax-error +plugins/modules/ecs_certificate.py validate-modules:invalid-documentation +plugins/modules/get_certificate.py validate-modules:invalid-documentation +plugins/modules/openssh_cert.py validate-modules:invalid-documentation +plugins/modules/openssl_csr.py validate-modules:invalid-documentation plugins/modules/openssl_csr_info.py validate-modules:invalid-documentation +plugins/modules/openssl_csr_pipe.py validate-modules:invalid-documentation plugins/modules/openssl_privatekey_info.py validate-modules:invalid-documentation plugins/modules/openssl_publickey_info.py validate-modules:invalid-documentation plugins/modules/x509_certificate_info.py validate-modules:invalid-documentation +plugins/modules/x509_crl.py validate-modules:invalid-documentation plugins/modules/x509_crl.py validate-modules:return-syntax-error plugins/modules/x509_crl_info.py validate-modules:invalid-documentation plugins/modules/x509_crl_info.py validate-modules:return-syntax-error diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 6ada4475..2677551d 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -6,10 +6,16 @@ .azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate plugins/modules/acme_account_info.py validate-modules:return-syntax-error plugins/modules/acme_challenge_cert_helper.py validate-modules:return-syntax-error +plugins/modules/ecs_certificate.py validate-modules:invalid-documentation +plugins/modules/get_certificate.py validate-modules:invalid-documentation +plugins/modules/openssh_cert.py validate-modules:invalid-documentation +plugins/modules/openssl_csr.py validate-modules:invalid-documentation plugins/modules/openssl_csr_info.py validate-modules:invalid-documentation +plugins/modules/openssl_csr_pipe.py validate-modules:invalid-documentation plugins/modules/openssl_privatekey_info.py validate-modules:invalid-documentation plugins/modules/openssl_publickey_info.py validate-modules:invalid-documentation plugins/modules/x509_certificate_info.py validate-modules:invalid-documentation +plugins/modules/x509_crl.py validate-modules:invalid-documentation plugins/modules/x509_crl.py validate-modules:return-syntax-error plugins/modules/x509_crl_info.py validate-modules:invalid-documentation plugins/modules/x509_crl_info.py validate-modules:return-syntax-error diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 88cece1f..26e5b686 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -1,10 +1,16 @@ .azure-pipelines/scripts/publish-codecov.py replace-urlopen plugins/modules/acme_account_info.py validate-modules:return-syntax-error plugins/modules/acme_challenge_cert_helper.py validate-modules:return-syntax-error +plugins/modules/ecs_certificate.py validate-modules:invalid-documentation +plugins/modules/get_certificate.py validate-modules:invalid-documentation +plugins/modules/openssh_cert.py validate-modules:invalid-documentation +plugins/modules/openssl_csr.py validate-modules:invalid-documentation plugins/modules/openssl_csr_info.py validate-modules:invalid-documentation +plugins/modules/openssl_csr_pipe.py validate-modules:invalid-documentation plugins/modules/openssl_privatekey_info.py validate-modules:invalid-documentation plugins/modules/openssl_publickey_info.py validate-modules:invalid-documentation plugins/modules/x509_certificate_info.py validate-modules:invalid-documentation +plugins/modules/x509_crl.py validate-modules:invalid-documentation plugins/modules/x509_crl.py validate-modules:return-syntax-error plugins/modules/x509_crl_info.py validate-modules:invalid-documentation plugins/modules/x509_crl_info.py validate-modules:return-syntax-error diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index aefcd307..74ca9471 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -1,9 +1,15 @@ .azure-pipelines/scripts/publish-codecov.py replace-urlopen plugins/lookup/gpg_fingerprint.py validate-modules:invalid-documentation +plugins/modules/ecs_certificate.py validate-modules:invalid-documentation +plugins/modules/get_certificate.py validate-modules:invalid-documentation +plugins/modules/openssh_cert.py validate-modules:invalid-documentation +plugins/modules/openssl_csr.py validate-modules:invalid-documentation plugins/modules/openssl_csr_info.py validate-modules:invalid-documentation +plugins/modules/openssl_csr_pipe.py validate-modules:invalid-documentation plugins/modules/openssl_privatekey_info.py validate-modules:invalid-documentation plugins/modules/openssl_publickey_info.py validate-modules:invalid-documentation plugins/modules/x509_certificate_info.py validate-modules:invalid-documentation +plugins/modules/x509_crl.py validate-modules:invalid-documentation plugins/modules/x509_crl_info.py validate-modules:invalid-documentation tests/ee/roles/smoke/library/smoke_ipaddress.py shebang tests/ee/roles/smoke/library/smoke_pyyaml.py shebang diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index aefcd307..74ca9471 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -1,9 +1,15 @@ .azure-pipelines/scripts/publish-codecov.py replace-urlopen plugins/lookup/gpg_fingerprint.py validate-modules:invalid-documentation +plugins/modules/ecs_certificate.py validate-modules:invalid-documentation +plugins/modules/get_certificate.py validate-modules:invalid-documentation +plugins/modules/openssh_cert.py validate-modules:invalid-documentation +plugins/modules/openssl_csr.py validate-modules:invalid-documentation plugins/modules/openssl_csr_info.py validate-modules:invalid-documentation +plugins/modules/openssl_csr_pipe.py validate-modules:invalid-documentation plugins/modules/openssl_privatekey_info.py validate-modules:invalid-documentation plugins/modules/openssl_publickey_info.py validate-modules:invalid-documentation plugins/modules/x509_certificate_info.py validate-modules:invalid-documentation +plugins/modules/x509_crl.py validate-modules:invalid-documentation plugins/modules/x509_crl_info.py validate-modules:invalid-documentation tests/ee/roles/smoke/library/smoke_ipaddress.py shebang tests/ee/roles/smoke/library/smoke_pyyaml.py shebang diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index 7f871d53..e20c4e5f 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -6,10 +6,16 @@ .azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate docs/docsite/rst/guide_selfsigned.rst rstcheck plugins/modules/acme_challenge_cert_helper.py validate-modules:return-syntax-error +plugins/modules/ecs_certificate.py validate-modules:invalid-documentation +plugins/modules/get_certificate.py validate-modules:invalid-documentation +plugins/modules/openssh_cert.py validate-modules:invalid-documentation +plugins/modules/openssl_csr.py validate-modules:invalid-documentation plugins/modules/openssl_csr_info.py validate-modules:invalid-documentation +plugins/modules/openssl_csr_pipe.py validate-modules:invalid-documentation plugins/modules/openssl_privatekey_info.py validate-modules:invalid-documentation plugins/modules/openssl_publickey_info.py validate-modules:invalid-documentation plugins/modules/x509_certificate_info.py validate-modules:invalid-documentation +plugins/modules/x509_crl.py validate-modules:invalid-documentation plugins/modules/x509_crl.py validate-modules:return-syntax-error plugins/modules/x509_crl_info.py validate-modules:invalid-documentation plugins/modules/x509_crl_info.py validate-modules:return-syntax-error