#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright (c) 2016-2017, Yanis Guenane # Copyright (c) 2017, Markus Teufelberger # Copyright (2) 2020, 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 = r''' --- module: x509_certificate_pipe short_description: Generate and/or check OpenSSL certificates version_added: 1.3.0 description: - It implements a notion of provider (one of V(selfsigned), V(ownca), V(entrust)) for your certificate. author: - Yanis Guenane (@Spredzy) - Markus Teufelberger (@MarkusTeufelberger) - Felix Fontein (@felixfontein) extends_documentation_fragment: - community.crypto.attributes - community.crypto.module_certificate - community.crypto.module_certificate.backend_entrust_documentation - community.crypto.module_certificate.backend_ownca_documentation - community.crypto.module_certificate.backend_selfsigned_documentation attributes: check_mode: support: full details: - Currently in check mode, private keys will not be (re-)generated, only the changed status is set. This will change in community.crypto 3.0.0. - From community.crypto 3.0.0 on, the module will ignore check mode and always behave as if check mode is not active. If you think this breaks your use-case of this module, please create an issue in the community.crypto repository. diff_mode: support: full options: provider: description: - Name of the provider to use to generate/retrieve the OpenSSL certificate. - "The V(entrust) provider requires credentials for the L(Entrust Certificate Services,https://www.entrustdatacard.com/products/categories/ssl-certificates) (ECS) API." type: str choices: [ entrust, ownca, selfsigned ] required: true content: description: - The existing certificate. type: str seealso: - module: community.crypto.x509_certificate ''' EXAMPLES = r''' - name: Generate a Self Signed OpenSSL certificate community.crypto.x509_certificate_pipe: provider: selfsigned privatekey_path: /etc/ssl/private/ansible.com.pem csr_path: /etc/ssl/csr/ansible.com.csr register: result - name: Print the certificate ansible.builtin.debug: var: result.certificate # In the following example, both CSR and certificate file are stored on the # machine where ansible-playbook is executed, while the OwnCA data (certificate, # private key) are stored on the remote machine. - name: (1/2) Generate an OpenSSL Certificate with the CSR provided inline community.crypto.x509_certificate_pipe: provider: ownca content: "{{ lookup('ansible.builtin.file', '/etc/ssl/csr/www.ansible.com.crt') }}" csr_content: "{{ lookup('ansible.builtin.file', '/etc/ssl/csr/www.ansible.com.csr') }}" ownca_cert: /path/to/ca_cert.crt ownca_privatekey: /path/to/ca_cert.key ownca_privatekey_passphrase: hunter2 register: result - name: (2/2) Store certificate ansible.builtin.copy: dest: /etc/ssl/csr/www.ansible.com.crt content: "{{ result.certificate }}" delegate_to: localhost when: result is changed # In the following example, the certificate from another machine is signed by # our OwnCA whose private key and certificate are only available on this # machine (where ansible-playbook is executed), without having to write # the certificate file to disk on localhost. The CSR could have been # provided by community.crypto.openssl_csr_pipe earlier, or also have been # read from the remote machine. - name: (1/3) Read certificate's contents from remote machine ansible.builtin.slurp: src: /etc/ssl/csr/www.ansible.com.crt register: certificate_content - name: (2/3) Generate an OpenSSL Certificate with the CSR provided inline community.crypto.x509_certificate_pipe: provider: ownca content: "{{ certificate_content.content | b64decode }}" csr_content: "{{ the_csr }}" ownca_cert: /path/to/ca_cert.crt ownca_privatekey: /path/to/ca_cert.key ownca_privatekey_passphrase: hunter2 delegate_to: localhost register: result - name: (3/3) Store certificate ansible.builtin.copy: dest: /etc/ssl/csr/www.ansible.com.crt content: "{{ result.certificate }}" when: result is changed ''' RETURN = r''' certificate: description: The (current or generated) certificate's content. returned: changed or success type: str ''' from ansible.module_utils.common.text.converters import to_native from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate import ( select_backend, get_certificate_argument_spec, ) from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_entrust import ( EntrustCertificateProvider, add_entrust_provider_to_argument_spec, ) from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_ownca import ( OwnCACertificateProvider, add_ownca_provider_to_argument_spec, ) from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.certificate_selfsigned import ( SelfSignedCertificateProvider, add_selfsigned_provider_to_argument_spec, ) from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( OpenSSLObjectError, ) class GenericCertificate(object): """Retrieve a certificate using the given module backend.""" def __init__(self, module, module_backend): self.check_mode = module.check_mode self.module = module self.module_backend = module_backend self.changed = False if module.params['content'] is not None: self.module_backend.set_existing(module.params['content'].encode('utf-8')) def generate(self, module): if self.module_backend.needs_regeneration(): if not self.check_mode: self.module_backend.generate_certificate() else: self.module.deprecate( 'Check mode support for x509_certificate_pipe will change in community.crypto 3.0.0' ' to behave the same as without check mode. You can get that behavior right now' ' by adding `check_mode: false` to the x509_certificate_pipe task. If you think this' ' breaks your use-case of this module, please create an issue in the' ' community.crypto repository', version='3.0.0', collection_name='community.crypto', ) self.changed = True def dump(self, check_mode=False): result = self.module_backend.dump(include_certificate=True) result.update({ 'changed': self.changed, }) return result def main(): argument_spec = get_certificate_argument_spec() argument_spec.argument_spec['provider']['required'] = True add_entrust_provider_to_argument_spec(argument_spec) add_ownca_provider_to_argument_spec(argument_spec) add_selfsigned_provider_to_argument_spec(argument_spec) argument_spec.argument_spec.update(dict( content=dict(type='str'), )) module = argument_spec.create_ansible_module( supports_check_mode=True, ) try: provider = module.params['provider'] provider_map = { 'entrust': EntrustCertificateProvider, 'ownca': OwnCACertificateProvider, 'selfsigned': SelfSignedCertificateProvider, } backend = module.params['select_crypto_backend'] module_backend = select_backend(module, backend, provider_map[provider]()) certificate = GenericCertificate(module, module_backend) certificate.generate(module) result = certificate.dump() module.exit_json(**result) except OpenSSLObjectError as exc: module.fail_json(msg=to_native(exc)) if __name__ == "__main__": main()