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 plugins to list objects in Vault #343

Merged
merged 23 commits into from Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
39acd69
read -> list
tomkivlin Jan 13, 2023
41bf98d
lookup and module for vault_list - initial tests
tomkivlin Jan 13, 2023
3831878
unit tests for list lookup/module
tomkivlin Jan 13, 2023
27ddf78
copyright - not sure if done correctly
tomkivlin Jan 13, 2023
80c7492
add new plugins to codecov.yml
tomkivlin Jan 13, 2023
859cc23
update documentation block for both plugins
tomkivlin Jan 13, 2023
db6c609
Apply suggestions from @briantist code review
tomkivlin Jan 16, 2023
5ffc8dc
add vault_list to meta/runtime.yml
tomkivlin Jan 16, 2023
cd583f9
add extra examples as per suggestion
tomkivlin Jan 16, 2023
148a3b1
new fixtures for unit tests
tomkivlin Jan 16, 2023
2a6d76d
dedup unit test for fixtures
briantist Jan 16, 2023
34c3d91
update module units with new fixtures
briantist Jan 16, 2023
3d981bb
more list lookup examples and formatting
briantist Jan 16, 2023
9d12d57
update secret path in list module examples
briantist Jan 16, 2023
d6d586f
fix policies to test inexistant path response
briantist Jan 16, 2023
8f74aa1
fix inexistant path integration tests
briantist Jan 16, 2023
86d1b86
missed variable substitution
briantist Jan 16, 2023
857568a
update paths and add comments explaining
tomkivlin Jan 17, 2023
4050a8e
correct the path for lookup plugin
tomkivlin Jan 17, 2023
1f0ea03
Update tests/integration/targets/setup_vault_configure/vars/main.yml
briantist Jan 17, 2023
c294b87
add further tests and comments
tomkivlin Jan 18, 2023
dd7c677
resolve merge conflict
tomkivlin Jan 18, 2023
2b43f4c
Apply suggestions from code review
tomkivlin Jan 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions codecov.yml
Expand Up @@ -20,6 +20,10 @@ flags:
paths:
- plugins/modules/vault_kv2_get.py

target_module_vault_list:
paths:
- plugins/modules/vault_list.py

target_module_vault_login:
paths:
- plugins/modules/vault_login.py
Expand All @@ -44,6 +48,10 @@ flags:
paths:
- plugins/lookup/vault_kv2_get.py

target_lookup_vault_list:
paths:
- plugins/lookup/vault_list.py

target_lookup_vault_login:
paths:
- plugins/lookup/vault_login.py
Expand Down
1 change: 1 addition & 0 deletions meta/runtime.yml
Expand Up @@ -6,6 +6,7 @@ action_groups:
- vault_kv1_get
- vault_kv2_delete
- vault_kv2_get
- vault_list
- vault_login
- vault_pki_generate_certificate
- vault_read
Expand Down
183 changes: 183 additions & 0 deletions plugins/lookup/vault_list.py
@@ -0,0 +1,183 @@
# (c) 2023, Tom Kivlin (@tomkivlin)
# 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: vault_list
version_added: 4.1.0
author:
- Tom Kivlin (@tomkivlin)
short_description: Perform a list operation against HashiCorp Vault
requirements:
- C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html))
- For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements).
description:
- Performs a generic list operation against a given path in HashiCorp Vault.
seealso:
- module: community.hashi_vault.vault_list
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
options:
_terms:
description: Vault path(s) to be listed.
type: str
required: true
"""

EXAMPLES = """
- name: List all secrets at a path
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_list', 'secret/metadata', url='https://vault:8201') }}"
# For kv2, the path needs to follow the pattern 'mount_point/metadata' to list all secrets in that path

- name: List access policies
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_list', 'sys/policies/acl', url='https://vault:8201') }}"

