feat: add private_key_format choices for openssh_keypair (#511)

* feat: add private_key_format choices for openssh_keypair

* chore: add changelog fragment
pull/512/head
Andrew Pantuso 2022-09-18 20:10:29 -04:00 committed by GitHub
parent 95626abdd3
commit 1dcc135da5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 4 deletions

View File

@ -0,0 +1,4 @@
minor_changes:
- "openssh_keypair - added ``pkcs1``, ``pkcs8``, and ``ssh`` to the available choices
for the ``private_key_format`` option.
(https://github.com/ansible-collections/community.crypto/pull/511)."

View File

@ -219,10 +219,11 @@ class KeygenCommand(object):
class PrivateKey(object): class PrivateKey(object):
def __init__(self, size, key_type, fingerprint): def __init__(self, size, key_type, fingerprint, format=''):
self._size = size self._size = size
self._type = key_type self._type = key_type
self._fingerprint = fingerprint self._fingerprint = fingerprint
self._format = format
@property @property
def size(self): def size(self):
@ -236,6 +237,10 @@ class PrivateKey(object):
def fingerprint(self): def fingerprint(self):
return self._fingerprint return self._fingerprint
@property
def format(self):
return self._format
@classmethod @classmethod
def from_string(cls, string): def from_string(cls, string):
properties = string.split() properties = string.split()
@ -251,6 +256,7 @@ class PrivateKey(object):
'size': self._size, 'size': self._size,
'type': self._type, 'type': self._type,
'fingerprint': self._fingerprint, 'fingerprint': self._fingerprint,
'format': self._format,
} }
@ -324,3 +330,17 @@ class PublicKey(object):
'comment': self._comment, 'comment': self._comment,
'public_key': self._data, 'public_key': self._data,
} }
def parse_private_key_format(path):
with open(path, 'r') as file:
header = file.readline().strip()
if header == '-----BEGIN OPENSSH PRIVATE KEY-----':
return 'SSH'
elif header == '-----BEGIN PRIVATE KEY-----':
return 'PKCS8'
elif header == '-----BEGIN RSA PRIVATE KEY-----':
return 'PKCS1'
return ''

View File

@ -31,6 +31,7 @@ from ansible_collections.community.crypto.plugins.module_utils.openssh.backends.
OpensshModule, OpensshModule,
PrivateKey, PrivateKey,
PublicKey, PublicKey,
parse_private_key_format,
) )
from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import ( from ansible_collections.community.crypto.plugins.module_utils.openssh.utils import (
any_in, any_in,
@ -182,8 +183,13 @@ class KeypairBackend(OpensshModule):
return all([ return all([
self.size == self.original_private_key.size, self.size == self.original_private_key.size,
self.type == self.original_private_key.type, self.type == self.original_private_key.type,
self._private_key_valid_backend(),
]) ])
@abc.abstractmethod
def _private_key_valid_backend(self):
pass
@OpensshModule.trigger_change @OpensshModule.trigger_change
@OpensshModule.skip_if_check_mode @OpensshModule.skip_if_check_mode
def _generate(self): def _generate(self):
@ -329,6 +335,9 @@ class KeypairBackendOpensshBin(KeypairBackend):
except (IOError, OSError) as e: except (IOError, OSError) as e:
self.module.fail_json(msg=to_native(e)) self.module.fail_json(msg=to_native(e))
def _private_key_valid_backend(self):
return True
class KeypairBackendCryptography(KeypairBackend): class KeypairBackendCryptography(KeypairBackend):
def __init__(self, module): def __init__(self, module):
@ -360,6 +369,8 @@ class KeypairBackendCryptography(KeypairBackend):
"or for ed25519 keys" "or for ed25519 keys"
) )
) )
else:
result = key_format.upper()
return result return result
@ -386,6 +397,7 @@ class KeypairBackendCryptography(KeypairBackend):
size=keypair.size, size=keypair.size,
key_type=keypair.key_type, key_type=keypair.key_type,
fingerprint=keypair.fingerprint, fingerprint=keypair.fingerprint,
format=parse_private_key_format(self.private_key_path)
) )
def _get_public_key(self): def _get_public_key(self):
@ -428,6 +440,14 @@ class KeypairBackendCryptography(KeypairBackend):
except (IOError, OSError) as e: except (IOError, OSError) as e:
self.module.fail_json(msg=to_native(e)) self.module.fail_json(msg=to_native(e))
def _private_key_valid_backend(self):
# avoids breaking behavior and prevents
# automatic conversions with OpenSSH upgrades
if self.module.params['private_key_format'] == 'auto':
return True
return self.private_key_format == self.original_private_key.format
def select_backend(module, backend): def select_backend(module, backend):
can_use_cryptography = HAS_OPENSSH_SUPPORT can_use_cryptography = HAS_OPENSSH_SUPPORT

