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:
Felix Fontein 2019-03-18 17:34:47 +01:00 committed by John R Barker
parent 3fa39ac818
commit e00f315358
9 changed files with 81 additions and 11 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "openssl_privatekey - add ``backup`` option."

View file

@ -230,7 +230,7 @@ class OpenSSLObject(object):
pass
def remove(self):
def remove(self, module):
"""Remove the resource from the filesystem."""
try:

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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))

View file

@ -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

View file

@ -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