openssh_keypair and openssl_privatekey: add regenerate option (#67038)
* Add regenerate option to openssh_keypair and openssl_privatekey. * Add changelog.
This commit is contained in:
parent
55cb8c5388
commit
b1de5d43fc
7 changed files with 828 additions and 63 deletions
|
@ -0,0 +1,3 @@
|
||||||
|
minor_changes:
|
||||||
|
- "openssh_keypair - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys."
|
||||||
|
- "openssl_privatekey - the ``regenerate`` option allows to configure the module's behavior when it should or needs to regenerate private keys."
|
|
@ -63,6 +63,39 @@ options:
|
||||||
- Provides a new comment to the public key. When checking if the key is in the correct state this will be ignored.
|
- Provides a new comment to the public key. When checking if the key is in the correct state this will be ignored.
|
||||||
type: str
|
type: str
|
||||||
version_added: "2.9"
|
version_added: "2.9"
|
||||||
|
regenerate:
|
||||||
|
description:
|
||||||
|
- Allows to configure in which situations the module is allowed to regenerate private keys.
|
||||||
|
The module will always generate a new key if the destination file does not exist.
|
||||||
|
- By default, the key will be regenerated when it doesn't match the module's options,
|
||||||
|
except when the key cannot be read or the passphrase does not match. Please note that
|
||||||
|
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if C(full_idempotence)
|
||||||
|
is specified.
|
||||||
|
- If set to C(never), the module will fail if the key cannot be read or the passphrase
|
||||||
|
isn't matching, and will never regenerate an existing key.
|
||||||
|
- If set to C(fail), the module will fail if the key does not correspond to the module's
|
||||||
|
options.
|
||||||
|
- If set to C(partial_idempotence), the key will be regenerated if it does not conform to
|
||||||
|
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
||||||
|
the key is protected by an unknown passphrase, or when they key is not protected by a
|
||||||
|
passphrase, but a passphrase is specified.
|
||||||
|
- If set to C(full_idempotence), the key will be regenerated if it does not conform to the
|
||||||
|
module's options. This is also the case if the key cannot be read (broken file), the key
|
||||||
|
is protected by an unknown passphrase, or when they key is not protected by a passphrase,
|
||||||
|
but a passphrase is specified. Make sure you have a B(backup) when using this option!
|
||||||
|
- If set to C(always), the module will always regenerate the key. This is equivalent to
|
||||||
|
setting I(force) to C(yes).
|
||||||
|
- Note that adjusting the comment and the permissions can be changed without regeneration.
|
||||||
|
Therefore, even for C(never), the task can result in changed.
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- never
|
||||||
|
- fail
|
||||||
|
- partial_idempotence
|
||||||
|
- full_idempotence
|
||||||
|
- always
|
||||||
|
default: partial_idempotence
|
||||||
|
version_added: '2.10'
|
||||||
notes:
|
notes:
|
||||||
- In case the ssh key is broken or password protected, the module will fail. Set the I(force) option to C(yes) if you want to regenerate the keypair.
|
- In case the ssh key is broken or password protected, the module will fail. Set the I(force) option to C(yes) if you want to regenerate the keypair.
|
||||||
|
|
||||||
|
@ -149,6 +182,9 @@ class Keypair(object):
|
||||||
self.privatekey = None
|
self.privatekey = None
|
||||||
self.fingerprint = {}
|
self.fingerprint = {}
|
||||||
self.public_key = {}
|
self.public_key = {}
|
||||||
|
self.regenerate = module.params['regenerate']
|
||||||
|
if self.regenerate == 'always':
|
||||||
|
self.force = True
|
||||||
|
|
||||||
if self.type in ('rsa', 'rsa1'):
|
if self.type in ('rsa', 'rsa1'):
|
||||||
self.size = 4096 if self.size is None else self.size
|
self.size = 4096 if self.size is None else self.size
|
||||||
|
@ -236,13 +272,7 @@ class Keypair(object):
|
||||||
if module.set_fs_attributes_if_different(file_args, False):
|
if module.set_fs_attributes_if_different(file_args, False):
|
||||||
self.changed = True
|
self.changed = True
|
||||||
|
|
||||||
def isPrivateKeyValid(self, module, perms_required=True):
|
def _check_pass_protected_or_broken_key(self, module):
|
||||||
|
|
||||||
# check if the key is correct
|
|
||||||
def _check_state():
|
|
||||||
return os.path.exists(self.path)
|
|
||||||
|
|
||||||
def _check_pass_protected_or_broken_key():
|
|
||||||
key_state = module.run_command([module.get_bin_path('ssh-keygen', True),
|
key_state = module.run_command([module.get_bin_path('ssh-keygen', True),
|
||||||
'-P', '', '-yf', self.path], check_rc=False)
|
'-P', '', '-yf', self.path], check_rc=False)
|
||||||
if key_state[0] == 255 or 'is not a public key file' in key_state[2]:
|
if key_state[0] == 255 or 'is not a public key file' in key_state[2]:
|
||||||
|
@ -251,27 +281,41 @@ class Keypair(object):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if _check_state():
|
def isPrivateKeyValid(self, module, perms_required=True):
|
||||||
if _check_pass_protected_or_broken_key():
|
|
||||||
|
# check if the key is correct
|
||||||
|
def _check_state():
|
||||||
|
return os.path.exists(self.path)
|
||||||
|
|
||||||
|
if not _check_state():
|
||||||
|
return False
|
||||||
|
|
||||||
|
if self._check_pass_protected_or_broken_key(module):
|
||||||
|
if self.regenerate in ('full_idempotence', 'always'):
|
||||||
|
return False
|
||||||
module.fail_json(msg='Unable to read the key. The key is protected with a passphrase or broken.'
|
module.fail_json(msg='Unable to read the key. The key is protected with a passphrase or broken.'
|
||||||
' Will not proceed. To force regeneration, call the module with `force=yes`.')
|
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||||
|
' set to `full_idempotence` or `always`, or with `force=yes`.')
|
||||||
|
|
||||||
proc = module.run_command([module.get_bin_path('ssh-keygen', True), '-lf', self.path], check_rc=False)
|
proc = module.run_command([module.get_bin_path('ssh-keygen', True), '-lf', self.path], check_rc=False)
|
||||||
if not proc[0] == 0:
|
if not proc[0] == 0:
|
||||||
if os.path.isdir(self.path):
|
if os.path.isdir(self.path):
|
||||||
module.fail_json(msg='%s is a directory. Please specify a path to a file.' % (self.path))
|
module.fail_json(msg='%s is a directory. Please specify a path to a file.' % (self.path))
|
||||||
|
|
||||||
|
if self.regenerate in ('full_idempotence', 'always'):
|
||||||
return False
|
return False
|
||||||
|
module.fail_json(msg='Unable to read the key. The key is protected with a passphrase or broken.'
|
||||||
|
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||||
|
' set to `full_idempotence` or `always`, or with `force=yes`.')
|
||||||
|
|
||||||
fingerprint = proc[1].split()
|
fingerprint = proc[1].split()
|
||||||
keysize = int(fingerprint[0])
|
keysize = int(fingerprint[0])
|
||||||
keytype = fingerprint[-1][1:-1].lower()
|
keytype = fingerprint[-1][1:-1].lower()
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _check_perms(module):
|
self.fingerprint = fingerprint
|
||||||
file_args = module.load_file_common_arguments(module.params)
|
|
||||||
return not module.set_fs_attributes_if_different(file_args, False)
|
if self.regenerate == 'never':
|
||||||
|
return True
|
||||||
|
|
||||||
def _check_type():
|
def _check_type():
|
||||||
return self.type == keytype
|
return self.type == keytype
|
||||||
|
@ -279,12 +323,18 @@ class Keypair(object):
|
||||||
def _check_size():
|
def _check_size():
|
||||||
return self.size == keysize
|
return self.size == keysize
|
||||||
|
|
||||||
self.fingerprint = fingerprint
|
if not (_check_type() and _check_size()):
|
||||||
|
if self.regenerate in ('partial_idempotence', 'full_idempotence', 'always'):
|
||||||
|
return False
|
||||||
|
module.fail_json(msg='Key has wrong type and/or size.'
|
||||||
|
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||||
|
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.')
|
||||||
|
|
||||||
if not perms_required:
|
def _check_perms(module):
|
||||||
return _check_state() and _check_type() and _check_size()
|
file_args = module.load_file_common_arguments(module.params)
|
||||||
|
return not module.set_fs_attributes_if_different(file_args, False)
|
||||||
|
|
||||||
return _check_state() and _check_perms(module) and _check_type() and _check_size()
|
return not perms_required or _check_perms(module)
|
||||||
|
|
||||||
def isPublicKeyValid(self, module, perms_required=True):
|
def isPublicKeyValid(self, module, perms_required=True):
|
||||||
|
|
||||||
|
@ -299,11 +349,13 @@ class Keypair(object):
|
||||||
def _parse_pubkey(pubkey_content):
|
def _parse_pubkey(pubkey_content):
|
||||||
if pubkey_content:
|
if pubkey_content:
|
||||||
parts = pubkey_content.split(' ', 2)
|
parts = pubkey_content.split(' ', 2)
|
||||||
|
if len(parts) < 2:
|
||||||
|
return False
|
||||||
return parts[0], parts[1], '' if len(parts) <= 2 else parts[2]
|
return parts[0], parts[1], '' if len(parts) <= 2 else parts[2]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _pubkey_valid(pubkey):
|
def _pubkey_valid(pubkey):
|
||||||
if pubkey_parts:
|
if pubkey_parts and _parse_pubkey(pubkey):
|
||||||
return pubkey_parts[:2] == _parse_pubkey(pubkey)[:2]
|
return pubkey_parts[:2] == _parse_pubkey(pubkey)[:2]
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -317,19 +369,24 @@ class Keypair(object):
|
||||||
file_args['path'] = file_args['path'] + '.pub'
|
file_args['path'] = file_args['path'] + '.pub'
|
||||||
return not module.set_fs_attributes_if_different(file_args, False)
|
return not module.set_fs_attributes_if_different(file_args, False)
|
||||||
|
|
||||||
|
pubkey_parts = _parse_pubkey(_get_pubkey_content())
|
||||||
|
|
||||||
pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path])
|
pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path])
|
||||||
pubkey = pubkey[1].strip('\n')
|
pubkey = pubkey[1].strip('\n')
|
||||||
pubkey_parts = _parse_pubkey(_get_pubkey_content())
|
|
||||||
if _pubkey_valid(pubkey):
|
if _pubkey_valid(pubkey):
|
||||||
self.public_key = pubkey
|
self.public_key = pubkey
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
if not self.comment:
|
if self.comment:
|
||||||
return _pubkey_valid(pubkey)
|
if not _comment_valid():
|
||||||
|
return False
|
||||||
|
|
||||||
if not perms_required:
|
if perms_required:
|
||||||
return _pubkey_valid(pubkey) and _comment_valid()
|
if not _check_perms(module):
|
||||||
|
return False
|
||||||
|
|
||||||
return _pubkey_valid(pubkey) and _comment_valid() and _check_perms(module)
|
return True
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
# return result as a dict
|
# return result as a dict
|
||||||
|
@ -382,6 +439,11 @@ def main():
|
||||||
force=dict(type='bool', default=False),
|
force=dict(type='bool', default=False),
|
||||||
path=dict(type='path', required=True),
|
path=dict(type='path', required=True),
|
||||||
comment=dict(type='str'),
|
comment=dict(type='str'),
|
||||||
|
regenerate=dict(
|
||||||
|
type='str',
|
||||||
|
default='partial_idempotence',
|
||||||
|
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
|
||||||
|
),
|
||||||
),
|
),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
|
@ -401,7 +463,7 @@ def main():
|
||||||
|
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
result = keypair.dump()
|
result = keypair.dump()
|
||||||
result['changed'] = module.params['force'] or not keypair.isPrivateKeyValid(module) or not keypair.isPublicKeyValid(module)
|
result['changed'] = keypair.force or not keypair.isPrivateKeyValid(module) or not keypair.isPublicKeyValid(module)
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -164,6 +164,39 @@ options:
|
||||||
type: bool
|
type: bool
|
||||||
default: no
|
default: no
|
||||||
version_added: "2.10"
|
version_added: "2.10"
|
||||||
|
regenerate:
|
||||||
|
description:
|
||||||
|
- Allows to configure in which situations the module is allowed to regenerate private keys.
|
||||||
|
The module will always generate a new key if the destination file does not exist.
|
||||||
|
- By default, the key will be regenerated when it doesn't match the module's options,
|
||||||
|
except when the key cannot be read or the passphrase does not match. Please note that
|
||||||
|
this B(changed) for Ansible 2.10. For Ansible 2.9, the behavior was as if C(full_idempotence)
|
||||||
|
is specified.
|
||||||
|
- If set to C(never), the module will fail if the key cannot be read or the passphrase
|
||||||
|
isn't matching, and will never regenerate an existing key.
|
||||||
|
- If set to C(fail), the module will fail if the key does not correspond to the module's
|
||||||
|
options.
|
||||||
|
- If set to C(partial_idempotence), the key will be regenerated if it does not conform to
|
||||||
|
the module's options. The key is B(not) regenerated if it cannot be read (broken file),
|
||||||
|
the key is protected by an unknown passphrase, or when they key is not protected by a
|
||||||
|
passphrase, but a passphrase is specified.
|
||||||
|
- If set to C(full_idempotence), the key will be regenerated if it does not conform to the
|
||||||
|
module's options. This is also the case if the key cannot be read (broken file), the key
|
||||||
|
is protected by an unknown passphrase, or when they key is not protected by a passphrase,
|
||||||
|
but a passphrase is specified. Make sure you have a B(backup) when using this option!
|
||||||
|
- If set to C(always), the module will always regenerate the key. This is equivalent to
|
||||||
|
setting I(force) to C(yes).
|
||||||
|
- Note that if I(format_mismatch) is set to C(convert) and everything matches except the
|
||||||
|
format, the key will always be converted, except if I(regenerate) is set to C(always).
|
||||||
|
type: str
|
||||||
|
choices:
|
||||||
|
- never
|
||||||
|
- fail
|
||||||
|
- partial_idempotence
|
||||||
|
- full_idempotence
|
||||||
|
- always
|
||||||
|
default: full_idempotence
|
||||||
|
version_added: '2.10'
|
||||||
extends_documentation_fragment:
|
extends_documentation_fragment:
|
||||||
- files
|
- files
|
||||||
seealso:
|
seealso:
|
||||||
|
@ -321,6 +354,9 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
||||||
self.format_mismatch = module.params['format_mismatch']
|
self.format_mismatch = module.params['format_mismatch']
|
||||||
self.privatekey_bytes = None
|
self.privatekey_bytes = None
|
||||||
self.return_content = module.params['return_content']
|
self.return_content = module.params['return_content']
|
||||||
|
self.regenerate = module.params['regenerate']
|
||||||
|
if self.regenerate == 'always':
|
||||||
|
self.force = True
|
||||||
|
|
||||||
self.backup = module.params['backup']
|
self.backup = module.params['backup']
|
||||||
self.backup_file = None
|
self.backup_file = None
|
||||||
|
@ -333,6 +369,11 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
||||||
"""(Re-)Generate private key."""
|
"""(Re-)Generate private key."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _ensure_private_key_loaded(self):
|
||||||
|
"""Make sure that the private key has been loaded."""
|
||||||
|
pass
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _get_private_key_data(self):
|
def _get_private_key_data(self):
|
||||||
"""Return bytes for self.privatekey"""
|
"""Return bytes for self.privatekey"""
|
||||||
|
@ -359,6 +400,7 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
||||||
# Convert
|
# Convert
|
||||||
if self.backup:
|
if self.backup:
|
||||||
self.backup_file = module.backup_local(self.path)
|
self.backup_file = module.backup_local(self.path)
|
||||||
|
self._ensure_private_key_loaded()
|
||||||
privatekey_data = self._get_private_key_data()
|
privatekey_data = self._get_private_key_data()
|
||||||
if self.return_content:
|
if self.return_content:
|
||||||
self.privatekey_bytes = privatekey_data
|
self.privatekey_bytes = privatekey_data
|
||||||
|
@ -390,19 +432,42 @@ class PrivateKeyBase(crypto_utils.OpenSSLObject):
|
||||||
def check(self, module, perms_required=True, ignore_conversion=True):
|
def check(self, module, perms_required=True, ignore_conversion=True):
|
||||||
"""Ensure the resource is in its desired state."""
|
"""Ensure the resource is in its desired state."""
|
||||||
|
|
||||||
state_and_perms = super(PrivateKeyBase, self).check(module, perms_required)
|
state_and_perms = super(PrivateKeyBase, self).check(module, perms_required=False)
|
||||||
|
|
||||||
if not state_and_perms or not self._check_passphrase():
|
if not state_and_perms:
|
||||||
|
# key does not exist
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if not self._check_passphrase():
|
||||||
|
if self.regenerate in ('full_idempotence', 'always'):
|
||||||
|
return False
|
||||||
|
module.fail_json(msg='Unable to read the key. The key is protected with a another passphrase / no passphrase or broken.'
|
||||||
|
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||||
|
' set to `full_idempotence` or `always`, or with `force=yes`.')
|
||||||
|
|
||||||
|
if self.regenerate != 'never':
|
||||||
if not self._check_size_and_type():
|
if not self._check_size_and_type():
|
||||||
|
if self.regenerate in ('partial_idempotence', 'full_idempotence', 'always'):
|
||||||
return False
|
return False
|
||||||
|
module.fail_json(msg='Key has wrong type and/or size.'
|
||||||
|
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||||
|
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.')
|
||||||
|
|
||||||
if not self._check_format():
|
if not self._check_format():
|
||||||
if not ignore_conversion or self.format_mismatch != 'convert':
|
# During conversion step, convert if format does not match and format_mismatch == 'convert'
|
||||||
|
if not ignore_conversion and self.format_mismatch == 'convert':
|
||||||
return False
|
return False
|
||||||
|
# During generation step, regenerate if format does not match and format_mismatch == 'regenerate'
|
||||||
|
if ignore_conversion and self.format_mismatch == 'regenerate' and self.regenerate != 'never':
|
||||||
|
if not ignore_conversion or self.regenerate in ('partial_idempotence', 'full_idempotence', 'always'):
|
||||||
|
return False
|
||||||
|
module.fail_json(msg='Key has wrong format.'
|
||||||
|
' Will not proceed. To force regeneration, call the module with `generate`'
|
||||||
|
' set to `partial_idempotence`, `full_idempotence` or `always`, or with `force=yes`.'
|
||||||
|
' To convert the key, set `format_mismatch` to `convert`.')
|
||||||
|
|
||||||
return True
|
# check whether permissions are correct (in case that needs to be checked)
|
||||||
|
return not perms_required or super(PrivateKeyBase, self).check(module, perms_required=perms_required)
|
||||||
|
|
||||||
def dump(self):
|
def dump(self):
|
||||||
"""Serialize the object into a dictionary."""
|
"""Serialize the object into a dictionary."""
|
||||||
|
@ -453,6 +518,14 @@ class PrivateKeyPyOpenSSL(PrivateKeyBase):
|
||||||
except (TypeError, ValueError) as exc:
|
except (TypeError, ValueError) as exc:
|
||||||
raise PrivateKeyError(exc)
|
raise PrivateKeyError(exc)
|
||||||
|
|
||||||
|
def _ensure_private_key_loaded(self):
|
||||||
|
"""Make sure that the private key has been loaded."""
|
||||||
|
if self.privatekey is None:
|
||||||
|
try:
|
||||||
|
self.privatekey = privatekey = crypto_utils.load_privatekey(self.path, self.passphrase)
|
||||||
|
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
||||||
|
raise PrivateKeyError(exc)
|
||||||
|
|
||||||
def _get_private_key_data(self):
|
def _get_private_key_data(self):
|
||||||
"""Return bytes for self.privatekey"""
|
"""Return bytes for self.privatekey"""
|
||||||
if self.cipher and self.passphrase:
|
if self.cipher and self.passphrase:
|
||||||
|
@ -478,12 +551,8 @@ class PrivateKeyPyOpenSSL(PrivateKeyBase):
|
||||||
def _check_type(privatekey):
|
def _check_type(privatekey):
|
||||||
return self.type == privatekey.type()
|
return self.type == privatekey.type()
|
||||||
|
|
||||||
try:
|
self._ensure_private_key_loaded()
|
||||||
privatekey = crypto_utils.load_privatekey(self.path, self.passphrase)
|
return _check_size(self.privatekey) and _check_type(self.privatekey)
|
||||||
except crypto_utils.OpenSSLBadPassphraseError as exc:
|
|
||||||
raise PrivateKeyError(exc)
|
|
||||||
|
|
||||||
return _check_size(privatekey) and _check_type(privatekey)
|
|
||||||
|
|
||||||
def _check_format(self):
|
def _check_format(self):
|
||||||
# Not supported by this backend
|
# Not supported by this backend
|
||||||
|
@ -606,6 +675,11 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
except cryptography.exceptions.UnsupportedAlgorithm as dummy:
|
except cryptography.exceptions.UnsupportedAlgorithm as dummy:
|
||||||
self.module.fail_json(msg='Cryptography backend does not support the algorithm required for {0}'.format(self.type))
|
self.module.fail_json(msg='Cryptography backend does not support the algorithm required for {0}'.format(self.type))
|
||||||
|
|
||||||
|
def _ensure_private_key_loaded(self):
|
||||||
|
"""Make sure that the private key has been loaded."""
|
||||||
|
if self.privatekey is None:
|
||||||
|
self.privatekey = self._load_privatekey()
|
||||||
|
|
||||||
def _get_private_key_data(self):
|
def _get_private_key_data(self):
|
||||||
"""Return bytes for self.privatekey"""
|
"""Return bytes for self.privatekey"""
|
||||||
# Select export format and encoding
|
# Select export format and encoding
|
||||||
|
@ -697,7 +771,11 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
data = f.read()
|
data = f.read()
|
||||||
format = crypto_utils.identify_private_key_format(data)
|
format = crypto_utils.identify_private_key_format(data)
|
||||||
if format == 'raw':
|
if format == 'raw':
|
||||||
# Raw keys cannot be encrypted
|
# Raw keys cannot be encrypted. To avoid incompatibilities, we try to
|
||||||
|
# actually load the key (and return False when this fails).
|
||||||
|
self._load_privatekey()
|
||||||
|
# Loading the key succeeded. Only return True when no passphrase was
|
||||||
|
# provided.
|
||||||
return self.passphrase is None
|
return self.passphrase is None
|
||||||
else:
|
else:
|
||||||
return cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
return cryptography.hazmat.primitives.serialization.load_pem_private_key(
|
||||||
|
@ -709,27 +787,26 @@ class PrivateKeyCryptography(PrivateKeyBase):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _check_size_and_type(self):
|
def _check_size_and_type(self):
|
||||||
privatekey = self._load_privatekey()
|
self._ensure_private_key_loaded()
|
||||||
self.privatekey = privatekey
|
|
||||||
|
|
||||||
if isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
if isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey):
|
||||||
return self.type == 'RSA' and self.size == privatekey.key_size
|
return self.type == 'RSA' and self.size == self.privatekey.key_size
|
||||||
if isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
if isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey):
|
||||||
return self.type == 'DSA' and self.size == privatekey.key_size
|
return self.type == 'DSA' and self.size == self.privatekey.key_size
|
||||||
if CRYPTOGRAPHY_HAS_X25519 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey):
|
if CRYPTOGRAPHY_HAS_X25519 and isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.x25519.X25519PrivateKey):
|
||||||
return self.type == 'X25519'
|
return self.type == 'X25519'
|
||||||
if CRYPTOGRAPHY_HAS_X448 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey):
|
if CRYPTOGRAPHY_HAS_X448 and isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.x448.X448PrivateKey):
|
||||||
return self.type == 'X448'
|
return self.type == 'X448'
|
||||||
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
|
if CRYPTOGRAPHY_HAS_ED25519 and isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.ed25519.Ed25519PrivateKey):
|
||||||
return self.type == 'Ed25519'
|
return self.type == 'Ed25519'
|
||||||
if CRYPTOGRAPHY_HAS_ED448 and isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
|
if CRYPTOGRAPHY_HAS_ED448 and isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.ed448.Ed448PrivateKey):
|
||||||
return self.type == 'Ed448'
|
return self.type == 'Ed448'
|
||||||
if isinstance(privatekey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
if isinstance(self.privatekey, cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey):
|
||||||
if self.type != 'ECC':
|
if self.type != 'ECC':
|
||||||
return False
|
return False
|
||||||
if self.curve not in self.curves:
|
if self.curve not in self.curves:
|
||||||
return False
|
return False
|
||||||
return self.curves[self.curve]['verify'](privatekey)
|
return self.curves[self.curve]['verify'](self.privatekey)
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -777,6 +854,11 @@ def main():
|
||||||
format_mismatch=dict(type='str', default='regenerate', choices=['regenerate', 'convert']),
|
format_mismatch=dict(type='str', default='regenerate', choices=['regenerate', 'convert']),
|
||||||
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
select_crypto_backend=dict(type='str', choices=['auto', 'pyopenssl', 'cryptography'], default='auto'),
|
||||||
return_content=dict(type='bool', default=False),
|
return_content=dict(type='bool', default=False),
|
||||||
|
regenerate=dict(
|
||||||
|
type='str',
|
||||||
|
default='full_idempotence',
|
||||||
|
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
|
||||||
|
),
|
||||||
),
|
),
|
||||||
supports_check_mode=True,
|
supports_check_mode=True,
|
||||||
add_file_common_args=True,
|
add_file_common_args=True,
|
||||||
|
@ -837,7 +919,9 @@ def main():
|
||||||
if private_key.state == 'present':
|
if private_key.state == 'present':
|
||||||
if module.check_mode:
|
if module.check_mode:
|
||||||
result = private_key.dump()
|
result = private_key.dump()
|
||||||
result['changed'] = module.params['force'] or not private_key.check(module)
|
result['changed'] = private_key.force \
|
||||||
|
or not private_key.check(module, ignore_conversion=True) \
|
||||||
|
or not private_key.check(module, ignore_conversion=False)
|
||||||
module.exit_json(**result)
|
module.exit_json(**result)
|
||||||
|
|
||||||
private_key.generate(module)
|
private_key.generate(module)
|
||||||
|
|
|
@ -109,3 +109,267 @@
|
||||||
register: privatekey8_result_force
|
register: privatekey8_result_force
|
||||||
|
|
||||||
- import_tasks: ../tests/validate.yml
|
- import_tasks: ../tests/validate.yml
|
||||||
|
|
||||||
|
|
||||||
|
# Test regenerate option
|
||||||
|
|
||||||
|
- name: Regenerate - setup simple keys
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
- name: Regenerate - setup password protected keys
|
||||||
|
command: 'ssh-keygen -f {{ output_dir }}/regenerate-b-{{ item }} -N password'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
- name: Regenerate - setup broken keys
|
||||||
|
copy:
|
||||||
|
dest: '{{ output_dir }}/regenerate-c-{{ item.0 }}{{ item.1 }}'
|
||||||
|
content: 'broken key'
|
||||||
|
mode: '0700'
|
||||||
|
with_nested:
|
||||||
|
- "{{ regenerate_values }}"
|
||||||
|
- [ '', '.pub' ]
|
||||||
|
|
||||||
|
- name: Regenerate - modify broken keys (check mode)
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-c-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - modify broken keys
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-c-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - modify password protected keys (check mode)
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-b-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - modify password protected keys
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-b-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a passphrase or broken. Will not proceed.' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - not modify regular keys (check mode)
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is not changed
|
||||||
|
- result.results[1] is not changed
|
||||||
|
- result.results[2] is not changed
|
||||||
|
- result.results[3] is not changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - not modify regular keys
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is not changed
|
||||||
|
- result.results[1] is not changed
|
||||||
|
- result.results[2] is not changed
|
||||||
|
- result.results[3] is not changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key size (check mode)
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1048
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key size
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: rsa
|
||||||
|
size: 1048
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - redistribute keys
|
||||||
|
copy:
|
||||||
|
src: '{{ output_dir }}/regenerate-a-always{{ item.1 }}'
|
||||||
|
dest: '{{ output_dir }}/regenerate-a-{{ item.0 }}{{ item.1 }}'
|
||||||
|
remote_src: true
|
||||||
|
with_nested:
|
||||||
|
- "{{ regenerate_values }}"
|
||||||
|
- [ '', '.pub' ]
|
||||||
|
when: "item.0 != 'always'"
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key type (check mode)
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: dsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key type
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: dsa
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - redistribute keys
|
||||||
|
copy:
|
||||||
|
src: '{{ output_dir }}/regenerate-a-always{{ item.1 }}'
|
||||||
|
dest: '{{ output_dir }}/regenerate-a-{{ item.0 }}{{ item.1 }}'
|
||||||
|
remote_src: true
|
||||||
|
with_nested:
|
||||||
|
- "{{ regenerate_values }}"
|
||||||
|
- [ '', '.pub' ]
|
||||||
|
when: "item.0 != 'always'"
|
||||||
|
|
||||||
|
- name: Regenerate - adjust comment (check mode)
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: dsa
|
||||||
|
size: 1024
|
||||||
|
comment: test comment
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust comment
|
||||||
|
openssh_keypair:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}'
|
||||||
|
type: dsa
|
||||||
|
size: 1024
|
||||||
|
comment: test comment
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result is changed
|
||||||
|
# for all values but 'always', the key should have not been regenerated.
|
||||||
|
# verify this by comparing fingerprints:
|
||||||
|
- result.results[0].fingerprint == result.results[1].fingerprint
|
||||||
|
- result.results[0].fingerprint == result.results[2].fingerprint
|
||||||
|
- result.results[0].fingerprint == result.results[3].fingerprint
|
||||||
|
- result.results[0].fingerprint != result.results[4].fingerprint
|
||||||
|
|
7
test/integration/targets/openssh_keypair/vars/main.yml
Normal file
7
test/integration/targets/openssh_keypair/vars/main.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
regenerate_values:
|
||||||
|
- never
|
||||||
|
- fail
|
||||||
|
- partial_idempotence
|
||||||
|
- full_idempotence
|
||||||
|
- always
|
|
@ -465,3 +465,341 @@
|
||||||
- privatekey_fmt_2_step_6.privatekey == lookup('file', output_dir ~ '/privatekey_fmt_2.pem', rstrip=False)
|
- privatekey_fmt_2_step_6.privatekey == lookup('file', output_dir ~ '/privatekey_fmt_2.pem', rstrip=False)
|
||||||
|
|
||||||
when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=")'
|
when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=")'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Test regenerate option
|
||||||
|
|
||||||
|
- name: Regenerate - setup simple keys
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
- name: Regenerate - setup password protected keys
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-b-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
passphrase: hunter2
|
||||||
|
cipher: "{{ 'aes256' if select_crypto_backend == 'pyopenssl' else 'auto' }}"
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
- name: Regenerate - setup broken keys
|
||||||
|
copy:
|
||||||
|
dest: '{{ output_dir }}/regenerate-c-{{ item }}.pem'
|
||||||
|
content: 'broken key'
|
||||||
|
mode: '0700'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
|
||||||
|
- name: Regenerate - modify broken keys (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-c-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg or 'Cannot load raw key' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg or 'Cannot load raw key' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg or 'Cannot load raw key' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - modify broken keys
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-c-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg or 'Cannot load raw key' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg or 'Cannot load raw key' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg or 'Cannot load raw key' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - modify password protected keys (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-b-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - modify password protected keys
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-b-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[0].msg"
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is failed
|
||||||
|
- "'Unable to read the key. The key is protected with a another passphrase / no passphrase or broken. Will not proceed.' in result.results[2].msg"
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - not modify regular keys (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is not changed
|
||||||
|
- result.results[1] is not changed
|
||||||
|
- result.results[2] is not changed
|
||||||
|
- result.results[3] is not changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - not modify regular keys
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is not changed
|
||||||
|
- result.results[1] is not changed
|
||||||
|
- result.results[2] is not changed
|
||||||
|
- result.results[3] is not changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key size (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1048
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key size
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: RSA
|
||||||
|
size: 1048
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - redistribute keys
|
||||||
|
copy:
|
||||||
|
src: '{{ output_dir }}/regenerate-a-always.pem'
|
||||||
|
dest: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
remote_src: true
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
when: "item != 'always'"
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key type (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: DSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - adjust key type
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: DSA
|
||||||
|
size: 1024
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong type and/or size. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- block:
|
||||||
|
- name: Regenerate - redistribute keys
|
||||||
|
copy:
|
||||||
|
src: '{{ output_dir }}/regenerate-a-always.pem'
|
||||||
|
dest: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
remote_src: true
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
when: "item != 'always'"
|
||||||
|
|
||||||
|
- name: Regenerate - format mismatch (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: DSA
|
||||||
|
size: 1024
|
||||||
|
format: pkcs8
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong format. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - format mismatch
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: DSA
|
||||||
|
size: 1024
|
||||||
|
format: pkcs8
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
ignore_errors: yes
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is success and result.results[0] is not changed
|
||||||
|
- result.results[1] is failed
|
||||||
|
- "'Key has wrong format. Will not proceed.' in result.results[1].msg"
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - redistribute keys
|
||||||
|
copy:
|
||||||
|
src: '{{ output_dir }}/regenerate-a-always.pem'
|
||||||
|
dest: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
remote_src: true
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
when: "item != 'always'"
|
||||||
|
|
||||||
|
- name: Regenerate - convert format (check mode)
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: DSA
|
||||||
|
size: 1024
|
||||||
|
format: pkcs1
|
||||||
|
format_mismatch: convert
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
check_mode: yes
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is changed
|
||||||
|
- result.results[1] is changed
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
|
||||||
|
- name: Regenerate - convert format
|
||||||
|
openssl_privatekey:
|
||||||
|
path: '{{ output_dir }}/regenerate-a-{{ item }}.pem'
|
||||||
|
type: DSA
|
||||||
|
size: 1024
|
||||||
|
format: pkcs1
|
||||||
|
format_mismatch: convert
|
||||||
|
regenerate: '{{ item }}'
|
||||||
|
select_crypto_backend: '{{ select_crypto_backend }}'
|
||||||
|
loop: "{{ regenerate_values }}"
|
||||||
|
register: result
|
||||||
|
- assert:
|
||||||
|
that:
|
||||||
|
- result.results[0] is changed
|
||||||
|
- result.results[1] is changed
|
||||||
|
- result.results[2] is changed
|
||||||
|
- result.results[3] is changed
|
||||||
|
- result.results[4] is changed
|
||||||
|
# for all values but 'always', the key should have not been regenerated.
|
||||||
|
# verify this by comparing fingerprints:
|
||||||
|
- result.results[0].fingerprint == result.results[1].fingerprint
|
||||||
|
- result.results[0].fingerprint == result.results[2].fingerprint
|
||||||
|
- result.results[0].fingerprint == result.results[3].fingerprint
|
||||||
|
- result.results[0].fingerprint != result.results[4].fingerprint
|
||||||
|
when: 'select_crypto_backend == "cryptography" and cryptography_version.stdout is version("2.6", ">=")'
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
regenerate_values:
|
||||||
|
- never
|
||||||
|
- fail
|
||||||
|
- partial_idempotence
|
||||||
|
- full_idempotence
|
||||||
|
- always
|
Loading…
Reference in a new issue