Adding in Ansible (still a WIP).

This commit is contained in:
2025-01-16 16:12:35 -05:00
parent 5495f51326
commit 7298674536
4270 changed files with 606627 additions and 0 deletions
@@ -0,0 +1,350 @@
# (c) 2020, Brian Scholer (@briantist)
# (c) 2015, Julie Davila (@juliedavila) <julie(at)davila.io>
# (c) 2017 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
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = """
name: hashi_vault
author:
- Julie Davila (@juliedavila) <julie(at)davila.io>
- Brian Scholer (@briantist)
short_description: Retrieve secrets from HashiCorp's 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:
- Retrieve secrets from HashiCorp's Vault.
- Consider R(migrating to other plugins in the collection,ansible_collections.community.hashi_vault.docsite.migration_hashi_vault_lookup).
seealso:
- ref: community.hashi_vault.hashi_vault Migration Guide <ansible_collections.community.hashi_vault.docsite.migration_hashi_vault_lookup>
description: Migrating from the C(hashi_vault) lookup.
- ref: About the community.hashi_vault.hashi_vault lookup <ansible_collections.community.hashi_vault.docsite.about_hashi_vault_lookup>
description: The past, present, and future of the C(hashi_vault) lookup.
- ref: community.hashi_vault.vault_read lookup <ansible_collections.community.hashi_vault.vault_read_lookup>
description: The official documentation for the C(community.hashi_vault.vault_read) lookup plugin.
- module: community.hashi_vault.vault_read
- ref: community.hashi_vault.vault_kv2_get lookup <ansible_collections.community.hashi_vault.vault_kv2_get_lookup>
description: The official documentation for the C(community.hashi_vault.vault_kv2_get) lookup plugin.
- module: community.hashi_vault.vault_kv2_get
- ref: community.hashi_vault.vault_kv1_get lookup <ansible_collections.community.hashi_vault.vault_kv1_get_lookup>
description: The official documentation for the C(community.hashi_vault.vault_kv1_get) lookup plugin.
- module: community.hashi_vault.vault_kv1_get
- ref: community.hashi_vault Lookup Guide <ansible_collections.community.hashi_vault.docsite.lookup_guide>
description: Guidance on using lookups in C(community.hashi_vault).
notes:
- Due to a current limitation in the HVAC library there won't necessarily be an error if a bad endpoint is specified.
- As of community.hashi_vault 0.1.0, only the latest version of a secret is returned when specifying a KV v2 path.
- As of community.hashi_vault 0.1.0, all options can be supplied via term string (space delimited key=value pairs) or by parameters (see examples).
- As of community.hashi_vault 0.1.0, when I(secret) is the first option in the term string, C(secret=) is not required (see examples).
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
options:
secret:
description: Vault path to the secret being requested in the format C(path[:field]).
required: True
return_format:
description:
- Controls how multiple key/value pairs in a path are treated on return.
- C(dict) returns a single dict containing the key/value pairs.
- C(values) returns a list of all the values only. Use when you don't care about the keys.
- C(raw) returns the actual API result (deserialized), which includes metadata and may have the data nested in other keys.
choices:
- dict
- values
- raw
default: dict
aliases: [ as ]
"""
EXAMPLES = """
- ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/hello:value token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200') }}"
- name: Return all secrets from a path
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 url=http://myvault:8200') }}"
- name: Vault that requires authentication via LDAP
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hello:value auth_method=ldap mount_point=ldap username=myuser password=mypas') }}"
- name: Vault that requires authentication via username and password
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/hola:val auth_method=userpass username=myuser password=psw url=http://vault:8200') }}"
- name: Connect to Vault using TLS
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/hola:value token=c975b780-d1be-8016-866b-01d0f9b688a5 validate_certs=False') }}"
- name: using certificate auth
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hi:val token=xxxx url=https://vault:8200 validate_certs=True cacert=/cacert/path/ca.pem') }}"
- name: Authenticate with a Vault app role
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/hello:value auth_method=approle role_id=myroleid secret_id=mysecretid') }}"
- name: Return all secrets from a path in a namespace
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/hello token=c975b780-d1be-8016-866b-01d0f9b688a5 namespace=teama/admins') }}"
# When using KV v2 the PATH should include "data" between the secret engine mount and path (e.g. "secret/data/:path")
# see: https://www.vaultproject.io/api/secret/kv/kv-v2.html#read-secret-version
- name: Return latest KV v2 secret from path
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=secret/data/hello token=my_vault_token url=http://myvault_url:8200') }}"
# The following examples show more modern syntax, with parameters specified separately from the term string.
- name: secret= is not required if secret is first
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/hello token=<token> url=http://myvault_url:8200') }}"
- name: options can be specified as parameters rather than put in term string
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/hello', token=my_token_var, url='http://myvault_url:8200') }}"
# return_format (or its alias 'as') can control how secrets are returned to you
- name: return secrets as a dict (default)
ansible.builtin.set_fact:
my_secrets: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/manysecrets', token=my_token_var, url='http://myvault_url:8200') }}"
- ansible.builtin.debug:
msg: "{{ my_secrets['secret_key'] }}"
- ansible.builtin.debug:
msg: "Secret '{{ item.key }}' has value '{{ item.value }}'"
loop: "{{ my_secrets | dict2items }}"
- name: return secrets as values only
ansible.builtin.debug:
msg: "A secret value: {{ item }}"
loop: "{{ query('community.hashi_vault.hashi_vault', 'secret/data/manysecrets', token=my_token_var, url='http://vault_url:8200', return_format='values') }}"
- name: return raw secret from API, including metadata
ansible.builtin.set_fact:
my_secret: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/hello:value', token=my_token_var, url='http://myvault_url:8200', as='raw') }}"
- ansible.builtin.debug:
msg: "This is version {{ my_secret['metadata']['version'] }} of hello:value. The secret data is {{ my_secret['data']['data']['value'] }}"
# AWS IAM authentication method
# uses Ansible standard AWS options
- name: authenticate with aws_iam
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hello:value', auth_method='aws_iam', role_id='myroleid', profile=my_boto_profile) }}"
# JWT auth
- name: Authenticate with a JWT
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hola:val', auth_method='jwt', role_id='myroleid', jwt='myjwt', url='https://vault:8200') }}"
# Disabling Token Validation
# Use this when your token does not have the lookup-self capability. Usually this is applied to all tokens via the default policy.
# However you can choose to create tokens without applying the default policy, or you can modify your default policy not to include it.
# When disabled, your invalid or expired token will be indistinguishable from insufficent permissions.
- name: authenticate without token validation
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hello:value', token=my_token, token_validate=False) }}"
# "none" auth method does no authentication and does not send a token to the Vault address.
# One example of where this could be used is with a Vault agent where the agent will handle authentication to Vault.
# https://www.vaultproject.io/docs/agent
- name: authenticate with vault agent
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hello:value', auth_method='none', url='http://127.0.0.1:8100') }}"
# Use a proxy
- name: use a proxy with login/password
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=... token=... url=https://... proxies=https://user:pass@myproxy:8080') }}"
- name: 'use a socks proxy (need some additional dependencies, see: https://requests.readthedocs.io/en/master/user/advanced/#socks )'
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret=... token=... url=https://... proxies=socks5://myproxy:1080') }}"
- name: use proxies with a dict (as param)
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', '...', proxies={'http': 'http://myproxy1', 'https': 'http://myproxy2'}) }}"
- name: use proxies with a dict (as param, pre-defined var)
vars:
prox:
http: http://myproxy1
https: https://myproxy2
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', '...', proxies=prox }}"
- name: use proxies with a dict (as direct ansible var)
vars:
ansible_hashi_vault_proxies:
http: http://myproxy1
https: https://myproxy2
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', '...' }}"
- name: use proxies with a dict (in the term string, JSON syntax)
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', '... proxies={\\"http\\":\\"http://myproxy1\\",\\"https\\":\\"http://myproxy2\\"}') }}"
- name: use ansible vars to supply some options
vars:
ansible_hashi_vault_url: 'https://myvault:8282'
ansible_hashi_vault_auth_method: token
set_fact:
secret1: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/secret1') }}"
secret2: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/secret2') }}"
- name: use a custom timeout
debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/secret1', timeout=120) }}"
- name: use a custom timeout and retry on failure 3 times (with collection retry defaults)
vars:
ansible_hashi_vault_timeout: 5
ansible_hashi_vault_retries: 3
debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/secret1') }}"
- name: retry on failure (with custom retry settings and no warnings)
vars:
ansible_hashi_vault_retries:
total: 6
backoff_factor: 0.9
status_forcelist: [500, 502]
allowed_methods:
- GET
- PUT
debug:
msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/data/secret1', retry_action='warn') }}"
"""
RETURN = """
_raw:
description:
- secrets(s) requested
type: list
elements: dict
"""
from ansible.errors import AnsibleError
from ansible.utils.display import Display
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()
HAS_HVAC = False
try:
import hvac
HAS_HVAC = True
except ImportError:
HAS_HVAC = False
class LookupModule(HashiVaultLookupBase):
def run(self, terms, variables=None, **kwargs):
if not HAS_HVAC:
raise AnsibleError("Please pip install hvac to use the hashi_vault lookup module.")
ret = []
for term in terms:
opts = kwargs.copy()
opts.update(self.parse_kev_term(term, first_unqualified='secret', plugin_name='hashi_vault'))
self.set_options(direct=opts, var_options=variables)
# TODO: remove process_deprecations() if backported fix is available (see method definition)
self.process_deprecations()
self.process_options()
client_args = self.connection_options.get_hvac_connection_options()
self.client = self.helper.get_vault_client(**client_args)
try:
self.authenticator.authenticate(self.client)
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)
ret.extend(self.get())
return ret
def process_options(self):
'''performs deep validation and value loading for options'''
# process connection options
self.connection_options.process_connection_options()
try:
self.authenticator.validate()
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)
# secret field splitter
self.field_ops()
# begin options processing methods
def field_ops(self):
# split secret and field
secret = self.get_option('secret')
s_f = secret.rsplit(':', 1)
self.set_option('secret', s_f[0])
if len(s_f) >= 2:
field = s_f[1]
else:
field = None
self._secret_field = field
def get(self):
'''gets a secret. should always return a list'''
field = self._secret_field
secret = self.get_option('secret')
return_as = self.get_option('return_format')
try:
data = self.client.read(secret)
except hvac.exceptions.Forbidden:
raise AnsibleError("Forbidden: Permission Denied to secret '%s'." % secret)
if data is None:
raise AnsibleError("The secret '%s' doesn't seem to exist." % secret)
if return_as == 'raw':
return [data]
# Check response for KV v2 fields and flatten nested secret data.
# https://vaultproject.io/api/secret/kv/kv-v2.html#sample-response-1
try:
# sentinel field checks
check_dd = data['data']['data']
check_md = data['data']['metadata']
# unwrap nested data
data = data['data']
except KeyError:
pass
if return_as == 'values':
return list(data['data'].values())
# everything after here implements return_as == 'dict'
if not field:
return [data['data']]
if field not in data['data']:
raise AnsibleError("The secret %s does not contain the field '%s'. for hashi_vault lookup" % (secret, field))
return [data['data'][field]]
@@ -0,0 +1,337 @@
# (c) 2022, Brian Scholer (@briantist)
# 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 = r'''
name: vault_ansible_settings
version_added: 2.5.0
author:
- Brian Scholer (@briantist)
short_description: Returns plugin settings (options)
description:
- Returns a dictionary of options and their values for a given plugin.
- This is most useful for using plugin settings in modules and C(module_defaults),
especially when common settings are set in C(ansible.cfg), in Ansible vars, or via environment variables on the controller.
- Options can be filtered by name, and can include or exclude defaults, unset options, and private options.
seealso:
- ref: Module defaults <module_defaults>
description: Using the C(module_defaults) keyword.
notes:
- This collection supports some "low precedence" environment variables that get loaded after all other sources, such as C(VAULT_ADDR).
- These environment variables B(are not supported) with this plugin.
- If you wish to use them, use the R(ansible.builtin.env lookup,ansible_collections.ansible.builtin.env_lookup) to
load them directly when calling a module or setting C(module_defaults).
- Similarly, any options that rely on additional processing to fill in their values will not have that done.
- For example, tokens will not be loaded from the token sink file, auth methods will not have their C(validate) methods called.
- See the examples for workarounds, but consider using Ansible-specific ways of setting these values instead.
options:
_terms:
description:
- The names of the options to load.
- Supports C(fnmatch) L(style wildcards,https://docs.python.org/3/library/fnmatch.html).
- Prepend any name or pattern with C(!) to invert the match.
type: list
elements: str
required: false
default: ['*']
plugin:
description:
- The name of the plugin whose options will be returned.
- Only lookups are supported.
- Short names (without a dot C(.)) will be fully qualified with C(community.hashi_vault).
type: str
default: community.hashi_vault.vault_login
include_private:
description: Include options that begin with underscore C(_).
type: bool
default: false
include_none:
description: Include options whose value is C(None) (this usually means they are unset).
type: bool
default: false
include_default:
description: Include options whose value comes from a default.
type: bool
default: false
'''
EXAMPLES = r'''
### In these examples, we assume an ansible.cfg like this:
# [hashi_vault_collection]
# url = https://config-based-vault.example.com
# retries = 5
### end ansible.cfg
### We assume some environment variables set as well
# ANSIBLE_HASHI_VAULT_URL: https://env-based-vault.example.com
# ANSIBLE_HASHI_VAULT_TOKEN: s.123456789
### end environment variables
# playbook - ansible-core 2.12 and higher
## set defaults for the collection group
- hosts: all
vars:
ansible_hashi_vault_auth_method: token
module_defaults:
group/community.hashi_vault.vault: "{{ lookup('community.hashi_vault.vault_ansible_settings') }}"
tasks:
- name: Get a secret from the remote host with settings from the controller
community.hashi_vault.vault_kv2_get:
path: app/some/secret
######
# playbook - ansible any version
## set defaults for a specific module
- hosts: all
vars:
ansible_hashi_vault_auth_method: token
module_defaults:
community.hashi_vault.vault_kv2_get: "{{ lookup('community.hashi_vault.vault_ansible_settings') }}"
tasks:
- name: Get a secret from the remote host with settings from the controller
community.hashi_vault.vault_kv2_get:
path: app/some/secret
######
# playbook - ansible any version
## set defaults for several modules
## do not use controller's auth
- hosts: all
vars:
ansible_hashi_vault_auth_method: aws_iam
settings: "{{ lookup('community.hashi_vault.vault_ansible_settings', '*', '!*token*') }}"
module_defaults:
community.hashi_vault.vault_kv2_get: '{{ settings }}'
community.hashi_vault.vault_kv1_get: '{{ settings }}'
tasks:
- name: Get a secret from the remote host with some settings from the controller, auth from remote
community.hashi_vault.vault_kv2_get:
path: app/some/secret
- name: Same with kv1
community.hashi_vault.vault_kv1_get:
path: app/some/secret
######
# playbook - ansible any version
## set defaults for several modules
## do not use controller's auth
## override returned settings
- hosts: all
vars:
ansible_hashi_vault_auth_method: userpass
plugin_settings: "{{ lookup('community.hashi_vault.vault_ansible_settings', '*', '!*token*') }}"
overrides:
auth_method: aws_iam
retries: '{{ (plugin_settings.retries | int) + 2 }}'
settings: >-
{{
plugin_settings
| combine(overrides)
}}
module_defaults:
community.hashi_vault.vault_kv2_get: '{{ settings }}'
community.hashi_vault.vault_kv1_get: '{{ settings }}'
tasks:
- name: Get a secret from the remote host with some settings from the controller, auth from remote
community.hashi_vault.vault_kv2_get:
path: app/some/secret
- name: Same with kv1
community.hashi_vault.vault_kv1_get:
path: app/some/secret
######
# using a block is similar
- name: Settings
vars:
ansible_hashi_vault_auth_method: aws_iam
settings: "{{ lookup('community.hashi_vault.vault_ansible_settings', '*', '!*token*') }}"
module_defaults:
community.hashi_vault.vault_kv2_get: '{{ settings }}'
community.hashi_vault.vault_kv1_get: '{{ settings }}'
block:
- name: Get a secret from the remote host with some settings from the controller, auth from remote
community.hashi_vault.vault_kv2_get:
path: app/some/secret
- name: Same with kv1
community.hashi_vault.vault_kv1_get:
path: app/some/secret
#####
# use settings from a different plugin
## when you need settings that are not in the default plugin (vault_login)
- name: Settings
vars:
ansible_hashi_vault_engine_mount_point: dept-secrets
settings: "{{ lookup('community.hashi_vault.vault_ansible_settings', plugin='community.hashi_vault.vault_kv2_get') }}"
module_defaults:
community.hashi_vault.vault_kv2_get: '{{ settings }}'
block:
- name: Get a secret from the remote host with some settings from the controller, auth from remote
community.hashi_vault.vault_kv2_get:
path: app/some/secret
#####
# use settings from a different plugin (on an indivdual call)
## short names assume community.hashi_vault
- name: Settings
vars:
ansible_hashi_vault_engine_mount_point: dept-secrets
settings: "{{ lookup('community.hashi_vault.vault_ansible_settings') }}"
module_defaults:
community.hashi_vault.vault_kv2_get: '{{ settings }}'
block:
- name: Get a secret from the remote host with some settings from the controller, auth from remote
community.hashi_vault.vault_kv2_get:
engine_mount_point: "{{ lookup('community.hashi_vault.vault_ansible_settings', plugin='vault_kv2_get') }}"
path: app/some/secret
#####
# normally, options with default values are not returned, but can be
- name: Settings
vars:
settings: "{{ lookup('community.hashi_vault.vault_ansible_settings') }}"
module_defaults:
# we usually want to use the remote host's IAM auth
community.hashi_vault.vault_kv2_get: >-
{{
settings
| combine({'auth_method': aws_iam})
}}
block:
- name: Use the plugin auth method instead, even if it is the default method
community.hashi_vault.vault_kv2_get:
auth_method: "{{ lookup('community.hashi_vault.vault_ansible_settings', 'auth_method', include_default=True) }}"
path: app/some/secret
#####
# normally, options with None/null values are not returned,
# nor are private options (names begin with underscore _),
# but they can be returned too if desired
- name: Show all plugin settings
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_ansible_settings', include_none=True, include_private=True, include_default=True) }}"
#####
# dealing with low-precedence env vars and token sink loading
## here, VAULT_ADDR is usually used with plugins, but that will not work with vault_ansible_settings.
## additionally, the CLI `vault login` is used before running Ansible, so the token sink is usually used, which also will not work.
- hosts: all
vars:
plugin_settings: "{{ lookup('community.hashi_vault.vault_ansible_settings', 'url', 'token*', include_default=True) }}"
overrides:
url: "{{ plugin_settings.url | default(lookup('ansible.builtin.env', 'VAULT_ADDR')) }}"
token: >-
{{
plugin_settings.token
| default(
lookup(
'ansible.builtin.file',
(
plugin_settings.token_path | default(lookup('ansible.builtin.env', 'HOME')),
plugin_settings.token_file
) | path_join
)
)
}}
auth_method: token
settings: >-
{{
plugin_settings
| combine(overrides)
}}
module_defaults:
community.hashi_vault.vault_kv2_get: "{{ lookup('community.hashi_vault.vault_ansible_settings') }}"
tasks:
- name: Get a secret from the remote host with settings from the controller
community.hashi_vault.vault_kv2_get:
path: app/some/secret
#####
'''
RETURN = r'''
_raw:
description:
- A dictionary of the options and their values.
- Only a single dictionary will be returned, even with multiple terms.
type: dict
sample:
retries: 5
timeout: 20
token: s.jRHAoqElnJDx6J5ExYelCDYR
url: https://vault.example.com
'''
from fnmatch import fnmatchcase
from ansible.errors import AnsibleError
from ansible.plugins.lookup import LookupBase
from ansible import constants as C
from ansible.plugins.loader import lookup_loader
from ansible.utils.display import Display
display = Display()
class LookupModule(LookupBase):
def run(self, terms, variables=None, **kwargs):
self.set_options(direct=kwargs, var_options=variables)
include_private = self.get_option('include_private')
include_none = self.get_option('include_none')
include_default = self.get_option('include_default')
plugin = self.get_option('plugin')
if '.' not in plugin:
plugin = 'community.hashi_vault.' + plugin
if not terms:
terms = ['*']
opts = {}
try:
# ansible-core 2.10 or later
p = lookup_loader.find_plugin_with_context(plugin)
loadname = p.plugin_resolved_name
resolved = p.resolved
except AttributeError:
# ansible 2.9
p = lookup_loader.find_plugin_with_name(plugin)
loadname = p[0]
resolved = loadname is not None
if not resolved:
raise AnsibleError("'%s' plugin not found." % plugin)
# Loading ensures that the options are initialized in ConfigManager
lookup_loader.get(plugin, class_only=True)
pluginget = C.config.get_configuration_definitions('lookup', loadname)
for option in pluginget.keys():
if not include_private and option.startswith('_'):
continue
keep = False
for pattern in terms:
if pattern.startswith('!'):
if keep and fnmatchcase(option, pattern[1:]):
keep = False
else:
keep = keep or fnmatchcase(option, pattern)
if not keep:
continue
value, origin = C.config.get_config_value_and_origin(option, None, 'lookup', loadname, None, variables=variables)
if (include_none or value is not None) and (include_default or origin != 'default'):
opts[option] = value
return [opts]
@@ -0,0 +1,220 @@
# (c) 2022, Brian Scholer (@briantist)
# 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 = r'''
name: vault_kv1_get
version_added: 2.5.0
author:
- Brian Scholer (@briantist)
short_description: Get a secret from HashiCorp Vault's KV version 1 secret store
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:
- Gets a secret from HashiCorp Vault's KV version 1 secret store.
seealso:
- module: community.hashi_vault.vault_kv1_get
- ref: community.hashi_vault.vault_kv2_get lookup <ansible_collections.community.hashi_vault.vault_kv2_get_lookup>
description: The official documentation for the C(community.hashi_vault.vault_kv2_get) lookup plugin.
- module: community.hashi_vault.vault_kv2_get
- ref: community.hashi_vault Lookup Guide <ansible_collections.community.hashi_vault.docsite.lookup_guide>
description: Guidance on using lookups in C(community.hashi_vault).
- name: KV1 Secrets Engine
description: Documentation for the Vault KV secrets engine, version 1.
link: https://www.vaultproject.io/docs/secrets/kv/kv-v1
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
- community.hashi_vault.engine_mount
- community.hashi_vault.engine_mount.plugins
options:
_terms:
description:
- Vault KV path(s) to be read.
- These are relative to the I(engine_mount_point), so the mount path should not be included.
type: str
required: True
engine_mount_point:
default: kv
'''
EXAMPLES = r'''
- name: Read a kv1 secret with the default mount point
ansible.builtin.set_fact:
response: "{{ lookup('community.hashi_vault.vault_kv1_get', 'hello', url='https://vault:8201') }}"
# equivalent API path is kv/hello
- name: Display the results
ansible.builtin.debug:
msg:
- "Secret: {{ response.secret }}"
- "Data: {{ response.data }} (same as secret in kv1)"
- "Metadata: {{ response.metadata }} (response info in kv1)"
- "Full response: {{ response.raw }}"
- "Value of key 'password' in the secret: {{ response.secret.password }}"
- name: Read a kv1 secret with a different mount point
ansible.builtin.set_fact:
response: "{{ lookup('community.hashi_vault.vault_kv1_get', 'hello', engine_mount_point='custom/kv1/mount', url='https://vault:8201') }}"
# equivalent API path is custom/kv1/mount/hello
- name: Display the results
ansible.builtin.debug:
msg:
- "Secret: {{ response.secret }}"
- "Data: {{ response.data }} (same as secret in kv1)"
- "Metadata: {{ response.metadata }} (response info in kv1)"
- "Full response: {{ response.raw }}"
- "Value of key 'password' in the secret: {{ response.secret.password }}"
- name: Perform multiple kv1 reads with a single Vault login, showing the secrets
vars:
paths:
- hello
- my-secret/one
- my-secret/two
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_kv1_get', *paths, auth_method='userpass', username=user, password=pwd)['secret'] }}"
- name: Perform multiple kv1 reads with a single Vault login in a loop
vars:
paths:
- hello
- my-secret/one
- my-secret/two
ansible.builtin.debug:
msg: '{{ item }}'
loop: "{{ query('community.hashi_vault.vault_kv1_get', *paths, auth_method='userpass', username=user, password=pwd) }}"
- name: Perform multiple kv1 reads with a single Vault login in a loop (via with_), display values only
vars:
ansible_hashi_vault_auth_method: userpass
ansible_hashi_vault_username: '{{ user }}'
ansible_hashi_vault_password: '{{ pwd }}'
ansible.builtin.debug:
msg: '{{ item.values() | list }}'
with_community.hashi_vault.vault_kv1_get:
- hello
- my-secret/one
- my-secret/two
'''
RETURN = r'''
_raw:
description:
- The result of the read(s) against the given path(s).
type: list
elements: dict
contains:
raw:
description: The raw result of the read against the given path.
returned: success
type: dict
sample:
auth: null
data:
Key1: value1
Key2: value2
lease_duration: 2764800
lease_id: ""
renewable: false
request_id: e99f145f-f02a-7073-1229-e3f191057a70
warnings: null
wrap_info: null
data:
description: The C(data) field of raw result. This can also be accessed via C(raw.data).
returned: success
type: dict
sample:
Key1: value1
Key2: value2
secret:
description: The C(data) field of the raw result. This is identical to C(data) in the return values.
returned: success
type: dict
sample:
Key1: value1
Key2: value2
metadata:
description: This is a synthetic result. It is the same as C(raw) with C(data) removed.
returned: success
type: dict
sample:
auth: null
lease_duration: 2764800
lease_id: ""
renewable: false
request_id: e99f145f-f02a-7073-1229-e3f191057a70
warnings: null
wrap_info: null
'''
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)
engine_mount_point = self._options_adapter.get_option('engine_mount_point')
try:
self.authenticator.validate()
self.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)
for term in terms:
try:
raw = client.secrets.kv.v1.read_secret(path=term, mount_point=engine_mount_point)
except hvac.exceptions.Forbidden as e:
raise_from(AnsibleError("Forbidden: Permission Denied to path ['%s']." % term), e)
except hvac.exceptions.InvalidPath as e:
if 'Invalid path for a versioned K/V secrets engine' in str(e):
msg = "Invalid path for a versioned K/V secrets engine ['%s']. If this is a KV version 2 path, use community.hashi_vault.vault_kv2_get."
else:
msg = "Invalid or missing path ['%s']."
raise_from(AnsibleError(msg % (term,)), e)
metadata = raw.copy()
data = metadata.pop('data')
ret.append(dict(raw=raw, data=data, secret=data, metadata=metadata))
return ret
@@ -0,0 +1,233 @@
# (c) 2022, Brian Scholer (@briantist)
# 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 = r'''
name: vault_kv2_get
version_added: 2.5.0
author:
- Brian Scholer (@briantist)
short_description: Get a secret from HashiCorp Vault's KV version 2 secret store
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:
- Gets a secret from HashiCorp Vault's KV version 2 secret store.
seealso:
- module: community.hashi_vault.vault_kv2_get
- ref: community.hashi_vault.vault_kv1_get lookup <ansible_collections.community.hashi_vault.vault_kv1_get_lookup>
description: The official documentation for the C(community.hashi_vault.vault_kv1_get) lookup plugin.
- module: community.hashi_vault.vault_kv1_get
- ref: community.hashi_vault Lookup Guide <ansible_collections.community.hashi_vault.docsite.lookup_guide>
description: Guidance on using lookups in C(community.hashi_vault).
- name: KV2 Secrets Engine
description: Documentation for the Vault KV secrets engine, version 2.
link: https://www.vaultproject.io/docs/secrets/kv/kv-v2
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
- community.hashi_vault.engine_mount
- community.hashi_vault.engine_mount.plugins
options:
_terms:
description:
- Vault KV path(s) to be read.
- These are relative to the I(engine_mount_point), so the mount path should not be included.
type: str
required: True
engine_mount_point:
default: secret
version:
description: Specifies the version to return. If not set the latest version is returned.
type: int
'''
EXAMPLES = r'''
- name: Read a kv2 secret with the default mount point
ansible.builtin.set_fact:
response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'hello', url='https://vault:8201') }}"
# equivalent API path in 3.x.x is kv/data/hello
# equivalent API path in 4.0.0+ is secret/data/hello
- name: Display the results
ansible.builtin.debug:
msg:
- "Secret: {{ response.secret }}"
- "Data: {{ response.data }} (contains secret data & metadata in kv2)"
- "Metadata: {{ response.metadata }}"
- "Full response: {{ response.raw }}"
- "Value of key 'password' in the secret: {{ response.secret.password }}"
- name: Read version 5 of a kv2 secret with a different mount point
ansible.builtin.set_fact:
response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'hello', version=5, engine_mount_point='custom/kv2/mount', url='https://vault:8201') }}"
# equivalent API path is custom/kv2/mount/data/hello
- name: Assert that the version returned is as expected
ansible.builtin.assert:
that:
- response.metadata.version == 5
- name: Perform multiple kv2 reads with a single Vault login, showing the secrets
vars:
paths:
- hello
- my-secret/one
- my-secret/two
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_kv2_get', *paths, auth_method='userpass', username=user, password=pwd)['secret'] }}"
- name: Perform multiple kv2 reads with a single Vault login in a loop
vars:
paths:
- hello
- my-secret/one
- my-secret/two
ansible.builtin.debug:
msg: '{{ item }}'
loop: "{{ query('community.hashi_vault.vault_kv2_get', *paths, auth_method='userpass', username=user, password=pwd) }}"
- name: Perform multiple kv2 reads with a single Vault login in a loop (via with_), display values only
vars:
ansible_hashi_vault_auth_method: userpass
ansible_hashi_vault_username: '{{ user }}'
ansible_hashi_vault_password: '{{ pwd }}'
ansible_hashi_vault_engine_mount_point: special/kv2
ansible.builtin.debug:
msg: '{{ item.values() | list }}'
with_community.hashi_vault.vault_kv2_get:
- hello
- my-secret/one
- my-secret/two
'''
RETURN = r'''
_raw:
description:
- The result of the read(s) against the given path(s).
type: list
elements: dict
contains:
raw:
description: The raw result of the read against the given path.
returned: success
type: dict
sample:
auth: null
data:
data:
Key1: value1
Key2: value2
metadata:
created_time: "2022-04-21T15:56:58.8525402Z"
custom_metadata: null
deletion_time: ""
destroyed: false
version: 2
lease_duration: 0
lease_id: ""
renewable: false
request_id: dc829675-9119-e831-ae74-35fc5d33d200
warnings: null
wrap_info: null
data:
description: The C(data) field of raw result. This can also be accessed via C(raw.data).
returned: success
type: dict
sample:
data:
Key1: value1
Key2: value2
metadata:
created_time: "2022-04-21T15:56:58.8525402Z"
custom_metadata: null
deletion_time: ""
destroyed: false
version: 2
secret:
description: The C(data) field within the C(data) field. Equivalent to C(raw.data.data).
returned: success
type: dict
sample:
Key1: value1
Key2: value2
metadata:
description: The C(metadata) field within the C(data) field. Equivalent to C(raw.data.metadata).
returned: success
type: dict
sample:
created_time: "2022-04-21T15:56:58.8525402Z"
custom_metadata: null
deletion_time: ""
destroyed: false
version: 2
'''
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)
version = self._options_adapter.get_option_default('version')
engine_mount_point = self._options_adapter.get_option('engine_mount_point')
try:
self.authenticator.validate()
self.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)
for term in terms:
try:
raw = client.secrets.kv.v2.read_secret_version(path=term, version=version, mount_point=engine_mount_point)
except hvac.exceptions.Forbidden as e:
raise_from(AnsibleError("Forbidden: Permission Denied to path ['%s']." % term), e)
except hvac.exceptions.InvalidPath as e:
raise_from(
AnsibleError("Invalid or missing path ['%s'] with secret version '%s'. Check the path or secret version." % (term, version or 'latest')),
e
)
data = raw['data']
metadata = data['metadata']
secret = data['data']
ret.append(dict(raw=raw, data=data, secret=secret, metadata=metadata))
return ret
@@ -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' or 'mount_point/metadata/path' 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
@@ -0,0 +1,138 @@
# (c) 2021, Brian Scholer (@briantist)
# 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_login
version_added: 2.2.0
author:
- Brian Scholer (@briantist)
short_description: Perform a login 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 login operation against a given path in HashiCorp Vault, returning the login response, including the token.
seealso:
- module: community.hashi_vault.vault_login
- ref: community.hashi_vault.vault_login_token filter <ansible_collections.community.hashi_vault.vault_login_token_filter>
description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin.
notes:
- This lookup does not use the term string and will not work correctly in loops. Only a single response will be returned.
- "A login is a write operation (creating a token persisted to storage), so this module always reports C(changed=True),
except when used with C(token) auth, because no new token is created in that case. For the purposes of Ansible playbooks however,
it may be more useful to set C(changed_when=false) if you're doing idempotency checks against the target system."
- The C(none) auth method is not valid for this plugin because there is no response to return.
- "With C(token) auth, no actual login is performed.
Instead, the given token's additional information is returned in a structure that resembles what login responses look like."
- "The C(token) auth method will only return full information if I(token_validate=True).
If the token does not have the C(lookup-self) capability, this will fail. If I(token_validate=False), only the token value itself
will be returned in the structure."
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
options:
_terms:
description: This is unused and any terms supplied will be ignored.
type: str
required: false
token_validate:
default: true
"""
EXAMPLES = """
- name: Set a fact with a lookup result
set_fact:
login_data: "{{ lookup('community.hashi_vault.vault_login', url='https://vault', auth_method='userpass', username=user, password=pwd) }}"
- name: Retrieve an approle role ID (token via filter)
community.hashi_vault.vault_read:
url: https://vault:8201
auth_method: token
token: '{{ login_data | community.hashi_vault.vault_login_token }}'
path: auth/approle/role/role-name/role-id
register: approle_id
- name: Retrieve an approle role ID (token via direct dict access)
community.hashi_vault.vault_read:
url: https://vault:8201
auth_method: token
token: '{{ login_data.auth.client_token }}'
path: auth/approle/role/role-name/role-id
register: approle_id
"""
RETURN = """
_raw:
description:
- The result of the login with the given auth method.
type: list
elements: dict
contains:
auth:
description: The C(auth) member of the login response.
returned: success
type: dict
contains:
client_token:
description: Contains the token provided by the login operation (or the input token when I(auth_method=token)).
returned: success
type: str
data:
description: The C(data) member of the login response.
returned: success, when available
type: dict
"""
from ansible.errors import AnsibleError
from ansible.utils.display import Display
from ansible.module_utils.six import raise_from
from ...plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase
from ...plugins.module_utils._hashi_vault_common import HashiVaultValueError
display = Display()
try:
import hvac # pylint: disable=unused-import
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
)
self.set_options(direct=kwargs, var_options=variables)
# TODO: remove process_deprecations() if backported fix is available (see method definition)
self.process_deprecations()
if self.get_option('auth_method') == 'none':
raise AnsibleError("The 'none' auth method is not valid for this lookup.")
self.connection_options.process_connection_options()
client_args = self.connection_options.get_hvac_connection_options()
client = self.helper.get_vault_client(**client_args)
if len(terms) != 0:
display.warning("Supplied term strings will be ignored. This lookup does not use term strings.")
try:
self.authenticator.validate()
response = self.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)
return [response]
@@ -0,0 +1,137 @@
# (c) 2021, Brian Scholer (@briantist)
# 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_read
version_added: 1.4.0
author:
- Brian Scholer (@briantist)
short_description: Perform a read 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 read operation against a given path in HashiCorp Vault.
seealso:
- module: community.hashi_vault.vault_read
- ref: community.hashi_vault.hashi_vault lookup <ansible_collections.community.hashi_vault.hashi_vault_lookup>
description: The official documentation for the C(community.hashi_vault.hashi_vault) lookup plugin.
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 read.
type: str
required: True
"""
EXAMPLES = """
- name: Read a kv2 secret
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_read', 'secret/data/hello', url='https://vault:8201') }}"
- name: Retrieve an approle role ID
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_read', 'auth/approle/role/role-name/role-id', url='https://vault:8201') }}"
- name: Perform multiple reads with a single Vault login
vars:
paths:
- secret/data/hello
- auth/approle/role/role-one/role-id
- auth/approle/role/role-two/role-id
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_read', *paths, auth_method='userpass', username=user, password=pwd) }}"
- name: Perform multiple reads with a single Vault login in a loop
vars:
paths:
- secret/data/hello
- auth/approle/role/role-one/role-id
- auth/approle/role/role-two/role-id
ansible.builtin.debug:
msg: '{{ item }}'
loop: "{{ query('community.hashi_vault.vault_read', *paths, auth_method='userpass', username=user, password=pwd) }}"
- name: Perform multiple reads 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_read:
- secret/data/hello
- auth/approle/role/role-one/role-id
- auth/approle/role/role-two/role-id
"""
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.read(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
@@ -0,0 +1,195 @@
# (c) 2022, Brian Scholer (@briantist)
# 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_token_create
version_added: 2.3.0
author:
- Brian Scholer (@briantist)
short_description: Create a HashiCorp Vault token
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:
- Creates a token in HashiCorp Vault, returning the response, including the token.
seealso:
- module: community.hashi_vault.vault_token_create
- ref: community.hashi_vault.vault_login lookup <ansible_collections.community.hashi_vault.vault_login_lookup>
description: The official documentation for the C(community.hashi_vault.vault_login) lookup plugin.
- module: community.hashi_vault.vault_login
- ref: community.hashi_vault.vault_login_token filter <ansible_collections.community.hashi_vault.vault_login_token_filter>
description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin.
notes:
- Token creation is a write operation (creating a token persisted to storage), so this module always reports C(changed=True).
- For the purposes of Ansible playbooks however,
it may be more useful to set I(changed_when=false) if you are doing idempotency checks against the target system.
- In check mode, this module will not create a token, and will instead return a basic structure with an empty token.
However, this may not be useful if the token is required for follow on tasks.
It may be better to use this module with I(check_mode=false) in order to have a valid token that can be used.
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
- community.hashi_vault.token_create
- community.hashi_vault.wrapping
- community.hashi_vault.wrapping.plugins
options:
_terms:
description: This is unused and any terms supplied will be ignored.
type: str
required: false
"""
EXAMPLES = """
- name: Login via userpass and create a child token
ansible.builtin.set_fact:
token_data: "{{ lookup('community.hashi_vault.vault_token_create', url='https://vault', auth_method='userpass', username=user, password=passwd) }}"
- name: Retrieve an approle role ID using the child token (token via filter)
community.hashi_vault.vault_read:
url: https://vault:8201
auth_method: token
token: '{{ token_data | community.hashi_vault.vault_login_token }}'
path: auth/approle/role/role-name/role-id
register: approle_id
- name: Retrieve an approle role ID (token via direct dict access)
community.hashi_vault.vault_read:
url: https://vault:8201
auth_method: token
token: '{{ token_data.auth.client_token }}'
path: auth/approle/role/role-name/role-id
register: approle_id
# implicitly uses url & token auth with a token from the environment
- name: Create an orphaned token with a short TTL and display the full response
ansible.builtin.debug:
var: lookup('community.hashi_vault.vault_token_create', orphan=True, ttl='60s')
"""
RETURN = """
_raw:
description: The result of the token creation operation.
returned: success
type: dict
sample:
auth:
client_token: s.rlwajI2bblHAWU7uPqZhLru3
data: null
contains:
auth:
description: The C(auth) member of the token response.
returned: success
type: dict
contains:
client_token:
description: Contains the newly created token.
returned: success
type: str
data:
description: The C(data) member of the token response.
returned: success, when available
type: dict
"""
from ansible.errors import AnsibleError
from ansible.utils.display import Display
from ansible.module_utils.six import raise_from
from ...plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase
from ...plugins.module_utils._hashi_vault_common import HashiVaultValueError
display = Display()
try:
import hvac # pylint: disable=unused-import
except ImportError as imp_exc:
HVAC_IMPORT_ERROR = imp_exc
else:
HVAC_IMPORT_ERROR = None
class LookupModule(HashiVaultLookupBase):
PASS_THRU_OPTION_NAMES = [
'no_parent',
'no_default_policy',
'policies',
'id',
'role_name',
'meta',
'renewable',
'ttl',
'type',
'explicit_max_ttl',
'display_name',
'num_uses',
'period',
'entity_alias',
'wrap_ttl',
]
ORPHAN_OPTION_TRANSLATION = {
'id': 'token_id',
'role_name': 'role',
'type': 'token_type',
}
def run(self, terms, variables=None, **kwargs):
if HVAC_IMPORT_ERROR:
raise_from(
AnsibleError("This plugin requires the 'hvac' Python library"),
HVAC_IMPORT_ERROR
)
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)
if len(terms) != 0:
display.warning("Supplied term strings will be ignored. This lookup does not use term strings.")
try:
self.authenticator.validate()
self.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
raise AnsibleError(e)
pass_thru_options = self._options_adapter.get_filled_options(*self.PASS_THRU_OPTION_NAMES)
orphan_options = pass_thru_options.copy()
for key in pass_thru_options.keys():
if key in self.ORPHAN_OPTION_TRANSLATION:
orphan_options[self.ORPHAN_OPTION_TRANSLATION[key]] = orphan_options.pop(key)
response = None
if self.get_option('orphan'):
try:
try:
# this method was added in hvac 1.0.0
# See: https://github.com/hvac/hvac/pull/869
response = client.auth.token.create_orphan(**orphan_options)
except AttributeError:
# this method was removed in hvac 1.0.0
# See: https://github.com/hvac/hvac/issues/758
response = client.create_token(orphan=True, **orphan_options)
except Exception as e:
raise AnsibleError(e)
else:
try:
response = client.auth.token.create(**pass_thru_options)
except Exception as e:
raise AnsibleError(e)
return [response]
@@ -0,0 +1,203 @@
# (c) 2022, Brian Scholer (@briantist)
# 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_write
version_added: 2.4.0
author:
- Brian Scholer (@briantist)
short_description: Perform a write 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 write operation against a given path in HashiCorp Vault, returning any output.
seealso:
- module: community.hashi_vault.vault_write
- module: community.hashi_vault.vault_kv2_write
- ref: community.hashi_vault.vault_read lookup <ansible_collections.community.hashi_vault.vault_read_lookup>
description: The official documentation for the C(community.hashi_vault.vault_read) lookup plugin.
- module: community.hashi_vault.vault_read
- ref: community.hashi_vault Lookup Guide <ansible_collections.community.hashi_vault.docsite.lookup_guide>
description: Guidance on using lookups in C(community.hashi_vault).
notes:
- C(vault_write) is a generic plugin to do operations that do not yet have a dedicated plugin. Where a specific plugin exists, that should be used instead.
- In the vast majority of cases, it will be better to do writes as a task, with the M(community.hashi_vault.vault_write) module.
- The lookup can be used in cases where you need a value directly in templating, but there is risk of executing the write many times unintentionally.
- The lookup is best used for endpoints that directly manipulate the input data and return a value, while not changing state in Vault.
- See the R(Lookup Guide,ansible_collections.community.hashi_vault.docsite.lookup_guide) for more information.
extends_documentation_fragment:
- community.hashi_vault.connection
- community.hashi_vault.connection.plugins
- community.hashi_vault.auth
- community.hashi_vault.auth.plugins
- community.hashi_vault.wrapping
- community.hashi_vault.wrapping.plugins
options:
_terms:
description: Vault path(s) to be written to.
type: str
required: true
data:
description:
- A dictionary to be serialized to JSON and then sent as the request body.
- If the dictionary contains keys named C(path) or C(wrap_ttl), the call will fail with C(hvac<1.2).
type: dict
required: false
default: {}
"""
EXAMPLES = """
# These examples show some uses that might work well as a lookup.
# For most uses, the vault_write module should be used.
- name: Retrieve and display random data
vars:
data:
format: hex
num_bytes: 64
ansible.builtin.debug:
msg: "{{ lookup('community.hashi_vault.vault_write', 'sys/tools/random/' ~ num_bytes, data=data) }}"
- name: Hash some data and display the hash
vars:
input: |
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Pellentesque posuere dui a ipsum dapibus, et placerat nibh bibendum.
data:
input: '{{ input | b64encode }}'
hash_algo: sha2-256
ansible.builtin.debug:
msg: "The hash is {{ lookup('community.hashi_vault.vault_write', 'sys/tools/hash/' ~ hash_algo, data=data) }}"
# In this next example, the Ansible controller's token does not have permission to read the secrets we need.
# It does have permission to generate new secret IDs for an approle which has permission to read the secrets,
# however the approle is configured to:
# 1) allow a maximum of 1 use per secret ID
# 2) restrict the IPs allowed to use login using the approle to those of the remote hosts
#
# Normally, the fact that a new secret ID would be generated on every loop iteration would not be desirable,
# but here it's quite convenient.
- name: Retrieve secrets from the remote host with one-time-use approle creds
vars:
role_id: "{{ lookup('community.hashi_vault.vault_read', 'auth/approle/role/role-name/role-id') }}"
secret_id: "{{ lookup('community.hashi_vault.vault_write', 'auth/approle/role/role-name/secret-id') }}"
community.hashi_vault.vault_read:
auth_method: approle
role_id: '{{ role_id }}'
secret_id: '{{ secret_id }}'
path: '{{ item }}'
register: secret_data
loop:
- secret/data/secret1
- secret/data/app/deploy-key
- secret/data/access-codes/self-destruct
# This time we have a secret values on the controller, and we need to run a command the remote host,
# that is expecting to a use single-use token as input, so we need to use wrapping to send the data.
- name: Run a command that needs wrapped secrets
vars:
secrets:
secret1: '{{ my_secret_1 }}'
secret2: '{{ second_secret }}'
wrapped: "{{ lookup('community.hashi_vault.vault_write', 'sys/wrapping/wrap', data=secrets) }}"
ansible.builtin.command: 'vault unwrap {{ wrapped }}'
"""
RETURN = """
_raw:
description: The raw result of the write 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 ..plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase
from ..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)
data = self._options_adapter.get_option('data')
wrap_ttl = self._options_adapter.get_option_default('wrap_ttl')
try:
self.authenticator.validate()
self.authenticator.authenticate(client)
except (NotImplementedError, HashiVaultValueError) as e:
raise_from(AnsibleError(e), e)
for term in terms:
try:
try:
# TODO: write_data will eventually turn back into write
# see: https://github.com/hvac/hvac/issues/1034
response = client.write_data(path=term, wrap_ttl=wrap_ttl, data=data)
except AttributeError as e:
# https://github.com/ansible-collections/community.hashi_vault/issues/389
if "path" in data or "wrap_ttl" in data:
raise_from(AnsibleError("To use 'path' or 'wrap_ttl' as data keys, use hvac >= 1.2"), e)
else:
response = client.write(path=term, wrap_ttl=wrap_ttl, **data)
except hvac.exceptions.Forbidden as e:
raise_from(AnsibleError("Forbidden: Permission Denied to path '%s'." % term), e)
except hvac.exceptions.InvalidPath as e:
raise_from(AnsibleError("The path '%s' doesn't seem to exist." % term), e)
except hvac.exceptions.InternalServerError as e:
raise_from(AnsibleError("Internal Server Error: %s" % str(e)), e)
# https://github.com/hvac/hvac/issues/797
# HVAC returns a raw response object when the body is not JSON.
# That includes 204 responses, which are successful with no body.
# So we will try to detect that and a act accordingly.
# A better way may be to implement our own adapter for this
# collection, but it's a little premature to do that.
if hasattr(response, 'json') and callable(response.json):
if response.status_code == 204:
output = {}
else:
display.warning('Vault returned status code %i and an unparsable body.' % response.status_code)
output = response.content
else:
output = response
ret.append(output)
return ret