- name: Perform multiple list operations with a single Vault login
vars:
paths:
- secret/metadata
- sys/policies/acl
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_list', *paths, auth_method='userpass', username=user, password=pwd) }}"

- name: Perform multiple list operations with a single Vault login in a loop
vars:
paths:
- secret/metadata
- sys/policies/acl
ansible.builtin.debug:
msg: '{{ item }}'
loop: "{{ query('community.hashi_vault.vault_list', *paths, auth_method='userpass', username=user, password=pwd) }}"

- name: Perform list operations with a single Vault login in a loop (via with_)
vars:
ansible_hashi_vault_auth_method: userpass
ansible_hashi_vault_username: '{{ user }}'
ansible_hashi_vault_password: '{{ pwd }}'
ansible.builtin.debug:
msg: '{{ item }}'
with_community.hashi_vault.vault_list:
- secret/metadata
- sys/policies/acl

- name: Create fact consisting of list of dictionaries each with secret name (e.g. username) and value of a key (e.g. 'password') within that secret
ansible.builtin.set_fact:
credentials: >-
{{
credentials
| default([]) + [
{
'username': item,
'password': lookup('community.hashi_vault.vault_kv2_get', item, engine_mount_point='vpn-users').secret.password
}
]
}}
loop: "{{ query('community.hashi_vault.vault_list', 'vpn-users/metadata')[0].data['keys'] }}"
no_log: true

- ansible.builtin.debug:
msg: "{{ credentials }}"

- name: Create the same as above without looping, and only 2 logins
vars:
secret_names: >-
{{
query('community.hashi_vault.vault_list', 'vpn-users/metadata')
| map(attribute='data')
| map(attribute='keys')
| flatten
}}
secret_values: >-
{{
lookup('community.hashi_vault.vault_kv2_get', *secret_names, engine_mount_point='vpn-users')
| map(attribute='secret')
| map(attribute='password')
| flatten
}}
credentials_dict: "{{ dict(secret_names | zip(secret_values)) }}"
ansible.builtin.set_fact:
credentials_dict: "{{ credentials_dict }}"
credentials_list: "{{ credentials_dict | dict2items(key_name='username', value_name='password') }}"
no_log: true

- ansible.builtin.debug:
msg:
- "Dictionary: {{ credentials_dict }}"
- "List: {{ credentials_list }}"

- name: List all userpass users and output the token policies for each user
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_read', 'auth/userpass/users/' + item).data.token_policies }}"
loop: "{{ query('community.hashi_vault.vault_list', 'auth/userpass/users')[0].data['keys'] }}"
"""

RETURN = """
_raw:
description:
- The raw result of the read against the given path.
type: list
elements: dict
"""

from ansible.errors import AnsibleError
from ansible.utils.display import Display

from ansible.module_utils.six import raise_from

from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase
from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError

display = Display()

try:
import hvac
except ImportError as imp_exc:
HVAC_IMPORT_ERROR = imp_exc
else:
HVAC_IMPORT_ERROR = None


class LookupModule(HashiVaultLookupBase):
def run(self, terms, variables=None, **kwargs):
if HVAC_IMPORT_ERROR:
raise_from(
AnsibleError("This plugin requires the 'hvac' Python library"),
HVAC_IMPORT_ERROR
)

ret = []

self.set_options(direct=kwargs, var_options=variables)
# TODO: remove process_deprecations() if backported fix is available (see method definition)
self.process_deprecations()

self.connection_options.process_connection_options()
client_args = self.connection_options.get_hvac_connection_options()
client = self.helper.get_vault_client(**client_args)

try:
self.authenticator.validate()
self.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)

for term in terms:
try:
data = client.list(term)
except hvac.exceptions.Forbidden:
raise AnsibleError("Forbidden: Permission Denied to path '%s'." % term)

if data is None:
raise AnsibleError("The path '%s' doesn't seem to exist." % term)

ret.append(data)

return ret
134 changes: 134 additions & 0 deletions plugins/modules/vault_list.py
@@ -0,0 +1,134 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2023, Tom Kivlin (@tomkivlin)
# 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 = """
module: vault_list
version_added: 4.1.0
author:
- Tom Kivlin (@tomkivlin)
short_description: Perform a list operation against HashiCorp Vault
requirements:
- C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html))
- For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements).
description:
- Performs a generic list operation against a given path in HashiCorp Vault.
seealso:
- ref: community.hashi_vault.vault_list lookup <ansible_collections.community.hashi_vault.vault_list_lookup>
description: The official documentation for the C(community.hashi_vault.vault_list) lookup plugin.
extends_documentation_fragment:
- community.hashi_vault.attributes
- community.hashi_vault.attributes.action_group
- community.hashi_vault.attributes.check_mode_read_only
- community.hashi_vault.connection
- community.hashi_vault.auth
options:
path:
description: Vault path to be listed.
type: str
required: true
"""

EXAMPLES = """
- name: List kv2 secrets from Vault via the remote host with userpass auth
community.hashi_vault.vault_list:
url: https://vault:8201
path: secret/metadata
# For kv2, the path needs to follow the pattern 'mount_point/metadata' to list all secrets in that path
auth_method: userpass
username: user
password: '{{ passwd }}'
register: secret

