Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add openssl_publickey_info filter #556

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions plugins/filter/openssl_publickey_info.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
# -*- 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_publickey_info
short_description: Retrieve information from OpenSSL public keys in PEM format
version_added: 2.10.0
author:
- Felix Fontein (@felixfontein)
description:
- Provided a public key in OpenSSL PEM format, retrieve information.
- This is a filter version of the M(community.crypto.openssl_publickey_info) module.
options:
_input:
description:
- The content of the OpenSSL PEM public key.
type: string
required: true
seealso:
- module: community.crypto.openssl_publickey_info
'''

EXAMPLES = '''
- name: Show the type of a public key
ansible.builtin.debug:
msg: >-
{{
(
lookup('ansible.builtin.file', '/path/to/public-key.pem')
| community.crypto.openssl_publickey_info
).type
}}
'''

RETURN = '''
_value:
description:
- Information on the public key.
type: dict
contains:
fingerprints:
description:
- Fingerprints of 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)
'''

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.publickey_info import (
PublicKeyParseError,
get_publickey_info,
)

from ansible_collections.community.crypto.plugins.plugin_utils.filter_module import FilterModuleMock


def openssl_publickey_info_filter(data):
'''Extract information from OpenSSL PEM public key.'''
if not isinstance(data, string_types):
raise AnsibleFilterError('The community.crypto.openssl_publickey_info input must be a text type, not %s' % type(data))

module = FilterModuleMock({})
try:
return get_publickey_info(module, 'cryptography', content=to_bytes(data))
except PublicKeyParseError 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_publickey_info': openssl_publickey_info_filter,
}
4 changes: 4 additions & 0 deletions plugins/modules/openssl_publickey_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@
seealso:
- module: community.crypto.openssl_publickey
- module: community.crypto.openssl_privatekey_info
- ref: community.crypto.openssl_publickey_info filter <ansible_collections.community.crypto.openssl_publickey_info_filter>
# - plugin: community.crypto.openssl_publickey_info
# plugin_type: filter
description: A filter variant of this module.
'''

EXAMPLES = r'''
Expand Down
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
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
---
# 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 ~ '/publickey_1.pem') | community.crypto.openssl_publickey_info }}

- name: Check that RSA key info is ok
assert:
that:
- "'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"

- name: Get key 2 info
set_fact:
result: >-
{{ lookup('file', remote_tmp_dir ~ '/publickey_2.pem') | community.crypto.openssl_publickey_info }}

- name: Check that RSA key info is ok
assert:
that:
- "'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"

- name: Get key 3 info
set_fact:
result: >-
{{ lookup('file', remote_tmp_dir ~ '/publickey_3.pem') | community.crypto.openssl_publickey_info }}

- name: Check that ECC key info is ok
assert:
that:
- "'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)"

- name: Get key 4 info
set_fact:
result: >-
{{ lookup('file', remote_tmp_dir ~ '/publickey_4.pem') | community.crypto.openssl_publickey_info }}

- name: Check that DSA key info is ok
assert:
that:
- "'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"

- name: Get invalid key info
set_fact:
result: >-
{{ [] | community.crypto.openssl_publickey_info }}
ignore_errors: true
register: output

- name: Check that task failed and error message is OK
assert:
that:
- output is failed
- output.msg is search("^The community.crypto.openssl_publickey_info input must be a text type, not <(?:class|type) 'list'>$")

- name: Get invalid key info
set_fact:
result: >-
{{ 'foo' | community.crypto.openssl_publickey_info }}
ignore_errors: true
register: output

- name: Check that task failed and error message is OK
assert:
that:
- output is failed
- 'output.msg is search("^Error while deserializing key: ")'
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
# 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 (ECC)
openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_3.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
select_crypto_backend: cryptography

- name: Generate privatekey 4 (DSA)
openssl_privatekey:
path: '{{ remote_tmp_dir }}/privatekey_4.pem'
type: DSA
size: 1024

- name: Generate public keys
openssl_publickey:
privatekey_path: '{{ remote_tmp_dir }}/privatekey_{{ item }}.pem'
path: '{{ remote_tmp_dir }}/publickey_{{ item }}.pem'
loop:
- 1
- 2
- 3
- 4

- name: Running tests
include_tasks: impl.yml
when: cryptography_version.stdout is version('1.2.3', '>=')