diff options
Diffstat (limited to 'collections-debian-merged/ansible_collections/community/hashi_vault/plugins')
-rw-r--r-- | collections-debian-merged/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py | 876 |
1 files changed, 876 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py b/collections-debian-merged/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py new file mode 100644 index 00000000..a00e29ba --- /dev/null +++ b/collections-debian-merged/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py @@ -0,0 +1,876 @@ +# (c) 2020, Brian Scholer (@briantist) +# (c) 2015, Jonathan Davila <jonathan(at)davila.io> +# (c) 2017 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ + lookup: hashi_vault + author: + - Jonathan Davila (!UNKNOWN) <jdavila(at)ansible.com> + - Brian Scholer (@briantist) + short_description: Retrieve secrets from HashiCorp's Vault + requirements: + - hvac (python library) + - hvac 0.7.0+ (for namespace support) + - hvac 0.9.6+ (to avoid most deprecation warnings) + - hvac 0.10.5+ (for JWT auth) + - hvac 0.10.6+ (to avoid deprecation warning for AppRole) + - botocore (only if inferring aws params from boto) + - boto3 (only if using a boto profile) + description: + - Retrieve secrets from HashiCorp's 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). + options: + secret: + description: Vault path to the secret being requested in the format C(path[:field]). + required: True + token: + description: + - Vault token. Token may be specified explicitly, through the listed env var, and also through the C(VAULT_TOKEN) env var. + - If no token is supplied, explicitly or through env, then the plugin will check for a token file, as determined by I(token_path) and I(token_file). + - The order of token loading (first found wins) is C(token param -> ANSIBLE_HASHI_VAULT_TOKEN -> VAULT_TOKEN -> token file). + env: + - name: ANSIBLE_HASHI_VAULT_TOKEN + version_added: '0.2.0' + token_path: + description: If no token is specified, will try to read the I(token_file) from this path. + env: + - name: VAULT_TOKEN_PATH + deprecated: + why: standardizing environment variables + version: 2.0.0 + collection_name: community.hashi_vault + alternatives: ANSIBLE_HASHI_VAULT_TOKEN_PATH + - name: ANSIBLE_HASHI_VAULT_TOKEN_PATH + version_added: '0.2.0' + ini: + - section: lookup_hashi_vault + key: token_path + token_file: + description: If no token is specified, will try to read the token from this file in I(token_path). + env: + - name: VAULT_TOKEN_FILE + deprecated: + why: standardizing environment variables + version: 2.0.0 + collection_name: community.hashi_vault + alternatives: ANSIBLE_HASHI_VAULT_TOKEN_FILE + - name: ANSIBLE_HASHI_VAULT_TOKEN_FILE + version_added: '0.2.0' + ini: + - section: lookup_hashi_vault + key: token_file + default: '.vault-token' + token_validate: + description: + - For token auth, will perform a C(lookup-self) operation to determine the token's validity before using it. + - Disable if your token doesn't have the C(lookup-self) capability. + env: + - name: ANSIBLE_HASHI_VAULT_TOKEN_VALIDATE + ini: + - section: lookup_hashi_vault + key: token_validate + type: boolean + default: true + version_added: 0.2.0 + url: + description: + - URL to the Vault service. + - If not specified by any other means, the value of the C(VAULT_ADDR) environment variable will be used. + env: + - name: ANSIBLE_HASHI_VAULT_ADDR + version_added: '0.2.0' + ini: + - section: lookup_hashi_vault + key: url + default: 'http://127.0.0.1:8200' + username: + description: Authentication user name. + password: + description: Authentication password. + proxies: + description: + - URL(s) to the proxies used to access the Vault service. + - It can be a string or a dict. + - If it's a dict, provide the scheme (eg. C(http) or C(https)) as the key, and the URL as the value. + - If it's a string, provide a single URL that will be used as the proxy for both C(http) and C(https) schemes. + - A string that can be interpreted as a dictionary will be converted to one (see examples). + - You can specify a different proxy for HTTP and HTTPS resources. + - If not specified, L(environment variables from the Requests library,https://requests.readthedocs.io/en/master/user/advanced/#proxies) are used. + env: + - name: ANSIBLE_HASHI_VAULT_PROXIES + ini: + - section: lookup_hashi_vault + key: proxies + type: raw + version_added: '1.1.0' + role_id: + description: Vault Role ID. Used in approle and aws_iam_login auth methods. + env: + - name: VAULT_ROLE_ID + deprecated: + why: standardizing environment variables + version: 2.0.0 + collection_name: community.hashi_vault + alternatives: ANSIBLE_HASHI_VAULT_ROLE_ID + - name: ANSIBLE_HASHI_VAULT_ROLE_ID + version_added: '0.2.0' + ini: + - section: lookup_hashi_vault + key: role_id + secret_id: + description: Secret ID to be used for Vault AppRole authentication. + env: + - name: VAULT_SECRET_ID + deprecated: + why: standardizing environment variables + version: 2.0.0 + collection_name: community.hashi_vault + alternatives: ANSIBLE_HASHI_VAULT_SECRET_ID + - name: ANSIBLE_HASHI_VAULT_SECRET_ID + version_added: '0.2.0' + auth_method: + description: + - Authentication method to be used. + env: + - name: VAULT_AUTH_METHOD + deprecated: + why: standardizing environment variables + version: 2.0.0 + collection_name: community.hashi_vault + alternatives: ANSIBLE_HASHI_VAULT_AUTH_METHOD + - name: ANSIBLE_HASHI_VAULT_AUTH_METHOD + version_added: '0.2.0' + ini: + - section: lookup_hashi_vault + key: auth_method + choices: + - token + - userpass + - ldap + - approle + - aws_iam_login + - jwt + default: token + 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 ] + mount_point: + description: + - Vault mount point. + - If not specified, the default mount point for a given auth method is used. + - Does not apply to token authentication. + jwt: + description: The JSON Web Token (JWT) to use for JWT authentication to Vault. + env: + - name: ANSIBLE_HASHI_VAULT_JWT + ca_cert: + description: Path to certificate to use for authentication. + aliases: [ cacert ] + validate_certs: + description: + - Controls verification and validation of SSL certificates, mostly you only want to turn off with self signed ones. + - Will be populated with the inverse of C(VAULT_SKIP_VERIFY) if that is set and I(validate_certs) is not explicitly provided. + - Will default to C(true) if neither I(validate_certs) or C(VAULT_SKIP_VERIFY) are set. + type: boolean + namespace: + description: + - Vault namespace where secrets reside. This option requires HVAC 0.7.0+ and Vault 0.11+. + - Optionally, this may be achieved by prefixing the authentication mount point and/or secret path with the namespace + (e.g C(mynamespace/secret/mysecret)). + - If environment variable C(VAULT_NAMESPACE) is set, its value will be used last among all ways to specify I(namespace). + env: + - name: ANSIBLE_HASHI_VAULT_NAMESPACE + version_added: '0.2.0' + ini: + - section: lookup_hashi_vault + key: namespace + version_added: '0.2.0' + aws_profile: + description: The AWS profile + type: str + aliases: [ boto_profile ] + env: + - name: AWS_DEFAULT_PROFILE + - name: AWS_PROFILE + aws_access_key: + description: The AWS access key to use. + type: str + aliases: [ aws_access_key_id ] + env: + - name: EC2_ACCESS_KEY + - name: AWS_ACCESS_KEY + - name: AWS_ACCESS_KEY_ID + aws_secret_key: + description: The AWS secret key that corresponds to the access key. + type: str + aliases: [ aws_secret_access_key ] + env: + - name: EC2_SECRET_KEY + - name: AWS_SECRET_KEY + - name: AWS_SECRET_ACCESS_KEY + aws_security_token: + description: The AWS security token if using temporary access and secret keys. + type: str + env: + - name: EC2_SECURITY_TOKEN + - name: AWS_SESSION_TOKEN + - name: AWS_SECURITY_TOKEN + region: + description: The AWS region for which to create the connection. + type: str + env: + - name: EC2_REGION + - name: AWS_REGION + aws_iam_server_id: + description: If specified, sets the value to use for the C(X-Vault-AWS-IAM-Server-ID) header as part of C(GetCallerIdentity) request. + env: + - name: ANSIBLE_HASHI_VAULT_AWS_IAM_SERVER_ID + ini: + - section: lookup_hashi_vault + key: aws_iam_server_id + required: False + version_added: '0.2.0' +""" + +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_login + ansible.builtin.debug: + msg: "{{ lookup('community.hashi_vault.hashi_vault', 'secret/hello:value', auth_method='aws_iam_login', 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) }}" + +# 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 (in the term string, Ansible key=value syntax) + ansible.builtin.debug: + msg: "{{ lookup('community.hashi_vault.hashi_vault', '... proxies=http=http://myproxy1,https=http://myproxy2') }}" + +- 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\\"}') }}" +""" + +RETURN = """ +_raw: + description: + - secrets(s) requested + type: list + elements: dict +""" + +import os + +from ansible.errors import AnsibleError +from ansible.plugins.lookup import LookupBase +from ansible.module_utils.parsing.convert_bool import boolean +from ansible import constants as C +from ansible.utils.display import Display +from ansible.module_utils.common.validation import check_type_dict, check_type_str + +display = Display() + +HAS_HVAC = False +try: + import hvac + HAS_HVAC = True +except ImportError: + HAS_HVAC = False + +HAS_BOTOCORE = False +try: + # import boto3 + import botocore + HAS_BOTOCORE = True +except ImportError: + HAS_BOTOCORE = False + +HAS_BOTO3 = False +try: + import boto3 + # import botocore + HAS_BOTO3 = True +except ImportError: + HAS_BOTO3 = False + +# see https://github.com/ansible-collections/community.hashi_vault/issues/10 +# +# Options which seek to use environment vars that are not Ansible-specific +# should load those as values of last resort, so that INI values can override them. +# For default processing, list such options and vars here. +# Alternatively, process them in another appropriate place like an auth method's +# validate_ method. +# +# key = option_name +# value = list of env vars (in order of those checked first; process stops when value is found) +LOW_PRECEDENCE_ENV_VAR_OPTIONS = { + 'token_path': ['HOME'], + 'namespace': ['VAULT_NAMESPACE'], + 'token': ['VAULT_TOKEN'], + 'url': ['VAULT_ADDR'], +} + + +# TODO: this is a workaround for deprecations not being shown in lookups +# See: https://github.com/ansible/ansible/issues/73051 +# Fix: https://github.com/ansible/ansible/pull/73058 +# +# If #73058 or another fix is backported, this should be removed. +def deprecate(collection_name='community.hashi_vault'): + + # nicked from cli/__init__.py + # with slight customizations to help filter out relevant messages + # (relying on the collection name since it's a valid attrib and we only have 1 plugin at this time) + + # warn about deprecated config options + + for deprecated in list(C.config.DEPRECATED): + name = deprecated[0] + why = deprecated[1]['why'] + if deprecated[1].get('collection_name') != collection_name: + continue + + if 'alternatives' in deprecated[1]: + alt = ', use %s instead' % deprecated[1]['alternatives'] + else: + alt = '' + ver = deprecated[1].get('version') + date = deprecated[1].get('date') + collection_name = deprecated[1].get('collection_name') + display.deprecated("%s option, %s%s" % (name, why, alt), version=ver, date=date, collection_name=collection_name) + + # remove this item from the list so it won't get processed again by something else + C.config.DEPRECATED.remove(deprecated) + + +class HashiVault: + def get_options(self, *option_names, **kwargs): + ret = {} + include_falsey = kwargs.get('include_falsey', False) + for option in option_names: + val = self.options.get(option) + if val or include_falsey: + ret[option] = val + return ret + + def __init__(self, **kwargs): + self.options = kwargs + + # check early that auth method is actually available + self.auth_function = 'auth_' + self.options['auth_method'] + if not (hasattr(self, self.auth_function) and callable(getattr(self, self.auth_function))): + raise AnsibleError( + "Authentication method '%s' is not implemented. ('%s' member function not found)" % (self.options['auth_method'], self.auth_function) + ) + + client_args = { + 'url': self.options['url'], + 'verify': self.options['ca_cert'] + } + + if self.options.get('namespace'): + client_args['namespace'] = self.options['namespace'] + + # this is the only auth_method-specific thing here, because if we're using a token, we need it now + if self.options['auth_method'] == 'token': + client_args['token'] = self.options.get('token') + + if self.options.get('proxies') is not None: + client_args['proxies'] = self.options.get('proxies') + + self.client = hvac.Client(**client_args) + # logout to prevent accidental use of inferred tokens + # https://github.com/ansible-collections/community.hashi_vault/issues/13 + if 'token' not in client_args: + self.client.logout() + + # Check for old version, before auth_methods class (added in 0.7.0): + # https://github.com/hvac/hvac/releases/tag/v0.7.0 + # + # hvac is moving auth methods into the auth_methods class + # which lives in the client.auth member. + # + # Attempting to find which backends were moved into the class when (this is primarily for warnings): + # 0.7.0 -- github, ldap, mfa, azure?, gcp + # 0.7.1 -- okta + # 0.8.0 -- kubernetes + # 0.9.0 -- azure?, radius + # 0.9.3 -- aws + # 0.9.6 -- userpass + # 0.10.5 -- jwt (new) + self.hvac_has_auth_methods = hasattr(self.client, 'auth') + + # We've already checked to ensure a method exists for a particular auth_method, of the form: + # + # auth_<method_name> + # + def authenticate(self): + getattr(self, self.auth_function)() + + def get(self): + '''gets a secret. should always return a list''' + secret = self.options['secret'] + field = self.options['secret_field'] + return_as = self.options['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]] + + # begin auth implementation methods + # + # To add new backends, 3 things should be added: + # + # 1. Add a new validate_auth_<method_name> method to the LookupModule, which is responsible for validating + # that it has the necessary options and whatever else it needs. + # + # 2. Add a new auth_<method_name> method to this class. These implementations are faily minimal as they should + # already have everything they need. This is also the place to check for deprecated auth methods as hvac + # continues to move backends into the auth_methods class. + # + # 3. Update the avail_auth_methods list in the LookupModule's auth_methods() method (for now this is static). + # + def auth_token(self): + if self.options.get('token_validate') and not self.client.is_authenticated(): + raise AnsibleError("Invalid Hashicorp Vault Token Specified for hashi_vault lookup.") + + def auth_userpass(self): + params = self.get_options('username', 'password', 'mount_point') + try: + self.client.auth.userpass.login(**params) + except (NotImplementedError, AttributeError): + display.warning("HVAC should be updated to version 0.9.6 or higher. Deprecated method 'auth_userpass' will be used.") + self.client.auth_userpass(**params) + + def auth_ldap(self): + params = self.get_options('username', 'password', 'mount_point') + try: + self.client.auth.ldap.login(**params) + except (NotImplementedError, AttributeError): + display.warning("HVAC should be updated to version 0.7.0 or higher. Deprecated method 'auth_ldap' will be used.") + self.client.auth_ldap(**params) + + def auth_approle(self): + params = self.get_options('role_id', 'secret_id', 'mount_point') + try: + self.client.auth.approle.login(**params) + except (NotImplementedError, AttributeError): + display.warning("HVAC should be updated to version 0.10.6 or higher. Deprecated method 'auth_approle' will be used.") + self.client.auth_approle(**params) + + def auth_aws_iam_login(self): + params = self.options['_auth_aws_iam_login_params'] + try: + self.client.auth.aws.iam_login(**params) + except (NotImplementedError, AttributeError): + display.warning("HVAC should be updated to version 0.9.3 or higher. Deprecated method 'auth_aws_iam' will be used.") + self.client.auth_aws_iam(**params) + + def auth_jwt(self): + params = self.get_options('role_id', 'jwt', 'mount_point') + params['role'] = params.pop('role_id') + + if 'mount_point' in params: + params['path'] = params.pop('mount_point') + + try: + response = self.client.auth.jwt.jwt_login(**params) + # must manually set the client token with JWT login + # see https://github.com/hvac/hvac/issues/644 + self.client.token = response['auth']['client_token'] + except (NotImplementedError, AttributeError): + raise AnsibleError("JWT authentication requires HVAC version 0.10.5 or higher.") + + # end auth implementation methods + + +class LookupModule(LookupBase): + 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_term(term)) + self.set_options(direct=opts) + # TODO: remove deprecate() if backported fix is available (see method definition) + deprecate() + self.process_options() + # FUTURE: Create one object, authenticate once, and re-use it, + # for gets, for better use during with_ loops. + client = HashiVault(**self._options) + client.authenticate() + ret.extend(client.get()) + + return ret + + def parse_term(self, term): + '''parses a term string into options''' + param_dict = {} + + for i, param in enumerate(term.split()): + try: + key, value = param.split('=', 1) + except ValueError: + if (i == 0): + # allow secret to be specified as value only if it's first + key = 'secret' + value = param + else: + raise AnsibleError("hashi_vault lookup plugin needs key=value pairs, but received %s" % term) + param_dict[key] = value + return param_dict + + def process_options(self): + '''performs deep validation and value loading for options''' + + # low preference env vars + self.low_preference_env_vars() + + # ca_cert to verify + self.boolean_or_cacert() + + # auth methods + self.auth_methods() + + # secret field splitter + self.field_ops() + + # proxies (dict or str) + self.process_option_proxies() + + # begin options processing methods + + def set_default_option_env(self, option, var): + '''sets an option to the value of an env var if None''' + if self.get_option(option) is None: + self.set_option(option, os.environ.get(var)) + + def low_preference_env_vars(self): + '''sets all options that have a low preference env var''' + # see definition of LOW_PRECEDENCE_ENV_VAR_OPTIONS near the top of the file + for opt, envs in LOW_PRECEDENCE_ENV_VAR_OPTIONS.items(): + for env in envs: + self.set_default_option_env(opt, env) + + def boolean_or_cacert(self): + # This is needed because of this (https://hvac.readthedocs.io/en/stable/source/hvac_v1.html): + # + # # verify (Union[bool,str]) - Either a boolean to indicate whether TLS verification should + # # be performed when sending requests to Vault, or a string pointing at the CA bundle to use for verification. + # + '''return a bool or cacert''' + ca_cert = self.get_option('ca_cert') + + validate_certs = self.get_option('validate_certs') + + if validate_certs is None: + # Validate certs option was not explicitly set + + # Check if VAULT_SKIP_VERIFY is set + vault_skip_verify = os.environ.get('VAULT_SKIP_VERIFY') + + if vault_skip_verify is not None: + # VAULT_SKIP_VERIFY is set + try: + # Check that we have a boolean value + vault_skip_verify = boolean(vault_skip_verify) + # Use the inverse of VAULT_SKIP_VERIFY + validate_certs = not vault_skip_verify + except TypeError: + # Not a boolean value fallback to default value (True) + validate_certs = True + else: + validate_certs = True + + if not (validate_certs and ca_cert): + self.set_option('ca_cert', validate_certs) + + 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.set_option('secret_field', field) + + def auth_methods(self): + # enforce and set the list of available auth methods + # TODO: can this be read from the choices: field in documentation? + avail_auth_methods = ['token', 'approle', 'userpass', 'ldap', 'aws_iam_login', 'jwt'] + self.set_option('avail_auth_methods', avail_auth_methods) + auth_method = self.get_option('auth_method') + + if auth_method not in avail_auth_methods: + raise AnsibleError( + "Authentication method '%s' not supported. Available options are %r" % (auth_method, avail_auth_methods) + ) + + # run validator if available + auth_validator = 'validate_auth_' + auth_method + if hasattr(self, auth_validator) and callable(getattr(self, auth_validator)): + getattr(self, auth_validator)(auth_method) + + def process_option_proxies(self): + # check if 'proxies' option is dict or str + + proxies_opt = self.get_option('proxies') + + if proxies_opt is None: + return + + try: + # if it can be interpreted as dict + # do it + proxies = check_type_dict(proxies_opt) + except TypeError: + # if it can't be interpreted as dict + proxy = check_type_str(proxies_opt) + # but can be interpreted as str + # use this str as http and https proxy + proxies = { + 'http': proxy, + 'https': proxy + } + + # record the new/interpreted value for 'proxies' option + self.set_option('proxies', proxies) + + # end options processing methods + + # begin auth method validators + + def validate_by_required_fields(self, auth_method, *field_names): + missing = [field for field in field_names if not self.get_option(field)] + + if missing: + raise AnsibleError("Authentication method %s requires options %r to be set, but these are missing: %r" % (auth_method, field_names, missing)) + + def validate_auth_userpass(self, auth_method): + self.validate_by_required_fields(auth_method, 'username', 'password') + + def validate_auth_ldap(self, auth_method): + self.validate_by_required_fields(auth_method, 'username', 'password') + + def validate_auth_approle(self, auth_method): + self.validate_by_required_fields(auth_method, 'role_id') + + # This lone superfluous get_option() is intentional, see: + # https://github.com/ansible-collections/community.hashi_vault/issues/35 + self.get_option('secret_id') + + def validate_auth_token(self, auth_method): + if auth_method == 'token': + if not self.get_option('token') and self.get_option('token_path'): + token_filename = os.path.join( + self.get_option('token_path'), + self.get_option('token_file') + ) + if os.path.exists(token_filename): + with open(token_filename) as token_file: + self.set_option('token', token_file.read().strip()) + + if not self.get_option('token'): + raise AnsibleError("No Vault Token specified or discovered.") + + def validate_auth_aws_iam_login(self, auth_method): + params = { + 'access_key': self.get_option('aws_access_key'), + 'secret_key': self.get_option('aws_secret_key'), + } + + if self.get_option('mount_point'): + params['mount_point'] = self.get_option('mount_point') + + if self.get_option('role_id'): + params['role'] = self.get_option('role_id') + + if self.get_option('region'): + params['region'] = self.get_option('region') + + if self.get_option('aws_iam_server_id'): + params['header_value'] = self.get_option('aws_iam_server_id') + + if not (params['access_key'] and params['secret_key']): + profile = self.get_option('aws_profile') + if profile: + # try to load boto profile + if not HAS_BOTO3: + raise AnsibleError("boto3 is required for loading a boto profile.") + session_credentials = boto3.session.Session(profile_name=profile).get_credentials() + else: + # try to load from IAM credentials + if not HAS_BOTOCORE: + raise AnsibleError("botocore is required for loading IAM role credentials.") + session_credentials = botocore.session.get_session().get_credentials() + + if not session_credentials: + raise AnsibleError("No AWS credentials supplied or available.") + + params['access_key'] = session_credentials.access_key + params['secret_key'] = session_credentials.secret_key + if session_credentials.token: + params['session_token'] = session_credentials.token + + self.set_option('_auth_aws_iam_login_params', params) + + def validate_auth_jwt(self, auth_method): + self.validate_by_required_fields(auth_method, 'role_id', 'jwt') + + # end auth method validators |