-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[edpm_image_build] Copy certificates based on regexes
To avoid assuming the source certificate to copy into the diskimage-builder has a specific name just use the system CAs chain and allow the user to provide some regexes to select which CAs should be copied. The new module can read a PEM file (that can list many certs) and, optionally, filter the certs by OU or CN regexes.
- Loading branch information
1 parent
5bbd8ad
commit c6ada1f
Showing
7 changed files
with
228 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
#!/usr/bin/python | ||
|
||
# Copyright: (c) 2025, Pablo Rodriguez <pabrodri@redhat.com> | ||
# Apache License Version 2.0 (see LICENSE) | ||
|
||
from __future__ import absolute_import, division, print_function | ||
|
||
__metaclass__ = type | ||
|
||
|
||
DOCUMENTATION = r""" | ||
--- | ||
module: pem_read | ||
short_description: Reads a PEM file (that can list many certs) | ||
description: | ||
- Reads a PEM file (that can list many certs) | ||
- OU or CN regexes can be provided to filter out the list | ||
author: | ||
- Pablo Rodriguez (@pablintino) | ||
options: | ||
path: | ||
description: | ||
- The path to the certificate file to be read | ||
required: True | ||
type: str | ||
ou_filter: | ||
description: | ||
- Regex that, if given, used to filter the list of certs by OU. | ||
required: False | ||
type: str | ||
cn_filter: | ||
description: | ||
- Regex that, if given, used to filter the list of certs by CN. | ||
required: False | ||
type: str | ||
""" | ||
|
||
EXAMPLES = r""" | ||
- name: Get pem certs from crt file | ||
pem_read: | ||
path: "/etc/ssl/certs/ca-certificates.crt" | ||
register: _certs | ||
- name: Get pem certs from crt file by OU | ||
pem_read: | ||
path: "/etc/ssl/certs/ca-certificates.crt" | ||
ou_filter: "Red Hat" | ||
register: _certs2 | ||
""" | ||
|
||
RETURN = r""" | ||
certs: | ||
description: The list of PEM files | ||
type: list | ||
returned: returned request | ||
""" | ||
|
||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib | ||
|
||
import re | ||
import typing | ||
|
||
try: | ||
from cryptography import x509 | ||
from cryptography.x509.oid import NameOID | ||
from cryptography.hazmat.primitives import serialization | ||
|
||
python_cryptography_installed = True | ||
except ImportError: | ||
python_cryptography_installed = False | ||
|
||
|
||
def filter_certs(input_certs, ou_filter=None, cn_filter=None): | ||
certs = [] | ||
for cert in input_certs: | ||
ou = cert.subject.get_attributes_for_oid(NameOID.ORGANIZATIONAL_UNIT_NAME) | ||
cn = cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) | ||
if ( | ||
(ou and ou_filter and re.search(ou_filter, ou[0].value)) | ||
or (cn and cn_filter and re.search(cn_filter, cn[0].value)) | ||
or (not ou_filter and not cn_filter) | ||
): | ||
certs.append(cert) | ||
return certs | ||
|
||
|
||
def main(): | ||
module_args = { | ||
"path": {"type": "str", "required": True}, | ||
"ou_filter": {"type": "str", "required": False}, | ||
"cn_filter": {"type": "str", "required": False}, | ||
} | ||
|
||
result = {"changed": False, "certs": []} | ||
|
||
module = AnsibleModule(argument_spec=module_args, supports_check_mode=False) | ||
if not python_cryptography_installed: | ||
module.fail_json(msg=missing_required_lib("cryptography")) | ||
|
||
path = module.params["path"] | ||
ou_filter = module.params.get("ou_filter", None) | ||
cn_filter = module.params.get("cn_filter", None) | ||
|
||
try: | ||
with open(path, "rb") as f: | ||
certs = filter_certs( | ||
x509.load_pem_x509_certificates(f.read()), | ||
ou_filter=ou_filter, | ||
cn_filter=cn_filter, | ||
) | ||
result["certs"].extend( | ||
[ | ||
cert.public_bytes(encoding=serialization.Encoding.PEM).decode( | ||
"utf8" | ||
) | ||
for cert in certs | ||
] | ||
) | ||
|
||
except Exception as e: | ||
module.fail_json(msg=f"Error fetching reading a PEM file {str(e)}") | ||
|
||
module.exit_json(**result) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
# Create some selfsigned certs to test | ||
- name: Create a temporal directory for the certs | ||
ansible.builtin.tempfile: | ||
state: directory | ||
register: _tmp_dir | ||
|
||
- name: Create private key (RSA, 4096 bits) | ||
community.crypto.openssl_privatekey: | ||
path: "{{ [_tmp_dir.path, 'certificate.key'] | path_join }}" | ||
|
||
- name: Generate the CSRs | ||
community.crypto.openssl_csr_pipe: | ||
privatekey_path: "{{ [_tmp_dir.path, 'certificate.key'] | path_join }}" | ||
organizational_unit_name: "{{ item.ou }}" | ||
common_name: "{{ item.cn }}" | ||
register: _csrs | ||
loop: | ||
- cn: common-name-test-1 | ||
ou: "Some OU" | ||
- cn: common-name-test-2 | ||
ou: "Test OU 1" | ||
- cn: another-cert-cn | ||
ou: "Test OU 2" | ||
- cn: cn-1 | ||
ou: "Not following any pattern" | ||
|
||
- name: Create simple self-signed certificate | ||
community.crypto.x509_certificate: | ||
path: "{{ [_tmp_dir.path, (idx | string) + '.crt'] | path_join }}" | ||
privatekey_path: "{{ [_tmp_dir.path, 'certificate.key'] | path_join }}" | ||
provider: selfsigned | ||
csr_content: "{{ item.csr }}" | ||
loop: "{{ _csrs.results }}" | ||
loop_control: | ||
index_var: idx | ||
label: "{{ item.subject }}" | ||
register: _certs | ||
|
||
- name: Create simple self-signed certificate | ||
ansible.builtin.shell: | ||
chdir: "{{ _tmp_dir.path }}" | ||
cmd: >- | ||
cat *.crt > original-pem.crt | ||
- name: Test the module | ||
cifmw.general.pem_read: | ||
path: "{{ [_tmp_dir.path, 'original-pem.crt'] | path_join }}" | ||
ou_filter: "^Some\\s?OU" | ||
cn_filter: "cert-cn$" | ||
register: _result | ||
|
||
- name: Ensure we got the expected certificates | ||
vars: | ||
_cert_1_content: "{{ lookup('file', [_tmp_dir.path, '0.crt'] | path_join) }}" | ||
_cert_2_content: "{{ lookup('file', [_tmp_dir.path, '2.crt'] | path_join) }}" | ||
ansible.builtin.assert: | ||
that: | ||
- _result.certs | length == 2 | ||
- _result.certs[0] | trim == _cert_1_content | trim | ||
- _result.certs[1] | trim == _cert_2_content | trim |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters