Adding in Ansible (still a WIP).
This commit is contained in:
+350
@@ -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]]
|
||||
+337
@@ -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]
|
||||
+220
@@ -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
|
||||
+233
@@ -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
|
||||
+183
@@ -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
|
||||
+138
@@ -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]
|
||||
+137
@@ -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
|
||||
+195
@@ -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]
|
||||
+203
@@ -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
|
||||
Reference in New Issue
Block a user