openssl_privatekey: add backup option (#53593)
* Add backup option to openssl_privatekey. * Add changelog fragment. * Make module available in remove(). * Add tests for backup. * Update lib/ansible/modules/crypto/openssl_privatekey.py Co-Authored-By: felixfontein <felix@fontein.de> * Update lib/ansible/modules/crypto/openssl_privatekey.py Co-Authored-By: felixfontein <felix@fontein.de> * Update lib/ansible/modules/crypto/openssl_privatekey.py Co-Authored-By: felixfontein <felix@fontein.de> * Update lib/ansible/modules/crypto/openssl_privatekey.py
This commit is contained in:
parent
3fa39ac818
commit
e00f315358
9 changed files with 81 additions and 11 deletions
2
changelogs/fragments/53593-openssl_privatekey-backup.yml
Normal file
2
changelogs/fragments/53593-openssl_privatekey-backup.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "openssl_privatekey - add ``backup`` option."
|
|
@ -230,7 +230,7 @@ class OpenSSLObject(object):
|
|||
|
||||
pass
|
||||
|
||||
def remove(self):
|
||||
def remove(self, module):
|
||||
"""Remove the resource from the filesystem."""
|
||||
|
||||
try:
|
||||
|
|
|
@ -1220,7 +1220,7 @@ def main():
|
|||
module.exit_json(**result)
|
||||
|
||||
try:
|
||||
certificate.remove()
|
||||
certificate.remove(module)
|
||||
except CertificateError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
|
|
@ -1046,7 +1046,7 @@ def main():
|
|||
module.exit_json(**result)
|
||||
|
||||
try:
|
||||
csr.remove()
|
||||
csr.remove(module)
|
||||
except (CertificateSigningRequestError, crypto_utils.OpenSSLObjectError) as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ class Pkcs(crypto_utils.OpenSSLObject):
|
|||
self.pkcs12 = crypto.PKCS12()
|
||||
|
||||
try:
|
||||
self.remove()
|
||||
self.remove(module)
|
||||
except PkcsError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
@ -274,14 +274,14 @@ class Pkcs(crypto_utils.OpenSSLObject):
|
|||
self.iter_size, self.maciter_size))
|
||||
os.close(pkcs12_file)
|
||||
except (IOError, OSError) as exc:
|
||||
self.remove()
|
||||
self.remove(module)
|
||||
raise PkcsError(exc)
|
||||
|
||||
def parse(self, module):
|
||||
"""Read PKCS#12 file."""
|
||||
|
||||
try:
|
||||
self.remove()
|
||||
self.remove(module)
|
||||
with open(self.src, 'rb') as pkcs12_fh:
|
||||
pkcs12_content = pkcs12_fh.read()
|
||||
p12 = crypto.load_pkcs12(pkcs12_content,
|
||||
|
@ -298,7 +298,7 @@ class Pkcs(crypto_utils.OpenSSLObject):
|
|||
os.close(pkcs12_file)
|
||||
|
||||
except IOError as exc:
|
||||
self.remove()
|
||||
self.remove(module)
|
||||
raise PkcsError(exc)
|
||||
|
||||
|
||||
|
@ -378,7 +378,7 @@ def main():
|
|||
|
||||
if os.path.exists(module.params['path']):
|
||||
try:
|
||||
pkcs12.remove()
|
||||
pkcs12.remove(module)
|
||||
changed = True
|
||||
except PkcsError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
|
|
@ -23,6 +23,11 @@ description:
|
|||
L(ECC,https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)
|
||||
private keys.
|
||||
- Keys are generated in PEM format.
|
||||
- "Please note that the module regenerates private keys if they don't match
|
||||
the module's options. In particular, if you provide another passphrase
|
||||
(or specify none), change the keysize, etc., the private key will be
|
||||
regenerated. If you are concerned that this could overwrite your private key,
|
||||
consider using the I(backup) option."
|
||||
- The module can use the cryptography Python library, or the pyOpenSSL Python
|
||||
library. By default, it tries to detect which one is available. This can be
|
||||
overridden with the I(select_crypto_backend) option."
|
||||
|
@ -111,6 +116,13 @@ options:
|
|||
default: auto
|
||||
choices: [ auto, cryptography, pyopenssl ]
|
||||
version_added: "2.8"
|
||||
backup:
|
||||
description:
|
||||
- Create a backup file including a timestamp so you can get
|
||||
the original private key back if you overwrote it with a new one by accident.
|
||||
type: bool
|
||||
default: no
|
||||
version_added: "2.8"
|
||||
extends_documentation_fragment:
|
||||
- files
|
||||
seealso:
|
||||
|
@ -182,6 +194,11 @@ fingerprint:
|
|||
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"
|
||||
backup_file:
|
||||
description: Name of backup file created.
|
||||
returned: changed and if I(backup) is C(yes)
|
||||
type: str
|
||||
sample: /path/to/privatekey.pem.2019-03-09@11:22~
|
||||
'''
|
||||
|
||||
import abc
|
||||
|
@ -255,6 +272,9 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
|||
self.privatekey = None
|
||||
self.fingerprint = {}
|
||||
|
||||
self.backup = module.params['backup']
|
||||
self.backup_path = None
|
||||
|
||||
self.mode = module.params.get('mode', None)
|
||||
if self.mode is None:
|
||||
self.mode = 0o600
|
||||
|
@ -271,6 +291,8 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
|||
"""Generate a keypair."""
|
||||
|
||||
if not self.check(module, perms_required=False) or self.force:
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
privatekey_data = self._generate_private_key_data()
|
||||
try:
|
||||
privatekey_file = os.open(self.path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC)
|
||||
|
@ -298,6 +320,11 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
|||
if module.set_fs_attributes_if_different(file_args, False):
|
||||
self.changed = True
|
||||
|
||||
def remove(self, module):
|
||||
if self.backup:
|
||||
self.backup_file = module.backup_local(self.path)
|
||||
super(PrivateKeyBase, self).remove(module)
|
||||
|
||||
@abc.abstractmethod
|
||||
def _check_passphrase(self):
|
||||
pass
|
||||
|
@ -325,6 +352,8 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
|||
'changed': self.changed,
|
||||
'fingerprint': self.fingerprint,
|
||||
}
|
||||
if self.backup_path:
|
||||
result['backup_path'] = self.backup_path
|
||||
|
||||
return result
|
||||
|
||||
|
@ -583,6 +612,7 @@ def main():
|
|||
path=dict(type='path', required=True),
|
||||
passphrase=dict(type='str', no_log=True),
|
||||
cipher=dict(type='str'),
|
||||
backup=dict(type='bool', default=False),
|
||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||
),
|
||||
supports_check_mode=True,
|
||||
|
@ -656,7 +686,7 @@ def main():
|
|||
module.exit_json(**result)
|
||||
|
||||
try:
|
||||
private_key.remove()
|
||||
private_key.remove(module)
|
||||
except PrivateKeyError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
|
|
@ -205,7 +205,7 @@ class PublicKey(crypto_utils.OpenSSLObject):
|
|||
except (IOError, OSError) as exc:
|
||||
raise PublicKeyError(exc)
|
||||
except AttributeError as exc:
|
||||
self.remove()
|
||||
self.remove(module)
|
||||
raise PublicKeyError('You need to have PyOpenSSL>=16.0.0 to generate public keys')
|
||||
|
||||
self.fingerprint = crypto_utils.get_fingerprint(
|
||||
|
@ -315,7 +315,7 @@ def main():
|
|||
module.exit_json(**result)
|
||||
|
||||
try:
|
||||
public_key.remove()
|
||||
public_key.remove(module)
|
||||
except PublicKeyError as exc:
|
||||
module.fail_json(msg=to_native(exc))
|
||||
|
||||
|
|
|
@ -149,6 +149,7 @@
|
|||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
register: passphrase_1
|
||||
|
||||
- name: Generate privatekey with passphrase (idempotent)
|
||||
|
@ -157,18 +158,21 @@
|
|||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
register: passphrase_2
|
||||
|
||||
- name: Regenerate privatekey without passphrase
|
||||
openssl_privatekey:
|
||||
path: '{{ output_dir }}/privatekeypw.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
register: passphrase_3
|
||||
|
||||
- name: Regenerate privatekey without passphrase (idempotent)
|
||||
openssl_privatekey:
|
||||
path: '{{ output_dir }}/privatekeypw.pem'
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
register: passphrase_4
|
||||
|
||||
- name: Regenerate privatekey with passphrase
|
||||
|
@ -177,4 +181,25 @@
|
|||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
register: passphrase_5
|
||||
|
||||
- name: Remove module
|
||||
openssl_privatekey:
|
||||
path: '{{ output_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
state: absent
|
||||
register: remove_1
|
||||
|
||||
- name: Remove module (idempotent)
|
||||
openssl_privatekey:
|
||||
path: '{{ output_dir }}/privatekeypw.pem'
|
||||
passphrase: hunter2
|
||||
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||
backup: yes
|
||||
state: absent
|
||||
register: remove_2
|
||||
|
|
|
@ -113,3 +113,16 @@
|
|||
- passphrase_3 is changed
|
||||
- passphrase_4 is not changed
|
||||
- passphrase_5 is changed
|
||||
- passphrase_1.backup_file is undefined
|
||||
- passphrase_2.backup_file is undefined
|
||||
- passphrase_3.backup_file is not none
|
||||
- passphrase_4.backup_file is undefined
|
||||
- passphrase_5.backup_file is not none
|
||||
|
||||
- name: Validate remove
|
||||
assert:
|
||||
that:
|
||||
- remove_1 is changed
|
||||
- remove_2 is not changed
|
||||
- remove_1.backup_file is not none
|
||||
- remove_2.backup_file is undefined
|
||||
|
|
Loading…
Reference in a new issue