community.general/lib/ansible/modules/crypto/openssl_csr.py
Yanis Guenane fdce6484ec crypto/openssl_csr: Specify proper Ansible version
The commit was started before 2.3 was branched, but was only merged once
2.3 was actually branched. This leads to documentation stating this
module is new in 2.3 when it will be actually new in 2.4
2017-04-18 15:46:07 +02:00

337 lines
11 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# (c) 2017, Yanis Guenane <yanis+ansible@guenane.org>
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
ANSIBLE_METADATA = {'metadata_version': '1.0',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: openssl_csr
author: "Yanis Guenane (@Spredzy)"
version_added: "2.4"
short_description: Generate OpenSSL Certificate Signing Request (CSR)
description:
- "This module allows one to (re)generates OpenSSL certificate signing requests.
It uses the pyOpenSSL python library to interact with openssl. This module support
the subjectAltName extension. Note: At least one of commonName or subjectAltName must
be specified."
requirements:
- "python-pyOpenSSL"
options:
state:
required: false
default: "present"
choices: [ present, absent ]
description:
- Whether the certificate signing request should exist or not, taking action if the state is different from what is stated.
digest:
required: false
default: "sha256"
description:
- Digest used when signing the certificate signing request with the private key
privatekey_path:
required: true
description:
- Path to the privatekey to use when signing the certificate signing request
version:
required: false
default: 3
description:
- Version of the certificate signing request
force:
required: false
default: False
choices: [ True, False ]
description:
- Should the certificate signing request be forced regenerated by this ansible module
path:
required: true
description:
- Name of the folder in which the generated OpenSSL certificate signing request will be written
subjectAltName:
required: false
description:
- SAN extention to attach to the certificate signing request
countryName:
required: false
aliases: [ 'C' ]
description:
- countryName field of the certificate signing request subject
stateOrProvinceName:
required: false
aliases: [ 'ST' ]
description:
- stateOrProvinceName field of the certificate signing request subject
localityName:
required: false
aliases: [ 'L' ]
description:
- localityName field of the certificate signing request subject
organizationName:
required: false
aliases: [ 'O' ]
description:
- organizationName field of the certificate signing request subject
organizationUnitName:
required: false
aliases: [ 'OU' ]
description:
- organizationUnitName field of the certificate signing request subject
commonName:
required: false
aliases: [ 'CN' ]
description:
- commonName field of the certificate signing request subject
emailAddress:
required: false
aliases: [ 'E' ]
description:
- emailAddress field of the certificate signing request subject
'''
EXAMPLES = '''
# Generate an OpenSSL Certificate Signing Request
- openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
commonName: www.ansible.com
# Generate an OpenSSL Certificate Signing Request with Subject informations
- openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
countryName: FR
organizationName: Ansible
emailAddress: jdoe@ansible.com
commonName: www.ansible.com
# Generate an OpenSSL Certificate Signing Request with subjectAltName extension
- openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
subjectAltName: 'DNS:www.ansible.com,DNS:m.ansible.com'
# Force re-generate an OpenSSL Certificate Signing Request
- openssl_csr:
path: /etc/ssl/csr/www.ansible.com.csr
privatekey_path: /etc/ssl/private/ansible.com.pem
force: True
commonName: www.ansible.com
'''
RETURN = '''
csr:
description: Path to the generated Certificate Signing Request
returned:
- changed
- success
type: string
sample: /etc/ssl/csr/www.ansible.com.csr
subject:
description: A dictionnary of the subject attached to the CSR
returned:
- changed
- success
type: list
sample: {'CN': 'www.ansible.com', 'O': 'Ansible'}
subjectAltName:
description: The alternative names this CSR is valid for
returned:
- changed
- success
type: string
sample: 'DNS:www.ansible.com,DNS:m.ansible.com'
'''
import errno
import os
try:
from OpenSSL import crypto
except ImportError:
pyopenssl_found = False
else:
pyopenssl_found = True
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.pycompat24 import get_exception
class CertificateSigningRequestError(Exception):
pass
class CertificateSigningRequest(object):
def __init__(self, module):
self.state = module.params['state']
self.digest = module.params['digest']
self.force = module.params['force']
self.subjectAltName = module.params['subjectAltName']
self.path = module.params['path']
self.privatekey_path = module.params['privatekey_path']
self.version = module.params['version']
self.changed = True
self.request = None
self.privatekey = None
self.subject = {
'C': module.params['countryName'],
'ST': module.params['stateOrProvinceName'],
'L': module.params['localityName'],
'O': module.params['organizationName'],
'OU': module.params['organizationalUnitName'],
'CN': module.params['commonName'],
'emailAddress': module.params['emailAddress'],
}
if self.subjectAltName is None:
self.subjectAltName = 'DNS:%s' % self.subject['CN']
for (key, value) in self.subject.items():
if value is None:
del self.subject[key]
def generate(self, module):
'''Generate the certificate signing request.'''
if not os.path.exists(self.path) or self.force:
req = crypto.X509Req()
req.set_version(self.version)
subject = req.get_subject()
for (key, value) in self.subject.items():
if value is not None:
setattr(subject, key, value)
if self.subjectAltName is not None:
req.add_extensions([crypto.X509Extension("subjectAltName", False, self.subjectAltName)])
privatekey_content = open(self.privatekey_path).read()
self.privatekey = crypto.load_privatekey(crypto.FILETYPE_PEM, privatekey_content)
req.set_pubkey(self.privatekey)
req.sign(self.privatekey, self.digest)
self.request = req
try:
csr_file = open(self.path, 'w')
csr_file.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, self.request))
csr_file.close()
except (IOError, OSError):
e = get_exception()
raise CertificateSigningRequestError(e)
else:
self.changed = False
file_args = module.load_file_common_arguments(module.params)
if module.set_fs_attributes_if_different(file_args, False):
self.changed = True
def remove(self):
'''Remove the Certificate Signing Request.'''
try:
os.remove(self.path)
except OSError:
e = get_exception()
if e.errno != errno.ENOENT:
raise CertificateSigningRequestError(e)
else:
self.changed = False
def dump(self):
'''Serialize the object into a dictionnary.'''
result = {
'csr': self.path,
'subject': self.subject,
'subjectAltName': self.subjectAltName,
'changed': self.changed
}
return result
def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(default='present', choices=['present', 'absent'], type='str'),
digest=dict(default='sha256', type='str'),
privatekey_path=dict(require=True, type='path'),
version=dict(default='3', type='int'),
force=dict(default=False, type='bool'),
subjectAltName=dict(aliases=['subjectAltName'], type='str'),
path=dict(required=True, type='path'),
countryName=dict(aliases=['C'], type='str'),
stateOrProvinceName=dict(aliases=['ST'], type='str'),
localityName=dict(aliases=['L'], type='str'),
organizationName=dict(aliases=['O'], type='str'),
organizationalUnitName=dict(aliases=['OU'], type='str'),
commonName=dict(aliases=['CN'], type='str'),
emailAddress=dict(aliases=['E'], type='str'),
),
add_file_common_args=True,
supports_check_mode=True,
required_one_of=[['commonName', 'subjectAltName']],
)
path = module.params['path']
base_dir = os.path.dirname(module.params['path'])
if not os.path.isdir(base_dir):
module.fail_json(name=path, msg='The directory %s does not exist' % path)
csr = CertificateSigningRequest(module)
if module.params['state'] == 'present':
if module.check_mode:
result = csr.dump()
result['changed'] = module.params['force'] or not os.path.exists(path)
module.exit_json(**result)
try:
csr.generate(module)
except CertificateSigningRequestError:
e = get_exception()
module.fail_json(msg=str(e))
else:
if module.check_mode:
result = csr.dump()
result['changed'] = os.path.exists(path)
module.exit_json(**result)
try:
csr.remove()
except CertificateSigningRequestError:
e = get_exception()
module.fail_json(msg=str(e))
result = csr.dump()
module.exit_json(**result)
if __name__ == "__main__":
main()