openssh_keypair: Adding passphrase parameter (#225)

* Integrating openssh module utils with openssh_keypair

* Added explicit PEM formatting for OpenSSH < 7.8

* Adding changelog fragment

* Adding OpenSSL/cryptography dependency for integration tests

* Adding private_key_format option and removing forced cryptography update for CI

* Fixed version check for bcrypt and key_format option name

* Setting no_log=False for private_key_format

* Docs correction and simplification of control flow for private_key_format
pull/229/head
Ajpantuso 2021-05-10 08:47:01 -04:00 committed by GitHub
parent 37c1540ff4
commit 6100d9b4df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 395 additions and 69 deletions

View File

@ -0,0 +1,2 @@
minor_changes:
- openssh_keypair - Added ``passphrase`` paramter to openssh_keypair for encrypting/decrtypting OpenSSH private keys (https://github.com/ansible-collections/community.crypto/pull/225).

View File

@ -19,24 +19,23 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
from base64 import b64encode, b64decode
from distutils.version import LooseVersion
from getpass import getuser
from socket import gethostname
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import (
HAS_CRYPTOGRAPHY,
CRYPTOGRAPHY_HAS_ED25519,
)
try:
import cryptography as c
from cryptography.exceptions import InvalidSignature, UnsupportedAlgorithm
from cryptography.hazmat.backends.openssl import backend
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa, padding
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
except ImportError:
pass
if HAS_CRYPTOGRAPHY and CRYPTOGRAPHY_HAS_ED25519:
if LooseVersion(c.__version__) >= LooseVersion("3.0"):
HAS_OPENSSH_PRIVATE_FORMAT = True
else:
HAS_OPENSSH_PRIVATE_FORMAT = False
HAS_OPENSSH_SUPPORT = True
_ALGORITHM_PARAMETERS = {
@ -76,7 +75,8 @@ if HAS_CRYPTOGRAPHY and CRYPTOGRAPHY_HAS_ED25519:
}
}
}
else:
except ImportError:
HAS_OPENSSH_PRIVATE_FORMAT = False
HAS_OPENSSH_SUPPORT = False
_ALGORITHM_PARAMETERS = {}
@ -192,12 +192,14 @@ class Asymmetric_Keypair(object):
)
@classmethod
def load(cls, path, passphrase=None, key_format='PEM'):
def load(cls, path, passphrase=None, private_key_format='PEM', public_key_format='PEM', no_public_key=False):
"""Returns an Asymmetric_Keypair object loaded from the supplied file path
:path: A path to an existing private key to be loaded
:passphrase: Secret of type bytes used to decrypt the private key being loaded
:key_format: Format of key files to be loaded
:private_key_format: Format of private key to be loaded
:public_key_format: Format of public key to be loaded
:no_public_key: Set 'True' to only load a private key and automatically populate the matching public key
"""
if passphrase:
@ -205,8 +207,11 @@ class Asymmetric_Keypair(object):
else:
encryption_algorithm = serialization.NoEncryption()
privatekey = load_privatekey(path, passphrase, key_format)
publickey = load_publickey(path + '.pub', key_format)
privatekey = load_privatekey(path, passphrase, private_key_format)
if no_public_key:
publickey = privatekey.public_key()
else:
publickey = load_publickey(path + '.pub', public_key_format)
# Ed25519 keys are always of size 256 and do not have a key_size attribute
if isinstance(privatekey, Ed25519PrivateKey):
@ -352,11 +357,11 @@ class OpenSSH_Keypair(object):
:comment: Comment for a newly generated OpenSSH public key
"""
if not comment:
if comment is None:
comment = "%s@%s" % (getuser(), gethostname())
asym_keypair = Asymmetric_Keypair.generate(keytype, size, passphrase)
openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair)
openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair, 'SSH')
openssh_publickey = cls.encode_openssh_publickey(asym_keypair, comment)
fingerprint = calculate_fingerprint(openssh_publickey)
@ -365,20 +370,21 @@ class OpenSSH_Keypair(object):
openssh_privatekey=openssh_privatekey,
openssh_publickey=openssh_publickey,
fingerprint=fingerprint,
comment=comment
comment=comment,
)
@classmethod
def load(cls, path, passphrase=None):
def load(cls, path, passphrase=None, no_public_key=False):
"""Returns an Openssh_Keypair object loaded from the supplied file path
:path: A path to an existing private key to be loaded
:passphrase: Secret used to decrypt the private key being loaded
:no_public_key: Set 'True' to only load a private key and automatically populate the matching public key
"""
comment = extract_comment(path + '.pub')
asym_keypair = Asymmetric_Keypair.load(path, passphrase, 'SSH')
openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair)
asym_keypair = Asymmetric_Keypair.load(path, passphrase, 'SSH', 'SSH', no_public_key)
openssh_privatekey = cls.encode_openssh_privatekey(asym_keypair, 'SSH')
openssh_publickey = cls.encode_openssh_publickey(asym_keypair, comment)
fingerprint = calculate_fingerprint(openssh_publickey)
@ -387,21 +393,31 @@ class OpenSSH_Keypair(object):
openssh_privatekey=openssh_privatekey,
openssh_publickey=openssh_publickey,
fingerprint=fingerprint,
comment=comment
comment=comment,
)
@staticmethod
def encode_openssh_privatekey(asym_keypair):
def encode_openssh_privatekey(asym_keypair, key_format):
"""Returns an OpenSSH encoded private key for a given keypair
:asym_keypair: Asymmetric_Keypair from the private key is extracted
:key_format: Format of the encoded private key.
"""
# OpenSSH formatted private keys are not available in Cryptography <3.0
try:
privatekey_format = serialization.PrivateFormat.OpenSSH
except AttributeError:
if key_format == 'SSH':
# Default to PEM format if SSH not available
if not HAS_OPENSSH_PRIVATE_FORMAT:
privatekey_format = serialization.PrivateFormat.PKCS8
else:
privatekey_format = serialization.PrivateFormat.OpenSSH
elif key_format == 'PKCS8':
privatekey_format = serialization.PrivateFormat.PKCS8
elif key_format == 'PKCS1':
if asym_keypair.key_type == 'ed25519':
raise InvalidKeyFormatError("ed25519 keys cannot be represented in PKCS1 format")
privatekey_format = serialization.PrivateFormat.TraditionalOpenSSL
else:
raise InvalidKeyFormatError("The accepted private key formats are SSH, PKCS8, and PKCS1")
encoded_privatekey = asym_keypair.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
@ -425,7 +441,7 @@ class OpenSSH_Keypair(object):
validate_comment(comment)
encoded_publickey += (" %s" % comment).encode(encoding=_TEXT_ENCODING)
encoded_publickey += (" %s" % comment).encode(encoding=_TEXT_ENCODING) if comment else b''
return encoded_publickey
@ -502,7 +518,7 @@ class OpenSSH_Keypair(object):
validate_comment(comment)
self.__comment = comment
encoded_comment = (" %s" % self.__comment).encode(encoding=_TEXT_ENCODING)
encoded_comment = (" %s" % self.__comment).encode(encoding=_TEXT_ENCODING) if self.__comment else b''
self.__openssh_publickey = b' '.join(self.__openssh_publickey.split(b' ', 2)[:2]) + encoded_comment
return self.__openssh_publickey
@ -513,7 +529,7 @@ class OpenSSH_Keypair(object):
"""
self.__asym_keypair.update_passphrase(passphrase)
self.__openssh_privatekey = OpenSSH_Keypair.encode_openssh_privatekey(self.__asym_keypair)
self.__openssh_privatekey = OpenSSH_Keypair.encode_openssh_privatekey(self.__asym_keypair, 'SSH')
def load_privatekey(path, passphrase, key_format):
@ -549,6 +565,21 @@ def load_privatekey(path, passphrase, key_format):
)
except ValueError as e:
# Revert to PEM if key could not be loaded in SSH format
if key_format == 'SSH':
try:
privatekey = privatekey_loaders['PEM'](
data=content,
password=passphrase,
backend=backend,
)
except ValueError as e:
raise InvalidPrivateKeyFileError(e)
except TypeError as e:
raise InvalidPassphraseError(e)
except UnsupportedAlgorithm as e:
raise InvalidAlgorithmError(e)
else:
raise InvalidPrivateKeyFileError(e)
except TypeError as e:
raise InvalidPassphraseError(e)
@ -645,4 +676,4 @@ def calculate_fingerprint(openssh_publickey):
decoded_pubkey = b64decode(openssh_publickey.split(b' ')[1])
digest.update(decoded_pubkey)
return b64encode(digest.finalize()).decode(encoding=_TEXT_ENCODING).rstrip('=')
return 'SHA256:%s' % b64encode(digest.finalize()).decode(encoding=_TEXT_ENCODING).rstrip('=')

View File

@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: openssh_keypair
@ -19,6 +18,8 @@ description:
or C(ecdsa) private keys."
requirements:
- "ssh-keygen"
- cryptography >= 2.6 (if using I(passphrase) and OpenSSH < 7.8 is installed)
- cryptography >= 3.0 (if using I(passphrase) and OpenSSH >= 7.8 is installed)
options:
state:
description:
@ -55,6 +56,23 @@ options:
description:
- Provides a new comment to the public key.
type: str
passphrase:
description:
- Passphrase used to decrypt an existing private key or encrypt a newly generated private key.
- Passphrases are not supported for I(type=rsa1).
type: str
version_added: 1.7.0
private_key_format:
description:
- Used when a value for I(passphrase) is provided to select a format for the private key at the provided I(path).
- The only valid option currently is C(auto) which will match the key format of the installed OpenSSH version.
- For OpenSSH < 7.8 private keys will be in PKCS1 format except ed25519 keys which will be in OpenSSH format.
- For OpenSSH >= 7.8 all private key types will be in the OpenSSH format.
type: str
default: auto
choices:
- auto
version_added: 1.7.0
regenerate:
description:
- Allows to configure in which situations the module is allowed to regenerate private keys.
@ -101,6 +119,11 @@ EXAMPLES = '''
community.crypto.openssh_keypair:
path: /tmp/id_ssh_rsa
- name: Generate an OpenSSH keypair with the default values (4096 bits, rsa) and encrypted private key
community.crypto.openssh_keypair:
path: /tmp/id_ssh_rsa
passphrase: super_secret_password
- name: Generate an OpenSSH rsa keypair with a different size (2048 bits)
community.crypto.openssh_keypair:
path: /tmp/id_ssh_rsa
@ -153,9 +176,19 @@ comment:
import errno
import os
import stat
from distutils.version import LooseVersion
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible.module_utils._text import to_native, to_text, to_bytes
from ansible_collections.community.crypto.plugins.module_utils.crypto.openssh import parse_openssh_version
from ansible_collections.community.crypto.plugins.module_utils.openssh.cryptography_openssh import (
HAS_OPENSSH_SUPPORT,
HAS_OPENSSH_PRIVATE_FORMAT,
InvalidPassphraseError,
InvalidPrivateKeyFileError,
OpenSSH_Keypair,
)
class KeypairError(Exception):
@ -171,6 +204,7 @@ class Keypair(object):
self.size = module.params['size']
self.type = module.params['type']
self.comment = module.params['comment']
self.passphrase = module.params['passphrase']
self.changed = False
self.check_mode = module.check_mode
self.privatekey = None
@ -180,6 +214,44 @@ class Keypair(object):
if self.regenerate == 'always':
self.force = True
# The empty string is intentionally ignored so that dependency checks do not cause unnecessary failure
if self.passphrase:
if not HAS_OPENSSH_SUPPORT:
module.fail_json(
msg=missing_required_lib(
'cryptography >= 2.6',
reason="to encrypt/decrypt private keys with passphrases"
)
)
if module.params['private_key_format'] == 'auto':
ssh = module.get_bin_path('ssh', True)
proc = module.run_command([ssh, '-Vq'])
ssh_version = parse_openssh_version(proc[2].strip())
self.private_key_format = 'SSH'
if LooseVersion(ssh_version) < LooseVersion("7.8") and self.type != 'ed25519':
# OpenSSH made SSH formatted private keys available in version 6.5,
# but still defaulted to PKCS1 format with the exception of ed25519 keys
self.private_key_format = 'PKCS1'
if self.private_key_format == 'SSH' and not HAS_OPENSSH_PRIVATE_FORMAT:
module.fail_json(
msg=missing_required_lib(
'cryptography >= 3.0',
reason="to load/dump private keys in the default OpenSSH format for OpenSSH >= 7.8 " +
"or for ed25519 keys"
)
)
if self.type == 'rsa1':
module.fail_json(msg="Passphrases are not supported for RSA1 keys.")
self.passphrase = to_bytes(self.passphrase)
else:
self.private_key_format = None
if self.type in ('rsa', 'rsa1'):
self.size = 4096 if self.size is None else self.size
if self.size < 1024:
@ -204,6 +276,12 @@ class Keypair(object):
def generate(self, module):
# generate a keypair
if self.force or not self.isPrivateKeyValid(module, perms_required=False):
try:
if os.path.exists(self.path) and not os.access(self.path, os.W_OK):
os.chmod(self.path, stat.S_IWUSR + stat.S_IRUSR)
self.changed = True
if not self.passphrase:
args = [
module.get_bin_path('ssh-keygen', True),
'-q',
@ -218,10 +296,6 @@ class Keypair(object):
else:
args.extend(['-C', ""])
try:
if os.path.exists(self.path) and not os.access(self.path, os.W_OK):
os.chmod(self.path, stat.S_IWUSR + stat.S_IRUSR)
self.changed = True
stdin_data = None
if os.path.exists(self.path):
stdin_data = 'y'
@ -230,13 +304,43 @@ class Keypair(object):
self.fingerprint = proc[1].split()
pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path])
self.public_key = pubkey[1].strip('\n')
else:
keypair = OpenSSH_Keypair.generate(
keytype=self.type,
size=self.size,
passphrase=self.passphrase,
comment=self.comment if self.comment else "",
)
with open(self.path, 'w+b') as f:
f.write(
OpenSSH_Keypair.encode_openssh_privatekey(
keypair.asymmetric_keypair,
self.private_key_format
)
)
os.chmod(self.path, stat.S_IWUSR + stat.S_IRUSR)
with open(self.path + '.pub', 'w+b') as f:
f.write(keypair.public_key)
os.chmod(self.path + ".pub", stat.S_IWUSR + stat.S_IRUSR + stat.S_IRGRP + stat.S_IROTH)
self.fingerprint = [
str(keypair.size), keypair.fingerprint, keypair.comment, "(%s)" % keypair.key_type.upper()
]
self.public_key = to_text(b' '.join(keypair.public_key.split(b' ', 2)[:2]))
except Exception as e:
self.remove()
module.fail_json(msg="%s" % to_native(e))
elif not self.isPublicKeyValid(module, perms_required=False):
if not self.passphrase:
pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path])
pubkey = pubkey[1].strip('\n')
else:
keypair = OpenSSH_Keypair.load(
path=self.path,
passphrase=self.passphrase,
no_public_key=True,
)
pubkey = to_text(keypair.public_key)
try:
self.changed = True
with open(self.path + ".pub", "w") as pubkey_f:
@ -252,9 +356,14 @@ class Keypair(object):
try:
if os.path.exists(self.path) and not os.access(self.path, os.W_OK):
os.chmod(self.path, stat.S_IWUSR + stat.S_IRUSR)
if not self.passphrase:
args = [module.get_bin_path('ssh-keygen', True),
'-q', '-o', '-c', '-C', self.comment, '-f', self.path]
module.run_command(args)
else:
keypair.comment = self.comment
with open(self.path + ".pub", "w+b") as pubkey_f:
pubkey_f.write(keypair.public_key + b'\n')
except IOError:
module.fail_json(
msg='Unable to update the comment for the public key.')
@ -269,11 +378,26 @@ class Keypair(object):
def _check_pass_protected_or_broken_key(self, module):
key_state = module.run_command([module.get_bin_path('ssh-keygen', True),
'-P', '', '-yf', self.path], check_rc=False)
if not self.passphrase:
if key_state[0] == 255 or 'is not a public key file' in key_state[2]:
return True
if 'incorrect passphrase' in key_state[2] or 'load failed' in key_state[2]:
return True
return False
else:
try:
OpenSSH_Keypair.load(
path=self.path,
passphrase=self.passphrase,
no_public_key=True,
)
except (InvalidPrivateKeyFileError, InvalidPassphraseError) as e:
return True
# Cryptography >= 3.0 uses a SSH key loader which does not raise an exception when a passphrase is provided
# when loading an unencrypted key so 'ssh-keygen' is used for this check
if key_state[0] == 0:
return True
return False
def isPrivateKeyValid(self, module, perms_required=True):
@ -365,8 +489,16 @@ class Keypair(object):
pubkey_parts = _parse_pubkey(_get_pubkey_content())
if not self.passphrase:
pubkey = module.run_command([module.get_bin_path('ssh-keygen', True), '-yf', self.path])
pubkey = pubkey[1].strip('\n')
else:
keypair = OpenSSH_Keypair.load(
path=self.path,
passphrase=self.passphrase,
no_public_key=True,
)
pubkey = to_text(keypair.public_key)
if _pubkey_valid(pubkey):
self.public_key = pubkey
else:
@ -438,6 +570,8 @@ def main():
default='partial_idempotence',
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
),
passphrase=dict(type='str', no_log=True),
private_key_format=dict(type='str', default='auto', no_log=False, choices=['auto'])
),
supports_check_mode=True,
add_file_common_args=True,

View File

@ -1,2 +1,3 @@
dependencies:
- setup_ssh_keygen
- setup_openssl

View File

@ -4,6 +4,9 @@
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Bumps up cryptography and bcrypt versions to be compatible with OpenSSH >= 7.8
- import_tasks: ./setup_bcrypt.yml
- name: Generate privatekey1 - standard (check mode)
openssh_keypair:
path: '{{ output_dir }}/privatekey1'
@ -124,7 +127,7 @@
register: privatekey7_modified_result
- name: Generate password protected key
command: 'ssh-keygen -f {{ output_dir }}/privatekey8 -N password'
command: 'ssh-keygen -f {{ output_dir }}/privatekey8 -N {{ passphrase }}'
- name: Try to modify the password protected key - should fail
openssh_keypair:
@ -140,6 +143,58 @@
size: 2048
register: privatekey8_result_force
- name: Generate another password protected key
command: 'ssh-keygen -f {{ output_dir }}/privatekey9 -N {{ passphrase }}'
- name: Try to modify the password protected key with passphrase
openssh_keypair:
path: '{{ output_dir }}/privatekey9'
size: 1024
passphrase: "{{ passphrase }}"
register: privatekey9_modified_result
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Generate another unprotected key
openssh_keypair:
path: '{{ output_dir }}/privatekey10'
size: 2048
- name: Try to Modify unprotected key with passphrase
openssh_keypair:
path: '{{ output_dir }}/privatekey10'
size: 2048
passphrase: "{{ passphrase }}"
ignore_errors: true
register: privatekey10_result
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Try to force modify the password protected key with force=true
openssh_keypair:
path: '{{ output_dir }}/privatekey10'
size: 2048
passphrase: "{{ passphrase }}"
force: true
register: privatekey10_result_force
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Ensure that ssh-keygen can read keys generated with passphrase
command: 'ssh-keygen -yf {{ output_dir }}/privatekey10 -P {{ passphrase }}'
register: privatekey10_result_sshkeygen
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Generate PEM encoded key with passphrase
command: 'ssh-keygen -f {{ output_dir }}/privatekey11 -N {{ passphrase }} -m PEM'
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Try to verify a PEM encoded key
openssh_keypair:
path: '{{ output_dir }}/privatekey11'
size: 2048
passphrase: "{{ passphrase }}"
register: privatekey11_result
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- import_tasks: ../tests/validate.yml
@ -152,8 +207,9 @@
size: 1024
loop: "{{ regenerate_values }}"
- name: Regenerate - setup password protected keys
command: 'ssh-keygen -f {{ output_dir }}/regenerate-b-{{ item }} -N password'
command: 'ssh-keygen -f {{ output_dir }}/regenerate-b-{{ item }} -N {{ passphrase }}'
loop: "{{ regenerate_values }}"
- name: Regenerate - setup broken keys
copy:
dest: '{{ output_dir }}/regenerate-c-{{ item.0 }}{{ item.1 }}'
@ -162,6 +218,10 @@
with_nested:
- "{{ regenerate_values }}"
- [ '', '.pub' ]
-
- name: Regenerate - setup password protected keys for passphrse test
command: 'ssh-keygen -f {{ output_dir }}/regenerate-d-{{ item }} -N {{ passphrase }}'
loop: "{{ regenerate_values }}"
- name: Regenerate - modify broken keys (check mode)
openssh_keypair:
@ -225,6 +285,29 @@
- result.results[3] is changed
- result.results[4] is changed
- name: Regenerate - modify password protected keys with passphrase (check mode)
openssh_keypair:
path: '{{ output_dir }}/regenerate-b-{{ item }}'
type: rsa
size: 1024
passphrase: "{{ passphrase }}"
regenerate: '{{ item }}'
check_mode: yes
loop: "{{ regenerate_values }}"
ignore_errors: yes
register: result
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- assert:
that:
- result.results[0] is success
- 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
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Regenerate - modify password protected keys
openssh_keypair:
path: '{{ output_dir }}/regenerate-b-{{ item }}'
@ -245,6 +328,28 @@
- result.results[3] is changed
- result.results[4] is changed
- name: Regenerate - modify password protected keys with passphrase
openssh_keypair:
path: '{{ output_dir }}/regenerate-d-{{ item }}'
type: rsa
size: 1024
passphrase: "{{ passphrase }}"
regenerate: '{{ item }}'
loop: "{{ regenerate_values }}"
ignore_errors: yes
register: result
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- assert:
that:
- result.results[0] is success
- 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
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')
- name: Regenerate - not modify regular keys (check mode)
openssh_keypair:
path: '{{ output_dir }}/regenerate-a-{{ item }}'

View File

@ -0,0 +1,24 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- name: Attempt to install dependencies for OpenSSH > 7.8
block:
- name: Ensure bcrypt 3.1.5 available
become: true
pip:
name: bcrypt==3.1.5
extra_args: "-c {{ remote_constraints }}"
- name: Register bcrypt version
command: "{{ ansible_python.executable }} -c 'import bcrypt; print(bcrypt.__version__)'"
register: bcrypt_version
ignore_errors: true
- name: Ensure bcrypt_version is defined
set_fact:
bcrypt_version:
stdout: 0.0
when: bcrypt_version is not defined

View File

@ -138,3 +138,31 @@
assert:
that:
- privatekey8_result_force is changed
- block:
- name: Check that password protected key with passphrase was regenerated
assert:
that:
- privatekey9_modified_result is changed
- name: Check that modifying unprotected key with passphrase fails
assert:
that:
- privatekey10_result is failed
- "'Unable to read the key. The key is protected with a passphrase or broken.' in privatekey8_result.msg"
- name: Check that unprotected key was regenerated with force=yes and passphrase supplied
assert:
that:
- privatekey10_result_force is changed
- name: Check that ssh-keygen output from passphrase protected key matches openssh_keypair
assert:
that:
- privatekey10_result_force.public_key == privatekey10_result_sshkeygen.stdout
- name: Check that PEM encoded private keys are loaded successfully
assert:
that:
- privatekey11_result is success
when: cryptography_version.stdout is version('3.0', '>=') and bcrypt_version.stdout is version('3.1.5', '>=')

View File

@ -1,4 +1,5 @@
---
passphrase: password
regenerate_values:
- never
- fail