- name: Display the secrets found at the path provided above
ansible.builtin.debug:
msg: "{{ secret.data.data['keys'] }}"
# Note that secret.data.data.keys won't work as 'keys' is a built-in method

- name: List access policies from Vault via the remote host
community.hashi_vault.vault_list:
url: https://vault:8201
path: sys/policies/acl
register: policies

- name: Display the policy names
ansible.builtin.debug:
msg: "{{ policies.data.data['keys'] }}"
# Note that secret.data.data.keys won't work as 'keys' is a built-in method
"""

RETURN = """
data:
description: The raw result of the list against the given path.
returned: success
type: dict
"""

import traceback

from ansible.module_utils._text import to_native
from ansible.module_utils.basic import missing_required_lib

from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError

try:
import hvac
except ImportError:
HAS_HVAC = False
HVAC_IMPORT_ERROR = traceback.format_exc()
else:
HVAC_IMPORT_ERROR = None
HAS_HVAC = True


def run_module():
argspec = HashiVaultModule.generate_argspec(
path=dict(type='str', required=True),
)

module = HashiVaultModule(
argument_spec=argspec,
supports_check_mode=True
)

if not HAS_HVAC:
module.fail_json(
msg=missing_required_lib('hvac'),
exception=HVAC_IMPORT_ERROR
)

path = module.params.get('path')

module.connection_options.process_connection_options()
client_args = module.connection_options.get_hvac_connection_options()
client = module.helper.get_vault_client(**client_args)

try:
module.authenticator.validate()
module.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
module.fail_json(msg=to_native(e), exception=traceback.format_exc())

try:
data = client.list(path)
except hvac.exceptions.Forbidden as e:
module.fail_json(msg="Forbidden: Permission Denied to path '%s'." % path, exception=traceback.format_exc())

if data is None:
module.fail_json(msg="The path '%s' doesn't seem to exist." % path)

module.exit_json(data=data)


def main():
run_module()


if __name__ == '__main__':
main()
1 change: 1 addition & 0 deletions tests/integration/targets/lookup_vault_list/aliases
@@ -0,0 +1 @@
# empty
4 changes: 4 additions & 0 deletions tests/integration/targets/lookup_vault_list/meta/main.yml
@@ -0,0 +1,4 @@
---
dependencies:
- setup_vault_test_plugins
- setup_vault_configure
@@ -0,0 +1,9 @@
---
- name: Configuration tasks
module_defaults:
vault_ci_token_create: '{{ vault_plugins_module_defaults_common }}'
block:
- name: 'Create a test non-root token'
vault_ci_token_create:
policies: test-policy
register: user_token_cmd