openssl_pkcs12: allow to specify certificate bundles in other_certificates (#166)
* Rename identify.py to pem.py. * Move split PEM list code to pem.py crypto module_utils. * Extend and use global certificate splitting code in acme_certificate. * openssl_pkcs12: allow to load multiple certificates from files mentioned in other_certificates. * Add changelog and module_utils redirect. * Remove old check. * Fix typo. * Apply suggestions from code review Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru> * Add example. Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>pull/178/head
parent
d8ccebce60
commit
c7ef362d7a
|
@ -0,0 +1,3 @@
|
||||||
|
minor_changes:
|
||||||
|
- "openssl_pkcs12 - allow to specify certificate bundles in ``other_certificates`` by using new option ``other_certificates_parse_all`` (https://github.com/ansible-collections/community.crypto/issues/149, https://github.com/ansible-collections/community.crypto/pull/166)."
|
||||||
|
- "The ``crypto/identify.py`` module_utils has been renamed to ``crypto/pem.py`` (https://github.com/ansible-collections/community.crypto/pull/166)."
|
|
@ -23,3 +23,9 @@ plugin_routing:
|
||||||
deprecation:
|
deprecation:
|
||||||
removal_version: 2.0.0
|
removal_version: 2.0.0
|
||||||
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'
|
warning_text: The 'community.crypto.openssl_certificate_info' module has been renamed to 'community.crypto.x509_certificate_info'
|
||||||
|
module_utils:
|
||||||
|
crypto.identify:
|
||||||
|
redirect: community.crypto.crypto.pem
|
||||||
|
deprecation:
|
||||||
|
removal_version: 2.0.0
|
||||||
|
warning_text: The 'crypto/identify.py' module_utils has been renamed 'crypto/pem.py'. Please update your imports
|
||||||
|
|
|
@ -55,7 +55,7 @@ from .cryptography_support import (
|
||||||
cryptography_compare_public_keys,
|
cryptography_compare_public_keys,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .identify import (
|
from .pem import (
|
||||||
identify_private_key_format,
|
identify_private_key_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||||
get_fingerprint_of_privatekey,
|
get_fingerprint_of_privatekey,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
identify_private_key_format,
|
identify_private_key_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -54,3 +54,21 @@ def identify_private_key_format(content):
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
pass
|
pass
|
||||||
return 'raw'
|
return 'raw'
|
||||||
|
|
||||||
|
|
||||||
|
def split_pem_list(text, keep_inbetween=False):
|
||||||
|
'''
|
||||||
|
Split concatenated PEM objects into a list of strings, where each is one PEM object.
|
||||||
|
'''
|
||||||
|
result = []
|
||||||
|
current = [] if keep_inbetween else None
|
||||||
|
for line in text.splitlines(True):
|
||||||
|
if line.strip():
|
||||||
|
if not keep_inbetween and line.startswith('-----BEGIN '):
|
||||||
|
current = []
|
||||||
|
if current is not None:
|
||||||
|
current.append(line)
|
||||||
|
if line.startswith('-----END '):
|
||||||
|
result.append(''.join(current))
|
||||||
|
current = [] if keep_inbetween else None
|
||||||
|
return result
|
|
@ -526,6 +526,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||||
cryptography_name_to_oid,
|
cryptography_name_to_oid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
|
split_pem_list,
|
||||||
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
from ansible_collections.community.crypto.plugins.module_utils.acme import (
|
||||||
ModuleFailException,
|
ModuleFailException,
|
||||||
write_file,
|
write_file,
|
||||||
|
@ -829,17 +833,10 @@ class ACMEClient(object):
|
||||||
chain = []
|
chain = []
|
||||||
|
|
||||||
# Parse data
|
# Parse data
|
||||||
lines = content.decode('utf-8').splitlines(True)
|
certs = split_pem_list(content.decode('utf-8'), keep_inbetween=True)
|
||||||
current = []
|
if certs:
|
||||||
for line in lines:
|
cert = certs[0]
|
||||||
if line.strip():
|
chain = certs[1:]
|
||||||
current.append(line)
|
|
||||||
if line.startswith('-----END CERTIFICATE-----'):
|
|
||||||
if cert is None:
|
|
||||||
cert = ''.join(current)
|
|
||||||
else:
|
|
||||||
chain.append(''.join(current))
|
|
||||||
current = []
|
|
||||||
|
|
||||||
alternates = []
|
alternates = []
|
||||||
|
|
||||||
|
@ -855,7 +852,7 @@ class ACMEClient(object):
|
||||||
|
|
||||||
process_links(info, f)
|
process_links(info, f)
|
||||||
|
|
||||||
if cert is None or current:
|
if cert is None:
|
||||||
raise ModuleFailException("Failed to parse certificate chain download from {0}: {1} (headers: {2})".format(url, content, info))
|
raise ModuleFailException("Failed to parse certificate chain download from {0}: {1} (headers: {2})".format(url, content, info))
|
||||||
return {'cert': cert, 'chain': chain, 'alternates': alternates}
|
return {'cert': cert, 'chain': chain, 'alternates': alternates}
|
||||||
|
|
||||||
|
|
|
@ -124,6 +124,10 @@ import traceback
|
||||||
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
|
||||||
from ansible.module_utils._text import to_bytes
|
from ansible.module_utils._text import to_bytes
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
|
split_pem_list,
|
||||||
|
)
|
||||||
|
|
||||||
CRYPTOGRAPHY_IMP_ERR = None
|
CRYPTOGRAPHY_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
import cryptography
|
import cryptography
|
||||||
|
@ -194,27 +198,17 @@ def parse_PEM_list(module, text, source, fail_on_error=True):
|
||||||
Parse concatenated PEM certificates. Return list of ``Certificate`` objects.
|
Parse concatenated PEM certificates. Return list of ``Certificate`` objects.
|
||||||
'''
|
'''
|
||||||
result = []
|
result = []
|
||||||
lines = text.splitlines(True)
|
for cert_pem in split_pem_list(text):
|
||||||
current = None
|
# Try to load PEM certificate
|
||||||
for line in lines:
|
try:
|
||||||
if line.strip():
|
cert = cryptography.x509.load_pem_x509_certificate(to_bytes(cert_pem), _cryptography_backend)
|
||||||
if line.startswith('-----BEGIN '):
|
result.append(Certificate(cert_pem, cert))
|
||||||
current = [line]
|
except Exception as e:
|
||||||
elif current is not None:
|
msg = 'Cannot parse certificate #{0} from {1}: {2}'.format(len(result) + 1, source, e)
|
||||||
current.append(line)
|
if fail_on_error:
|
||||||
if line.startswith('-----END '):
|
module.fail_json(msg=msg)
|
||||||
cert_pem = ''.join(current)
|
else:
|
||||||
current = None
|
module.warn(msg)
|
||||||
# Try to load PEM certificate
|
|
||||||
try:
|
|
||||||
cert = cryptography.x509.load_pem_x509_certificate(to_bytes(cert_pem), _cryptography_backend)
|
|
||||||
result.append(Certificate(cert_pem, cert))
|
|
||||||
except Exception as e:
|
|
||||||
msg = 'Cannot parse certificate #{0} from {1}: {2}'.format(len(result) + 1, source, e)
|
|
||||||
if fail_on_error:
|
|
||||||
module.fail_json(msg=msg)
|
|
||||||
else:
|
|
||||||
module.warn(msg)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -27,10 +27,19 @@ options:
|
||||||
choices: [ export, parse ]
|
choices: [ export, parse ]
|
||||||
other_certificates:
|
other_certificates:
|
||||||
description:
|
description:
|
||||||
- List of other certificates to include. Pre 2.8 this parameter was called C(ca_certificates)
|
- List of other certificates to include. Pre Ansible 2.8 this parameter was called I(ca_certificates).
|
||||||
|
- Assumes there is one PEM-encoded certificate per file. If a file contains multiple PEM certificates,
|
||||||
|
set I(other_certificates_parse_all) to C(true).
|
||||||
type: list
|
type: list
|
||||||
elements: path
|
elements: path
|
||||||
aliases: [ ca_certificates ]
|
aliases: [ ca_certificates ]
|
||||||
|
other_certificates_parse_all:
|
||||||
|
description:
|
||||||
|
- If set to C(true), assumes that the files mentioned in I(other_certificates) can contain more than one
|
||||||
|
certificate per file (or even none per file).
|
||||||
|
type: bool
|
||||||
|
default: false
|
||||||
|
version_added: 1.4.0
|
||||||
certificate_path:
|
certificate_path:
|
||||||
description:
|
description:
|
||||||
- The path to read certificates and private keys from.
|
- The path to read certificates and private keys from.
|
||||||
|
@ -115,6 +124,27 @@ EXAMPLES = r'''
|
||||||
privatekey_path: /opt/certs/keys/key.pem
|
privatekey_path: /opt/certs/keys/key.pem
|
||||||
certificate_path: /opt/certs/cert.pem
|
certificate_path: /opt/certs/cert.pem
|
||||||
other_certificates: /opt/certs/ca.pem
|
other_certificates: /opt/certs/ca.pem
|
||||||
|
# Note that if /opt/certs/ca.pem contains multiple certificates,
|
||||||
|
# only the first one will be used. See the other_certificates_parse_all
|
||||||
|
# option for changing this behavior.
|
||||||
|
state: present
|
||||||
|
|
||||||
|
- name: Generate PKCS#12 file
|
||||||
|
community.crypto.openssl_pkcs12:
|
||||||
|
action: export
|
||||||
|
path: /opt/certs/ansible.p12
|
||||||
|
friendly_name: raclette
|
||||||
|
privatekey_path: /opt/certs/keys/key.pem
|
||||||
|
certificate_path: /opt/certs/cert.pem
|
||||||
|
other_certificates_parse_all: true
|
||||||
|
other_certificates:
|
||||||
|
- /opt/certs/ca_bundle.pem
|
||||||
|
# Since we set other_certificates_parse_all to true, all
|
||||||
|
# certificates in the CA bundle are included and not just
|
||||||
|
# the first one.
|
||||||
|
- /opt/certs/intermediate.pem
|
||||||
|
# In case this file has multiple certificates in it,
|
||||||
|
# all will be included as well.
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
- name: Change PKCS#12 file permission
|
- name: Change PKCS#12 file permission
|
||||||
|
@ -201,6 +231,10 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.support im
|
||||||
load_certificate,
|
load_certificate,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
|
split_pem_list,
|
||||||
|
)
|
||||||
|
|
||||||
PYOPENSSL_IMP_ERR = None
|
PYOPENSSL_IMP_ERR = None
|
||||||
try:
|
try:
|
||||||
from OpenSSL import crypto
|
from OpenSSL import crypto
|
||||||
|
@ -211,6 +245,15 @@ else:
|
||||||
pyopenssl_found = True
|
pyopenssl_found = True
|
||||||
|
|
||||||
|
|
||||||
|
def load_certificate_set(filename):
|
||||||
|
'''
|
||||||
|
Load list of concatenated PEM files, and return a list of parsed certificates.
|
||||||
|
'''
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
data = f.read().decode('utf-8')
|
||||||
|
return [load_certificate(None, content=cert) for cert in split_pem_list(data)]
|
||||||
|
|
||||||
|
|
||||||
class PkcsError(OpenSSLObjectError):
|
class PkcsError(OpenSSLObjectError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -226,6 +269,7 @@ class Pkcs(OpenSSLObject):
|
||||||
)
|
)
|
||||||
self.action = module.params['action']
|
self.action = module.params['action']
|
||||||
self.other_certificates = module.params['other_certificates']
|
self.other_certificates = module.params['other_certificates']
|
||||||
|
self.other_certificates_parse_all = module.params['other_certificates_parse_all']
|
||||||
self.certificate_path = module.params['certificate_path']
|
self.certificate_path = module.params['certificate_path']
|
||||||
self.friendly_name = module.params['friendly_name']
|
self.friendly_name = module.params['friendly_name']
|
||||||
self.iter_size = module.params['iter_size']
|
self.iter_size = module.params['iter_size']
|
||||||
|
@ -244,6 +288,17 @@ class Pkcs(OpenSSLObject):
|
||||||
self.backup = module.params['backup']
|
self.backup = module.params['backup']
|
||||||
self.backup_file = None
|
self.backup_file = None
|
||||||
|
|
||||||
|
if self.other_certificates:
|
||||||
|
if self.other_certificates_parse_all:
|
||||||
|
filenames = list(self.other_certificates)
|
||||||
|
self.other_certificates = []
|
||||||
|
for other_cert_bundle in filenames:
|
||||||
|
self.other_certificates.extend(load_certificate_set(other_cert_bundle))
|
||||||
|
else:
|
||||||
|
self.other_certificates = [
|
||||||
|
load_certificate(other_cert) for other_cert in self.other_certificates
|
||||||
|
]
|
||||||
|
|
||||||
def check(self, module, perms_required=True):
|
def check(self, module, perms_required=True):
|
||||||
"""Ensure the resource is in its desired state."""
|
"""Ensure the resource is in its desired state."""
|
||||||
|
|
||||||
|
@ -340,9 +395,7 @@ class Pkcs(OpenSSLObject):
|
||||||
self.pkcs12 = crypto.PKCS12()
|
self.pkcs12 = crypto.PKCS12()
|
||||||
|
|
||||||
if self.other_certificates:
|
if self.other_certificates:
|
||||||
other_certs = [load_certificate(other_cert) for other_cert
|
self.pkcs12.set_ca_certificates(self.other_certificates)
|
||||||
in self.other_certificates]
|
|
||||||
self.pkcs12.set_ca_certificates(other_certs)
|
|
||||||
|
|
||||||
if self.certificate_path:
|
if self.certificate_path:
|
||||||
self.pkcs12.set_certificate(load_certificate(self.certificate_path))
|
self.pkcs12.set_certificate(load_certificate(self.certificate_path))
|
||||||
|
@ -402,6 +455,7 @@ def main():
|
||||||
argument_spec = dict(
|
argument_spec = dict(
|
||||||
action=dict(type='str', default='export', choices=['export', 'parse']),
|
action=dict(type='str', default='export', choices=['export', 'parse']),
|
||||||
other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']),
|
other_certificates=dict(type='list', elements='path', aliases=['ca_certificates']),
|
||||||
|
other_certificates_parse_all=dict(type='bool', default=False),
|
||||||
certificate_path=dict(type='path'),
|
certificate_path=dict(type='path'),
|
||||||
force=dict(type='bool', default=False),
|
force=dict(type='bool', default=False),
|
||||||
friendly_name=dict(type='str', aliases=['name']),
|
friendly_name=dict(type='str', aliases=['name']),
|
||||||
|
|
|
@ -405,7 +405,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||||
cryptography_get_signature_algorithm_oid_from_crl,
|
cryptography_get_signature_algorithm_oid_from_crl,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
identify_pem_format,
|
identify_pem_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ from ansible_collections.community.crypto.plugins.module_utils.crypto.cryptograp
|
||||||
cryptography_get_signature_algorithm_oid_from_crl,
|
cryptography_get_signature_algorithm_oid_from_crl,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ansible_collections.community.crypto.plugins.module_utils.crypto.identify import (
|
from ansible_collections.community.crypto.plugins.module_utils.crypto.pem import (
|
||||||
identify_pem_format,
|
identify_pem_format,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,12 @@
|
||||||
pkey: ansible_pkey2.pem
|
pkey: ansible_pkey2.pem
|
||||||
- name: ansible3
|
- name: ansible3
|
||||||
pkey: ansible_pkey3.pem
|
pkey: ansible_pkey3.pem
|
||||||
|
- name: Generate concatenated PEM file
|
||||||
|
copy:
|
||||||
|
dest: '{{ output_dir }}/ansible23.crt'
|
||||||
|
content: |
|
||||||
|
{{ lookup("file", output_dir ~ "/ansible2.crt") }}
|
||||||
|
{{ lookup("file", output_dir ~ "/ansible3.crt") }}
|
||||||
- name: Generate PKCS#12 file
|
- name: Generate PKCS#12 file
|
||||||
openssl_pkcs12:
|
openssl_pkcs12:
|
||||||
path: '{{ output_dir }}/ansible.p12'
|
path: '{{ output_dir }}/ansible.p12'
|
||||||
|
@ -113,7 +119,7 @@
|
||||||
friendly_name: abracadabra
|
friendly_name: abracadabra
|
||||||
privatekey_path: '{{ output_dir }}/ansible_pkey.pem'
|
privatekey_path: '{{ output_dir }}/ansible_pkey.pem'
|
||||||
certificate_path: '{{ output_dir }}/ansible.crt'
|
certificate_path: '{{ output_dir }}/ansible.crt'
|
||||||
ca_certificates:
|
other_certificates:
|
||||||
- '{{ output_dir }}/ansible2.crt'
|
- '{{ output_dir }}/ansible2.crt'
|
||||||
- '{{ output_dir }}/ansible3.crt'
|
- '{{ output_dir }}/ansible3.crt'
|
||||||
state: present
|
state: present
|
||||||
|
@ -124,7 +130,7 @@
|
||||||
friendly_name: abracadabra
|
friendly_name: abracadabra
|
||||||
privatekey_path: '{{ output_dir }}/ansible_pkey.pem'
|
privatekey_path: '{{ output_dir }}/ansible_pkey.pem'
|
||||||
certificate_path: '{{ output_dir }}/ansible.crt'
|
certificate_path: '{{ output_dir }}/ansible.crt'
|
||||||
ca_certificates:
|
other_certificates:
|
||||||
- '{{ output_dir }}/ansible2.crt'
|
- '{{ output_dir }}/ansible2.crt'
|
||||||
- '{{ output_dir }}/ansible3.crt'
|
- '{{ output_dir }}/ansible3.crt'
|
||||||
state: present
|
state: present
|
||||||
|
@ -237,7 +243,7 @@
|
||||||
openssl_pkcs12:
|
openssl_pkcs12:
|
||||||
path: '{{ output_dir }}/ansible_empty.p12'
|
path: '{{ output_dir }}/ansible_empty.p12'
|
||||||
friendly_name: abracadabra
|
friendly_name: abracadabra
|
||||||
ca_certificates:
|
other_certificates:
|
||||||
- '{{ output_dir }}/ansible2.crt'
|
- '{{ output_dir }}/ansible2.crt'
|
||||||
- '{{ output_dir }}/ansible3.crt'
|
- '{{ output_dir }}/ansible3.crt'
|
||||||
state: present
|
state: present
|
||||||
|
@ -246,11 +252,20 @@
|
||||||
openssl_pkcs12:
|
openssl_pkcs12:
|
||||||
path: '{{ output_dir }}/ansible_empty.p12'
|
path: '{{ output_dir }}/ansible_empty.p12'
|
||||||
friendly_name: abracadabra
|
friendly_name: abracadabra
|
||||||
ca_certificates:
|
other_certificates:
|
||||||
- '{{ output_dir }}/ansible2.crt'
|
|
||||||
- '{{ output_dir }}/ansible3.crt'
|
- '{{ output_dir }}/ansible3.crt'
|
||||||
|
- '{{ output_dir }}/ansible2.crt'
|
||||||
state: present
|
state: present
|
||||||
register: p12_empty_idem
|
register: p12_empty_idem
|
||||||
|
- name: Generate 'empty' PKCS#12 file (idempotent, concatenated other certificates)
|
||||||
|
openssl_pkcs12:
|
||||||
|
path: '{{ output_dir }}/ansible_empty.p12'
|
||||||
|
friendly_name: abracadabra
|
||||||
|
other_certificates:
|
||||||
|
- '{{ output_dir }}/ansible23.crt'
|
||||||
|
other_certificates_parse_all: true
|
||||||
|
state: present
|
||||||
|
register: p12_empty_concat_idem
|
||||||
- name: Generate 'empty' PKCS#12 file (parse)
|
- name: Generate 'empty' PKCS#12 file (parse)
|
||||||
openssl_pkcs12:
|
openssl_pkcs12:
|
||||||
src: '{{ output_dir }}/ansible_empty.p12'
|
src: '{{ output_dir }}/ansible_empty.p12'
|
||||||
|
|
|
@ -64,4 +64,5 @@
|
||||||
that:
|
that:
|
||||||
- p12_empty is changed
|
- p12_empty is changed
|
||||||
- p12_empty_idem is not changed
|
- p12_empty_idem is not changed
|
||||||
|
- p12_empty_concat_idem is not changed
|
||||||
- "lookup('file', output_dir ~ '/ansible_empty.pem') == lookup('file', output_dir ~ '/ansible3.crt') ~ '\n' ~ lookup('file', output_dir ~ '/ansible2.crt')"
|
- "lookup('file', output_dir ~ '/ansible_empty.pem') == lookup('file', output_dir ~ '/ansible3.crt') ~ '\n' ~ lookup('file', output_dir ~ '/ansible2.crt')"
|
||||||
|
|
Loading…
Reference in New Issue