-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c42733e
commit 1f99f57
Showing
7 changed files
with
370 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
# -*- coding: utf-8 -*- | ||
|
||
# Copyright (c) 2022, Felix Fontein <[email protected]> | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
from __future__ import absolute_import, division, print_function | ||
__metaclass__ = type | ||
|
||
DOCUMENTATION = ''' | ||
name: openssl_privatekey_info | ||
short_description: Retrieve information from OpenSSL private keys | ||
version_added: 2.10.0 | ||
author: | ||
- Felix Fontein (@felixfontein) | ||
description: | ||
- Provided an OpenSSL private keys, retrieve information. | ||
- This is a filter version of the M(community.crypto.openssl_privatekey_info) module. | ||
options: | ||
_input: | ||
description: | ||
- The content of the OpenSSL private key. | ||
type: string | ||
required: true | ||
passphrase: | ||
description: | ||
- The passphrase for the private key. | ||
type: str | ||
return_private_key_data: | ||
description: | ||
- Whether to return private key data. | ||
- Only set this to C(true) when you want private information about this key to | ||
leave the remote machine. | ||
- "B(WARNING:) you have to make sure that private key data is not accidentally logged!" | ||
type: bool | ||
default: false | ||
extends_documentation_fragment: | ||
- community.crypto.name_encoding | ||
seealso: | ||
- module: community.crypto.openssl_privatekey_info | ||
''' | ||
|
||
EXAMPLES = ''' | ||
- name: Show the Subject Alt Names of the CSR | ||
ansible.builtin.debug: | ||
msg: >- | ||
{{ | ||
( | ||
lookup('ansible.builtin.file', '/path/to/cert.csr') | ||
| community.crypto.openssl_privatekey_info | ||
).subject_alt_name | join(', ') | ||
}} | ||
''' | ||
|
||
RETURN = ''' | ||
_value: | ||
description: | ||
- Information on the certificate. | ||
type: dict | ||
contains: | ||
public_key: | ||
description: Private key's public key in PEM format. | ||
returned: success | ||
type: str | ||
sample: "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A..." | ||
public_key_fingerprints: | ||
description: | ||
- Fingerprints of private key's public key. | ||
- For every hash algorithm available, the fingerprint is computed. | ||
returned: success | ||
type: dict | ||
sample: "{'sha256': 'd4:b3:aa:6d:c8:04:ce:4e:ba:f6:29:4d:92:a3:94:b0:c2:ff:bd:bf:33:63:11:43:34:0f:51:b0:95:09:2f:63', | ||
'sha512': 'f7:07:4a:f0:b0:f0:e6:8b:95:5f:f9:e6:61:0a:32:68:f1..." | ||
type: | ||
description: | ||
- The key's type. | ||
- One of C(RSA), C(DSA), C(ECC), C(Ed25519), C(X25519), C(Ed448), or C(X448). | ||
- Will start with C(unknown) if the key type cannot be determined. | ||
returned: success | ||
type: str | ||
sample: RSA | ||
public_data: | ||
description: | ||
- Public key data. Depends on key type. | ||
returned: success | ||
type: dict | ||
contains: | ||
size: | ||
description: | ||
- Bit size of modulus (RSA) or prime number (DSA). | ||
type: int | ||
returned: When C(type=RSA) or C(type=DSA) | ||
modulus: | ||
description: | ||
- The RSA key's modulus. | ||
type: int | ||
returned: When C(type=RSA) | ||
exponent: | ||
description: | ||
- The RSA key's public exponent. | ||
type: int | ||
returned: When C(type=RSA) | ||
p: | ||
description: | ||
- The C(p) value for DSA. | ||
- This is the prime modulus upon which arithmetic takes place. | ||
type: int | ||
returned: When C(type=DSA) | ||
q: | ||
description: | ||
- The C(q) value for DSA. | ||
- This is a prime that divides C(p - 1), and at the same time the order of the subgroup of the | ||
multiplicative group of the prime field used. | ||
type: int | ||
returned: When C(type=DSA) | ||
g: | ||
description: | ||
- The C(g) value for DSA. | ||
- This is the element spanning the subgroup of the multiplicative group of the prime field used. | ||
type: int | ||
returned: When C(type=DSA) | ||
curve: | ||
description: | ||
- The curve's name for ECC. | ||
type: str | ||
returned: When C(type=ECC) | ||
exponent_size: | ||
description: | ||
- The maximum number of bits of a private key. This is basically the bit size of the subgroup used. | ||
type: int | ||
returned: When C(type=ECC) | ||
x: | ||
description: | ||
- The C(x) coordinate for the public point on the elliptic curve. | ||
type: int | ||
returned: When C(type=ECC) | ||
y: | ||
description: | ||
- For C(type=ECC), this is the C(y) coordinate for the public point on the elliptic curve. | ||
- For C(type=DSA), this is the publicly known group element whose discrete logarithm w.r.t. C(g) is the private key. | ||
type: int | ||
returned: When C(type=DSA) or C(type=ECC) | ||
private_data: | ||
description: | ||
- Private key data. Depends on key type. | ||
returned: success and when I(return_private_key_data) is set to C(true) | ||
type: dict | ||
''' | ||
|
||
from ansible.errors import AnsibleFilterError | ||
from ansible.module_utils.six import string_types | ||
from ansible.module_utils.common.text.converters import to_bytes, to_native | ||
|
||
from ansible_collections.community.crypto.plugins.module_utils.crypto.basic import ( | ||
OpenSSLObjectError, | ||
) | ||
|
||
from ansible_collections.community.crypto.plugins.module_utils.crypto.module_backends.privatekey_info import ( | ||
PrivateKeyParseError, | ||
get_privatekey_info, | ||
) | ||
|
||
from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock | ||
|
||
|
||
def openssl_privatekey_info_filter(data, passphrase=None, return_private_key_data=False): | ||
'''Extract information from X.509 PEM certificate.''' | ||
if not isinstance(data, string_types): | ||
raise AnsibleFilterError('The community.crypto.openssl_privatekey_info input must be a text type, not %s' % type(data)) | ||
if passphrase is not None and not isinstance(passphrase, string_types): | ||
raise AnsibleFilterError('The passphrase option must be a text type, not %s' % type(passphrase)) | ||
if not isinstance(return_private_key_data, bool): | ||
raise AnsibleFilterError('The return_private_key_data option must be a boolean, not %s' % type(return_private_key_data)) | ||
|
||
module = FilterModuleMock({}) | ||
try: | ||
result = get_privatekey_info(module, 'cryptography', content=to_bytes(data), passphrase=passphrase, return_private_key_data=return_private_key_data) | ||
result.pop('can_parse_key', None) | ||
result.pop('key_is_consistent', None) | ||
return result | ||
except PrivateKeyParseError as exc: | ||
raise AnsibleFilterError(exc.error_message) | ||
except OpenSSLObjectError as exc: | ||
raise AnsibleFilterError(to_native(exc)) | ||
|
||
|
||
class FilterModule(object): | ||
'''Ansible jinja2 filters''' | ||
|
||
def filters(self): | ||
return { | ||
'openssl_privatekey_info': openssl_privatekey_info_filter, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
tests/integration/targets/filter_openssl_privatekey_info/aliases
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
azp/generic/2 | ||
azp/posix/2 | ||
destructive |
9 changes: 9 additions & 0 deletions
9
tests/integration/targets/filter_openssl_privatekey_info/meta/main.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
dependencies: | ||
- setup_openssl | ||
- setup_remote_tmp_dir | ||
- prepare_jinja2_compat |
113 changes: 113 additions & 0 deletions
113
tests/integration/targets/filter_openssl_privatekey_info/tasks/impl.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
- name: Get key 1 info | ||
set_fact: | ||
result: >- | ||
{{ lookup('file', remote_tmp_dir ~ '/privatekey_1.pem') | community.crypto.openssl_privatekey_info }} | ||
- name: Check that RSA key info is ok | ||
assert: | ||
that: | ||
- "'public_key' in result" | ||
- "'public_key_fingerprints' in result" | ||
- "'type' in result" | ||
- "result.type == 'RSA'" | ||
- "'public_data' in result" | ||
- "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size" | ||
- "result.public_data.exponent > 5" | ||
- "'private_data' not in result" | ||
|
||
- name: Get key 2 info | ||
set_fact: | ||
result: >- | ||
{{ lookup('file', remote_tmp_dir ~ '/privatekey_2.pem') | community.crypto.openssl_privatekey_info(return_private_key_data=true) }} | ||
- name: Check that RSA key info is ok | ||
assert: | ||
that: | ||
- "'public_key' in result" | ||
- "'public_key_fingerprints' in result" | ||
- "'type' in result" | ||
- "result.type == 'RSA'" | ||
- "'public_data' in result" | ||
- "result.public_data.size == default_rsa_key_size" | ||
- "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size" | ||
- "result.public_data.exponent > 5" | ||
- "'private_data' in result" | ||
- "result.public_data.modulus == result.private_data.p * result.private_data.q" | ||
- "result.private_data.exponent > 5" | ||
|
||
- name: Get key 3 info (without passphrase) | ||
set_fact: | ||
result_: >- | ||
{{ lookup('file', remote_tmp_dir ~ '/privatekey_3.pem') | community.crypto.openssl_privatekey_info(return_private_key_data=true) }} | ||
ignore_errors: yes | ||
register: result | ||
|
||
- name: Check that loading passphrase protected key without passphrase failed | ||
assert: | ||
that: | ||
- result is failed | ||
- result.msg == 'Wrong or empty passphrase provided for private key' | ||
|
||
- name: Get key 3 info (with passphrase) | ||
set_fact: | ||
result: >- | ||
{{ lookup('file', remote_tmp_dir ~ '/privatekey_3.pem') | community.crypto.openssl_privatekey_info(passphrase='hunter2', return_private_key_data=true) }} | ||
- name: Check that RSA key info is ok | ||
assert: | ||
that: | ||
- "'public_key' in result" | ||
- "'public_key_fingerprints' in result" | ||
- "'type' in result" | ||
- "result.type == 'RSA'" | ||
- "'public_data' in result" | ||
- "2 ** (result.public_data.size - 1) < result.public_data.modulus < 2 ** result.public_data.size" | ||
- "result.public_data.exponent > 5" | ||
- "'private_data' in result" | ||
- "result.public_data.modulus == result.private_data.p * result.private_data.q" | ||
- "result.private_data.exponent > 5" | ||
|
||
- name: Get key 4 info | ||
set_fact: | ||
result: >- | ||
{{ lookup('file', remote_tmp_dir ~ '/privatekey_4.pem') | community.crypto.openssl_privatekey_info(return_private_key_data=true) }} | ||
- name: Check that ECC key info is ok | ||
assert: | ||
that: | ||
- "'public_key' in result" | ||
- "'public_key_fingerprints' in result" | ||
- "'type' in result" | ||
- "result.type == 'ECC'" | ||
- "'public_data' in result" | ||
- "result.public_data.curve is string" | ||
- "result.public_data.x != 0" | ||
- "result.public_data.y != 0" | ||
- "result.public_data.exponent_size == (521 if (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') else 256)" | ||
- "'private_data' in result" | ||
- "result.private_data.multiplier > 1024" | ||
|
||
- name: Get key 5 info | ||
set_fact: | ||
result: >- | ||
{{ lookup('file', remote_tmp_dir ~ '/privatekey_5.pem') | community.crypto.openssl_privatekey_info(return_private_key_data=true) }} | ||
- name: Check that DSA key info is ok | ||
assert: | ||
that: | ||
- "'public_key' in result" | ||
- "'public_key_fingerprints' in result" | ||
- "'type' in result" | ||
- "result.type == 'DSA'" | ||
- "'public_data' in result" | ||
- "result.public_data.p > 2" | ||
- "result.public_data.q > 2" | ||
- "result.public_data.g >= 2" | ||
- "result.public_data.y > 2" | ||
- "'private_data' in result" | ||
- "result.private_data.x > 2" |
43 changes: 43 additions & 0 deletions
43
tests/integration/targets/filter_openssl_privatekey_info/tasks/main.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
--- | ||
# Copyright (c) Ansible Project | ||
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) | ||
# SPDX-License-Identifier: GPL-3.0-or-later | ||
|
||
#################################################################### | ||
# WARNING: These are designed specifically for Ansible tests # | ||
# and should not be used as examples of how to write Ansible roles # | ||
#################################################################### | ||
|
||
- name: Generate privatekey 1 | ||
openssl_privatekey: | ||
path: '{{ remote_tmp_dir }}/privatekey_1.pem' | ||
|
||
- name: Generate privatekey 2 (less bits) | ||
openssl_privatekey: | ||
path: '{{ remote_tmp_dir }}/privatekey_2.pem' | ||
type: RSA | ||
size: '{{ default_rsa_key_size }}' | ||
|
||
- name: Generate privatekey 3 (with password) | ||
openssl_privatekey: | ||
path: '{{ remote_tmp_dir }}/privatekey_3.pem' | ||
passphrase: hunter2 | ||
cipher: auto | ||
size: '{{ default_rsa_key_size }}' | ||
|
||
- name: Generate privatekey 4 (ECC) | ||
openssl_privatekey: | ||
path: '{{ remote_tmp_dir }}/privatekey_4.pem' | ||
type: ECC | ||
curve: "{{ (ansible_distribution == 'CentOS' and ansible_distribution_major_version == '6') | ternary('secp521r1', 'secp256k1') }}" | ||
# ^ cryptography on CentOS6 doesn't support secp256k1, so we use secp521r1 instead | ||
|
||
- name: Generate privatekey 5 (DSA) | ||
openssl_privatekey: | ||
path: '{{ remote_tmp_dir }}/privatekey_5.pem' | ||
type: DSA | ||
size: 1024 | ||
|
||
- name: Running tests | ||
include_tasks: impl.yml | ||
when: cryptography_version.stdout is version('1.2.3', '>=') |