View File

@ -66,14 +66,20 @@ options:
version_added: 1.7.0 version_added: 1.7.0
private_key_format: private_key_format:
description: description:
- Used when a I(backend=cryptography) to select a format for the private key at the provided I(path). - Used when I(backend=cryptography) 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. - When set to C(auto) this module 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 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. - For OpenSSH >= 7.8 all private key types will be in the OpenSSH format.
- Using this option when I(regenerate=partial_idempotence) or I(regenerate=full_idempotence) will cause
a new keypair to be generated if the private key's format does not match the value of I(private_key_format).
This module will not however convert existing private keys between formats.
type: str type: str
default: auto default: auto
choices: choices:
- auto - auto
- pkcs1
- pkcs8
- ssh
version_added: 1.7.0 version_added: 1.7.0
backend: backend:
description: description:
@ -210,7 +216,11 @@ def main():
choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always'] choices=['never', 'fail', 'partial_idempotence', 'full_idempotence', 'always']
), ),
passphrase=dict(type='str', no_log=True), passphrase=dict(type='str', no_log=True),
private_key_format=dict(type='str', default='auto', no_log=False, choices=['auto']), private_key_format=dict(
type='str',
default='auto',
no_log=False,
choices=['auto', 'pkcs1', 'pkcs8', 'ssh']),
backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'opensshbin']) backend=dict(type='str', default='auto', choices=['auto', 'cryptography', 'opensshbin'])
), ),
supports_check_mode=True, supports_check_mode=True,

View File

@ -94,3 +94,75 @@
path: '{{ remote_tmp_dir }}/pem_encoded' path: '{{ remote_tmp_dir }}/pem_encoded'
backend: cryptography backend: cryptography
state: absent state: absent
- name: Generate a private key with specified format
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
private_key_format: pkcs1
backend: cryptography
- name: Generate a private key with specified format (Idempotent)
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
private_key_format: pkcs1
backend: cryptography
register: private_key_format_idempotent
- name: Check that private key with specified format is idempotent
assert:
that:
- private_key_format_idempotent is not changed
- name: Change to PKCS8 format
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
private_key_format: pkcs8
backend: cryptography
register: private_key_format_pkcs8
- name: Check that format change causes regeneration
assert:
that:
- private_key_format_pkcs8 is changed
- name: Change to PKCS8 format (Idempotent)
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
private_key_format: pkcs8
backend: cryptography
register: private_key_format_pkcs8_idempotent
- name: Check that private key with PKCS8 format is idempotent
assert:
that:
- private_key_format_pkcs8_idempotent is not changed
- name: Change to SSH format
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
private_key_format: ssh
backend: cryptography
register: private_key_format_ssh
- name: Check that format change causes regeneration
assert:
that:
- private_key_format_ssh is changed
- name: Change to SSH format (Idempotent)
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
private_key_format: ssh
backend: cryptography
register: private_key_format_ssh_idempotent
- name: Check that private key with SSH format is idempotent
assert:
that:
- private_key_format_ssh_idempotent is not changed
- name: Remove private key with specified format
openssh_keypair:
path: '{{ remote_tmp_dir }}/private_key_format'
backend: cryptography
state: absent