feat: add private_key_format choices for openssh_keypair (#511)
* feat: add private_key_format choices for openssh_keypair * chore: add changelog fragmentpull/512/head
parent
95626abdd3
commit
1dcc135da5
|
@ -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)."
|
|
@ -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 ''
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue