community.general/lib/ansible/modules/crypto/openssl_privatekey.py

272 lines
8.5 KiB
Python
Raw Normal View History

#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2016, 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'}
2016-12-06 10:35:25 +00:00
DOCUMENTATION = '''
---
module: openssl_privatekey
author: "Yanis Guenane (@Spredzy)"
version_added: "2.3"
short_description: Generate OpenSSL private keys.
description:
- "This module allows one to (re)generate OpenSSL private keys. It uses
the pyOpenSSL python library to interact with openssl. One can generate
either RSA or DSA private keys. Keys are generated in PEM format."
requirements:
- "python-pyOpenSSL"
options:
state:
required: false
default: "present"
choices: [ present, absent ]
description:
- Whether the private key should exist or not, taking action if the state is different from what is stated.
size:
required: false
default: 4096
description:
- Size (in bits) of the TLS/SSL key to generate
type:
required: false
default: "RSA"
choices: [ RSA, DSA ]
description:
- The algorithm used to generate the TLS/SSL private key
force:
required: false
default: False
choices: [ True, False ]
description:
- Should the key be regenerated even it it already exists
path:
required: true
description:
- Name of the file in which the generated TLS/SSL private key will be written. It will have 0600 mode.
'''
EXAMPLES = '''
# Generate an OpenSSL private key with the default values (4096 bits, RSA)
# and no public key
- openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
# Generate an OpenSSL private key with a different size (2048 bits)
- openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
size: 2048
# Force regenerate an OpenSSL private key if it already exists
- openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
force: True
# Generate an OpenSSL private key with a different algorithm (DSA)
- openssl_privatekey:
path: /etc/ssl/private/ansible.com.pem
type: DSA
'''
RETURN = '''
size:
description: Size (in bits) of the TLS/SSL private key
returned: changed or success
type: int
sample: 4096
type:
description: Algorithm used to generate the TLS/SSL private key
returned: changed or success
type: string
sample: RSA
filename:
description: Path to the generated TLS/SSL private key file
returned: changed or success
type: string
sample: /etc/ssl/private/ansible.com.pem
fingerprint:
description: The fingerprint of the public key. Fingerprint will be generated for each hashlib.algorithms available.
Requires PyOpenSSL >= 16.0 for meaningful output.
returned: changed or success
type: dict
sample:
md5: "84:75:71:72:8d:04:b5:6c:4d:37:6d:66:83:f5:4c:29"
sha1: "51:cc:7c:68:5d:eb:41:43:88:7e:1a:ae:c7:f8:24:72:ee:71:f6:10"
sha224: "b1:19:a6:6c:14:ac:33:1d:ed:18:50:d3:06:5c:b2:32:91:f1:f1:52:8c:cb:d5:75:e9:f5:9b:46"
sha256: "41:ab:c7:cb:d5:5f:30:60:46:99:ac:d4:00:70:cf:a1:76:4f:24:5d:10:24:57:5d:51:6e:09:97:df:2f:de:c7"
sha384: "85:39:50:4e:de:d9:19:33:40:70:ae:10:ab:59:24:19:51:c3:a2:e4:0b:1c:b1:6e:dd:b3:0c:d9:9e:6a:46:af:da:18:f8:ef:ae:2e:c0:9a:75:2c:9b:b3:0f:3a:5f:3d"
sha512: "fd:ed:5e:39:48:5f:9f:fe:7f:25:06:3f:79:08:cd:ee:a5:e7:b3:3d:13:82:87:1f:84:e1:f5:c7:28:77:53:94:86:56:38:69:f0:d9:35:22:01:1e:a6:60:...:0f:9b"
'''
import errno
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.crypto import get_fingerprint
from ansible.module_utils.pycompat24 import get_exception
try:
from OpenSSL import crypto
except ImportError:
pyopenssl_found = False
else:
pyopenssl_found = True
import os
from ansible.module_utils._text import to_native
class PrivateKeyError(Exception):
pass
class PrivateKey(object):
def __init__(self, module):
self.size = module.params['size']
self.state = module.params['state']
self.name = os.path.basename(module.params['path'])
self.type = module.params['type']
self.force = module.params['force']
self.path = module.params['path']
self.mode = module.params['mode']
self.changed = True
self.privatekey = None
self.fingerprint = {}
self.check_mode = module.check_mode
def generate(self, module):
"""Generate a keypair."""
if not os.path.exists(self.path) or self.force:
self.privatekey = crypto.PKey()
if self.type == 'RSA':
crypto_type = crypto.TYPE_RSA
else:
crypto_type = crypto.TYPE_DSA
try:
self.privatekey.generate_key(crypto_type, self.size)
except (TypeError, ValueError) as exc:
raise PrivateKeyError(exc)
try:
privatekey_file = os.open(self.path,
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
self.mode)
os.write(privatekey_file, crypto.dump_privatekey(crypto.FILETYPE_PEM, self.privatekey))
os.close(privatekey_file)
except IOError as exc:
self.remove()
raise PrivateKeyError(exc)
else:
self.changed = False
self.fingerprint = get_fingerprint(self.path)
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 private key from the filesystem."""
try:
os.remove(self.path)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise PrivateKeyError(exc)
else:
self.changed = False
def dump(self):
"""Serialize the object into a dictionary."""
result = {
'size': self.size,
'type': self.type,
'filename': self.path,
'changed': self.changed,
'fingerprint': self.fingerprint,
}
return result
def main():
module = AnsibleModule(
argument_spec = dict(
state=dict(default='present', choices=['present', 'absent'], type='str'),
size=dict(default=4096, type='int'),
type=dict(default='RSA', choices=['RSA', 'DSA'], type='str'),
force=dict(default=False, type='bool'),
path=dict(required=True, type='path'),
),
supports_check_mode = True,
add_file_common_args = True,
)
if not pyopenssl_found:
module.fail_json(msg='the python pyOpenSSL module is required')
path = module.params['path']
base_dir = os.path.dirname(module.params['path'])
if not os.path.isdir(base_dir):
module.fail_json(name=base_dir, msg='The directory %s does not exist or the file is not a directory' % base_dir)
if not module.params['mode']:
2016-11-03 02:32:02 +00:00
module.params['mode'] = int('0600', 8)
private_key = PrivateKey(module)
if private_key.state == 'present':
if module.check_mode:
result = private_key.dump()
result['changed'] = module.params['force'] or not os.path.exists(path)
module.exit_json(**result)
try:
private_key.generate(module)
except PrivateKeyError as exc:
module.fail_json(msg=to_native(exc))
else:
if module.check_mode:
result = private_key.dump()
result['changed'] = os.path.exists(path)
module.exit_json(**result)
try:
private_key.remove()
except PrivateKeyError as exc:
module.fail_json(msg=to_native(exc))
result = private_key.dump()
module.exit_json(**result)
if __name__ == '__main__':
main()