Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x509_certificate_convert: add validation option #830

Merged
merged 1 commit into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelogs/fragments/830-x509-convert-verify.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
minor_changes:
- "x509_certificate_convert - add new option ``verify_cert_parsable`` which allows to check whether the certificate can actually be parsed
(https://github.com/ansible-collections/community.crypto/issues/809, https://github.com/ansible-collections/community.crypto/pull/830)."
41 changes: 40 additions & 1 deletion plugins/modules/x509_certificate_convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
description:
- If the input is a PEM file, ensure that it contains a single PEM object, that the header and footer match, and are
of type C(CERTIFICATE) or C(X509 CERTIFICATE).
- See also the O(verify_cert_parsable) option, which checks whether the certificate is parsable.
type: bool
default: false
dest_path:
Expand All @@ -72,12 +73,21 @@
with a new one by accident.
type: bool
default: false
verify_cert_parsable:
description:
- If set to V(true), ensures that the certificate can be parsed.
- To ensure that a PEM file does not contain multiple certificates, use the O(strict) option.
type: bool
default: false
version_added: 2.23.0
seealso:
- plugin: ansible.builtin.b64encode
plugin_type: filter
- module: community.crypto.x509_certificate
- module: community.crypto.x509_certificate_pipe
- module: community.crypto.x509_certificate_info
requirements:
- cryptography >= 1.6 if O(verify_cert_parsable=true)
"""

EXAMPLES = r"""
Expand All @@ -98,8 +108,9 @@

import base64
import os
import traceback

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils.common.text.converters import to_native, to_bytes, to_text

from ansible_collections.community.crypto.plugins.module_utils.io import (
Expand All @@ -124,6 +135,19 @@
OpenSSLObject,
)

MINIMAL_CRYPTOGRAPHY_VERSION = '1.6'

CRYPTOGRAPHY_IMP_ERR = None
try:
import cryptography # noqa: F401, pylint: disable=unused-import
from cryptography.x509 import load_der_x509_certificate
from cryptography.hazmat.backends import default_backend
except ImportError:
CRYPTOGRAPHY_IMP_ERR = traceback.format_exc()
CRYPTOGRAPHY_FOUND = False
else:
CRYPTOGRAPHY_FOUND = True


def parse_certificate(input, strict=False):
input_format = 'pem' if identify_pem_format(input) else 'der'
Expand Down Expand Up @@ -175,6 +199,9 @@ def __init__(self, module):
except Exception as exc:
module.fail_json(msg='Error while parsing PEM: {exc}'.format(exc=exc))

if module.params['verify_cert_parsable']:
self.verify_cert_parsable(module)

self.backup = module.params['backup']
self.backup_file = None

Expand All @@ -190,6 +217,17 @@ def __init__(self, module):
except Exception:
pass

def verify_cert_parsable(self, module):
if not CRYPTOGRAPHY_FOUND:
module.fail_json(
msg=missing_required_lib('cryptography >= {0}'.format(MINIMAL_CRYPTOGRAPHY_VERSION)),
exception=CRYPTOGRAPHY_IMP_ERR,
)
try:
load_der_x509_certificate(self.input, default_backend())
except Exception as exc:
module.fail_json(msg='Error while parsing certificate: {exc}'.format(exc=exc))

def needs_conversion(self):
if self.dest_content is None or self.dest_content_format is None:
return True
Expand Down Expand Up @@ -247,6 +285,7 @@ def main():
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),
)
module = AnsibleModule(
argument_spec,
Expand Down
30 changes: 30 additions & 0 deletions tests/integration/targets/x509_certificate_convert/tasks/impl.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,33 @@
- result_8 is not changed
- result_9 is not changed
- result_10 is not changed

- name: Create empty file
ansible.builtin.copy:
dest: '{{ remote_tmp_dir }}/empty'
content: ''

- name: Convert empty file to PEM (w/o verify)
community.crypto.x509_certificate_convert:
src_path: '{{ remote_tmp_dir }}/empty'
dest_path: '{{ remote_tmp_dir }}/empty.pem'
format: pem
verify_cert_parsable: false
register: result_1

- name: Convert empty file to PEM (w/ verify)
community.crypto.x509_certificate_convert:
src_path: '{{ remote_tmp_dir }}/empty'
dest_path: '{{ remote_tmp_dir }}/empty.pem'
format: pem
verify_cert_parsable: true
register: result_2
ignore_errors: true

- name: Check conditions
assert:
that:
- result_1 is changed
- result_2 is failed
- >-
result_2.msg.startswith('Error while parsing certificate: ')
Loading