summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/hashi_vault/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
commit66cec45960ce1d9c794e9399de15c138acb18aed (patch)
tree59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/community/hashi_vault/plugins
parentInitial commit. (diff)
downloadansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz
ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/hashi_vault/plugins')
-rw-r--r--ansible_collections/community/hashi_vault/plugins/doc_fragments/attributes.py38
-rw-r--r--ansible_collections/community/hashi_vault/plugins/doc_fragments/auth.py308
-rw-r--r--ansible_collections/community/hashi_vault/plugins/doc_fragments/connection.py161
-rw-r--r--ansible_collections/community/hashi_vault/plugins/doc_fragments/engine_mount.py25
-rw-r--r--ansible_collections/community/hashi_vault/plugins/doc_fragments/token_create.py93
-rw-r--r--ansible_collections/community/hashi_vault/plugins/doc_fragments/wrapping.py25
-rw-r--r--ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.py38
-rw-r--r--ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.yml98
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py349
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_ansible_settings.py337
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_kv1_get.py220
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_kv2_get.py233
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_list.py183
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_login.py138
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_read.py137
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_token_create.py195
-rw-r--r--ansible_collections/community/hashi_vault/plugins/lookup/vault_write.py191
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_approle.py40
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_aws_iam.py97
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_azure.py105
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_cert.py42
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_jwt.py51
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_ldap.py40
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_none.py33
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_token.py105
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_userpass.py47
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_authenticator.py102
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_connection_options.py260
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_common.py302
-rw-r--r--ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_module.py56
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_kv1_get.py197
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_delete.py179
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_get.py212
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_list.py134
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_login.py177
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_pki_generate_certificate.py296
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_read.py133
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_token_create.py223
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_write.py191
-rw-r--r--ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_lookup_base.py47
-rw-r--r--ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_plugin.py87
41 files changed, 5925 insertions, 0 deletions
diff --git a/ansible_collections/community/hashi_vault/plugins/doc_fragments/attributes.py b/ansible_collections/community/hashi_vault/plugins/doc_fragments/attributes.py
new file mode 100644
index 00000000..7536fa79
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/doc_fragments/attributes.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (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
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r'''
+options: {}
+attributes:
+ check_mode:
+ description: Can run in C(check_mode) and return changed status prediction without modifying target.
+'''
+
+ ACTION_GROUP = r'''
+options: {}
+attributes:
+ action_group:
+ description: Use C(group/community.hashi_vault.vault) in C(module_defaults) to set defaults for this module.
+ support: full
+ membership:
+ - community.hashi_vault.vault
+'''
+
+ # Should be used together with the standard fragment
+ CHECK_MODE_READ_ONLY = r'''
+options: {}
+attributes:
+ check_mode:
+ support: full
+ details:
+ - This module is "read only" and operates the same regardless of check mode.
+'''
diff --git a/ansible_collections/community/hashi_vault/plugins/doc_fragments/auth.py b/ansible_collections/community/hashi_vault/plugins/doc_fragments/auth.py
new file mode 100644
index 00000000..8c6bd876
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/doc_fragments/auth.py
@@ -0,0 +1,308 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (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
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r'''
+ options:
+ auth_method:
+ description:
+ - Authentication method to be used.
+ - C(none) auth method was added in collection version C(1.2.0).
+ - C(cert) auth method was added in collection version C(1.4.0).
+ - C(aws_iam_login) was renamed C(aws_iam) in collection version C(2.1.0) and was removed in C(3.0.0).
+ - C(azure) auth method was added in collection version C(3.2.0).
+ choices:
+ - token
+ - userpass
+ - ldap
+ - approle
+ - aws_iam
+ - azure
+ - jwt
+ - cert
+ - none
+ default: token
+ type: str
+ 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.
+ type: str
+ token:
+ description:
+ - Vault token. Token may be specified explicitly, through the listed [env] vars, 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 var -> ANSIBLE_HASHI_VAULT_TOKEN -> VAULT_TOKEN -> token file).
+ type: str
+ token_path:
+ description: If no token is specified, will try to read the I(token_file) from this path.
+ type: str
+ token_file:
+ description: If no token is specified, will try to read the token from this file in I(token_path).
+ default: '.vault-token'
+ type: str
+ 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 does not have the C(lookup-self) capability.
+ type: bool
+ default: false
+ version_added: 0.2.0
+ username:
+ description: Authentication user name.
+ type: str
+ password:
+ description: Authentication password.
+ type: str
+ role_id:
+ description:
+ - Vault Role ID or name. Used in C(approle), C(aws_iam), C(azure) and C(cert) auth methods.
+ - For C(cert) auth, if no I(role_id) is supplied, the default behavior is to try all certificate roles and return any one that matches.
+ - For C(azure) auth, I(role_id) is required.
+ type: str
+ secret_id:
+ description: Secret ID to be used for Vault AppRole authentication.
+ type: str
+ jwt:
+ description: The JSON Web Token (JWT) to use for JWT authentication to Vault.
+ type: str
+ aws_profile:
+ description: The AWS profile
+ type: str
+ aliases: [ boto_profile ]
+ aws_access_key:
+ description: The AWS access key to use.
+ type: str
+ aliases: [ 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 ]
+ aws_security_token:
+ description: The AWS security token if using temporary access and secret keys.
+ type: str
+ region:
+ description: The AWS region for which to create the connection.
+ type: str
+ 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.
+ required: False
+ type: str
+ version_added: '0.2.0'
+ azure_tenant_id:
+ description:
+ - The Azure Active Directory Tenant ID (also known as the Directory ID) of the service principal. Should be a UUID.
+ - >-
+ Required when using a service principal to authenticate to Vault,
+ e.g. required when both I(azure_client_id) and I(azure_client_secret) are specified.
+ - Optional when using managed identity to authenticate to Vault.
+ required: False
+ type: str
+ version_added: '3.2.0'
+ azure_client_id:
+ description:
+ - The client ID (also known as application ID) of the Azure AD service principal or managed identity. Should be a UUID.
+ - If not specified, will use the system assigned managed identity.
+ required: False
+ type: str
+ version_added: '3.2.0'
+ azure_client_secret:
+ description: The client secret of the Azure AD service principal.
+ required: False
+ type: str
+ version_added: '3.2.0'
+ azure_resource:
+ description: The resource URL for the application registered in Azure Active Directory. Usually should not be changed from the default.
+ required: False
+ type: str
+ default: https://management.azure.com/
+ version_added: '3.2.0'
+ cert_auth_public_key:
+ description: For C(cert) auth, path to the certificate file to authenticate with, in PEM format.
+ type: path
+ version_added: 1.4.0
+ cert_auth_private_key:
+ description: For C(cert) auth, path to the private key file to authenticate with, in PEM format.
+ type: path
+ version_added: 1.4.0
+ '''
+
+ PLUGINS = r'''
+ options:
+ auth_method:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_AUTH_METHOD
+ version_added: 0.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: auth_method
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_auth_method
+ version_added: 1.2.0
+ mount_point:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_MOUNT_POINT
+ version_added: 1.5.0
+ ini:
+ - section: hashi_vault_collection
+ key: mount_point
+ version_added: 1.5.0
+ vars:
+ - name: ansible_hashi_vault_mount_point
+ version_added: 1.5.0
+ token:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_TOKEN
+ version_added: 0.2.0
+ vars:
+ - name: ansible_hashi_vault_token
+ version_added: 1.2.0
+ token_path:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_TOKEN_PATH
+ version_added: 0.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: token_path
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_token_path
+ version_added: 1.2.0
+ token_file:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_TOKEN_FILE
+ version_added: 0.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: token_file
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_token_file
+ version_added: 1.2.0
+ token_validate:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_TOKEN_VALIDATE
+ ini:
+ - section: hashi_vault_collection
+ key: token_validate
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_token_validate
+ version_added: 1.2.0
+ username:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_USERNAME
+ version_added: '1.2.0'
+ vars:
+ - name: ansible_hashi_vault_username
+ version_added: '1.2.0'
+ password:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_PASSWORD
+ version_added: '1.2.0'
+ vars:
+ - name: ansible_hashi_vault_password
+ version_added: '1.2.0'
+ role_id:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_ROLE_ID
+ version_added: 0.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: role_id
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_role_id
+ version_added: 1.2.0
+ secret_id:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_SECRET_ID
+ version_added: 0.2.0
+ vars:
+ - name: ansible_hashi_vault_secret_id
+ version_added: 1.2.0
+ jwt:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_JWT
+ aws_profile:
+ env:
+ - name: AWS_DEFAULT_PROFILE
+ - name: AWS_PROFILE
+ aws_access_key:
+ env:
+ - name: EC2_ACCESS_KEY
+ - name: AWS_ACCESS_KEY
+ - name: AWS_ACCESS_KEY_ID
+ aws_secret_key:
+ env:
+ - name: EC2_SECRET_KEY
+ - name: AWS_SECRET_KEY
+ - name: AWS_SECRET_ACCESS_KEY
+ aws_security_token:
+ env:
+ - name: EC2_SECURITY_TOKEN
+ - name: AWS_SESSION_TOKEN
+ - name: AWS_SECURITY_TOKEN
+ region:
+ env:
+ - name: EC2_REGION
+ - name: AWS_REGION
+ aws_iam_server_id:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_AWS_IAM_SERVER_ID
+ ini:
+ - section: hashi_vault_collection
+ key: aws_iam_server_id
+ version_added: 1.4.0
+ azure_tenant_id:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_AZURE_TENANT_ID
+ ini:
+ - section: hashi_vault_collection
+ key: azure_tenant_id
+ vars:
+ - name: ansible_hashi_vault_azure_tenant_id
+ azure_client_id:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_AZURE_CLIENT_ID
+ ini:
+ - section: hashi_vault_collection
+ key: azure_client_id
+ vars:
+ - name: ansible_hashi_vault_azure_client_id
+ azure_client_secret:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_AZURE_CLIENT_SECRET
+ vars:
+ - name: ansible_hashi_vault_azure_client_secret
+ azure_resource:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_AZURE_RESOURCE
+ ini:
+ - section: hashi_vault_collection
+ key: azure_resource
+ vars:
+ - name: ansible_hashi_vault_azure_resource
+ cert_auth_public_key:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_CERT_AUTH_PUBLIC_KEY
+ ini:
+ - section: hashi_vault_collection
+ key: cert_auth_public_key
+ cert_auth_private_key:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_CERT_AUTH_PRIVATE_KEY
+ ini:
+ - section: hashi_vault_collection
+ key: cert_auth_private_key
+ '''
diff --git a/ansible_collections/community/hashi_vault/plugins/doc_fragments/connection.py b/ansible_collections/community/hashi_vault/plugins/doc_fragments/connection.py
new file mode 100644
index 00000000..e7ab8d07
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/doc_fragments/connection.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (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
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r'''
+ options:
+ 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.
+ - If C(VAULT_ADDR) is also not defined then an error will be raised.
+ type: str
+ 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.
+ type: raw
+ version_added: 1.1.0
+ ca_cert:
+ description:
+ - Path to certificate to use for authentication.
+ - If not specified by any other means, the C(VAULT_CACERT) environment variable will be used.
+ aliases: [ cacert ]
+ type: str
+ 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: bool
+ 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).
+ type: str
+ timeout:
+ description:
+ - Sets the connection timeout in seconds.
+ - If not set, then the C(hvac) library's default is used.
+ type: int
+ version_added: 1.3.0
+ retries:
+ description:
+ - "Allows for retrying on errors, based on
+ the L(Retry class in the urllib3 library,https://urllib3.readthedocs.io/en/latest/reference/urllib3.util.html#urllib3.util.Retry)."
+ - This collection defines recommended defaults for retrying connections to Vault.
+ - This option can be specified as a positive number (integer) or dictionary.
+ - If this option is not specified or the number is C(0), then retries are disabled.
+ - A number sets the total number of retries, and uses collection defaults for the other settings.
+ - A dictionary value is used directly to initialize the C(Retry) class, so it can be used to fully customize retries.
+ - For detailed information on retries, see the collection User Guide.
+ type: raw
+ version_added: 1.3.0
+ retry_action:
+ description:
+ - Controls whether and how to show messages on I(retries).
+ - This has no effect if a request is not retried.
+ type: str
+ choices:
+ - ignore
+ - warn
+ default: warn
+ version_added: 1.3.0
+ '''
+
+ PLUGINS = r'''
+ options:
+ url:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_ADDR
+ version_added: 0.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: url
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_url
+ version_added: 1.2.0
+ - name: ansible_hashi_vault_addr
+ version_added: 1.2.0
+ proxies:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_PROXIES
+ ini:
+ - section: hashi_vault_collection
+ key: proxies
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_proxies
+ version_added: 1.2.0
+ type: raw
+ version_added: 1.1.0
+ ca_cert:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_CA_CERT
+ version_added: 1.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: ca_cert
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_ca_cert
+ version_added: 1.2.0
+ validate_certs:
+ vars:
+ - name: ansible_hashi_vault_validate_certs
+ version_added: 1.2.0
+ namespace:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_NAMESPACE
+ version_added: 0.2.0
+ ini:
+ - section: hashi_vault_collection
+ key: namespace
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_namespace
+ version_added: 1.2.0
+ timeout:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_TIMEOUT
+ ini:
+ - section: hashi_vault_collection
+ key: timeout
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_timeout
+ retries:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_RETRIES
+ ini:
+ - section: hashi_vault_collection
+ key: retries
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_retries
+ retry_action:
+ env:
+ - name: ANSIBLE_HASHI_VAULT_RETRY_ACTION
+ ini:
+ - section: hashi_vault_collection
+ key: retry_action
+ version_added: 1.4.0
+ vars:
+ - name: ansible_hashi_vault_retry_action
+ '''
diff --git a/ansible_collections/community/hashi_vault/plugins/doc_fragments/engine_mount.py b/ansible_collections/community/hashi_vault/plugins/doc_fragments/engine_mount.py
new file mode 100644
index 00000000..8187f28e
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/doc_fragments/engine_mount.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (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
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r'''
+options:
+ engine_mount_point:
+ description: The path where the secret backend is mounted.
+ type: str
+'''
+
+ PLUGINS = r'''
+options:
+ engine_mount_point:
+ vars:
+ - name: ansible_hashi_vault_engine_mount_point
+'''
diff --git a/ansible_collections/community/hashi_vault/plugins/doc_fragments/token_create.py b/ansible_collections/community/hashi_vault/plugins/doc_fragments/token_create.py
new file mode 100644
index 00000000..30031f07
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/doc_fragments/token_create.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (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
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r'''
+options:
+ orphan:
+ description:
+ - When C(true), uses the C(/create-orphan) API endpoint, which requires C(sudo) (but not C(root)) to create an orphan.
+ - With C(hvac>=1.0.0), requires collection version C(>=3.3.0).
+ type: bool
+ default: false
+ no_parent:
+ description:
+ - This option only has effect if used by a C(root) or C(sudo) caller and only when I(orphan=false).
+ - When C(true), the token created will not have a parent.
+ type: bool
+ no_default_policy:
+ description:
+ - If C(true) the default policy will not be contained in this token's policy set.
+ - If the token will be used with this collection, set I(token_validate=false).
+ type: bool
+ policies:
+ description:
+ - A list of policies for the token. This must be a subset of the policies belonging to the token making the request, unless root.
+ - If not specified, defaults to all the policies of the calling token.
+ type: list
+ elements: str
+ id:
+ description:
+ - The ID of the client token. Can only be specified by a root token.
+ - The ID provided may not contain a C(.) character.
+ - Otherwise, the token ID is a randomly generated value.
+ type: str
+ role_name:
+ description:
+ - The name of the token role. If used, the token will be created against the specified role name which may override options set during this call.
+ type: str
+ meta:
+ description: A dict of string to string valued metadata. This is passed through to the audit devices.
+ type: dict
+ renewable:
+ description:
+ - Set to C(false) to disable the ability of the token to be renewed past its initial TTL.
+ - Setting the value to C(true) will allow the token to be renewable up to the system/mount maximum TTL.
+ type: bool
+ ttl:
+ description:
+ - The TTL period of the token, provided as C(1h) for example, where hour is the largest suffix.
+ - If not provided, the token is valid for the default lease TTL, or indefinitely if the root policy is used.
+ type: str
+ type:
+ description: The token type. The default is determined by the role configuration specified by I(role_name).
+ type: str
+ choices:
+ - batch
+ - service
+ explicit_max_ttl:
+ description:
+ - If set, the token will have an explicit max TTL set upon it.
+ - This maximum token TTL cannot be changed later,
+ and unlike with normal tokens, updates to the system/mount max TTL value will have no effect at renewal time.
+ - The token will never be able to be renewed or used past the value set at issue time.
+ type: str
+ display_name:
+ description: The display name of the token.
+ type: str
+ num_uses:
+ description:
+ - The maximum uses for the given token. This can be used to create a one-time-token or limited use token.
+ - The value of C(0) has no limit to the number of uses.
+ type: int
+ period:
+ description:
+ - If specified, the token will be periodic.
+ - It will have no maximum TTL (unless an I(explicit_max_ttl) is also set) but every renewal will use the given period.
+ - Requires a root token or one with the C(sudo) capability.
+ type: str
+ entity_alias:
+ description:
+ - Name of the entity alias to associate with during token creation.
+ - Only works in combination with I(role_name) option and used entity alias must be listed in C(allowed_entity_aliases).
+ - If this has been specified, the entity will not be inherited from the parent.
+ type: str
+'''
diff --git a/ansible_collections/community/hashi_vault/plugins/doc_fragments/wrapping.py b/ansible_collections/community/hashi_vault/plugins/doc_fragments/wrapping.py
new file mode 100644
index 00000000..720f7bdf
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/doc_fragments/wrapping.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+
+# Copyright: (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
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = r'''
+options:
+ wrap_ttl:
+ description: Specifies response wrapping token creation with duration. For example C(15s), C(20m), C(25h).
+ type: str
+'''
+
+ PLUGINS = r'''
+options:
+ wrap_ttl:
+ vars:
+ - name: ansible_hashi_vault_wrap_ttl
+'''
diff --git a/ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.py b/ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.py
new file mode 100644
index 00000000..543d60c3
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.py
@@ -0,0 +1,38 @@
+# -*- coding: utf-8 -*-
+# (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
+
+from ansible.errors import AnsibleError
+
+
+def vault_login_token(login_response, optional_field='login'):
+ '''Extracts the token value from a Vault login response.
+ Meant to be used with the vault_login module and lookup plugin.
+ '''
+
+ try:
+ deref = login_response[optional_field]
+ except TypeError:
+ raise AnsibleError("The 'vault_login_token' filter expects a dictionary.")
+ except KeyError:
+ deref = login_response
+
+ try:
+ token = deref['auth']['client_token']
+ except KeyError:
+ raise AnsibleError("Could not find 'auth' or 'auth.client_token' fields. Input may not be a Vault login response.")
+
+ return token
+
+
+class FilterModule(object):
+ '''Ansible jinja2 filters'''
+
+ def filters(self):
+ return {
+ 'vault_login_token': vault_login_token,
+ }
diff --git a/ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.yml b/ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.yml
new file mode 100644
index 00000000..e2946baf
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/filter/vault_login_token.yml
@@ -0,0 +1,98 @@
+# (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
+---
+DOCUMENTATION:
+ name: vault_login_token
+ short_description: Extracts the Vault token from a login or token creation
+ version_added: 2.2.0
+ description:
+ - Extracts the token value from the structure returned by a Vault token creation operation.
+ seealso:
+ - module: community.hashi_vault.vault_login
+ - module: community.hashi_vault.vault_token_create
+ - plugin: community.hashi_vault.vault_login
+ plugin_type: lookup
+ - plugin: community.hashi_vault.vault_token_create
+ plugin_type: lookup
+ - ref: Filter Guide <ansible_collections.community.hashi_vault.docsite.filter_guide.vault_login_token>
+ description: The C(community.hashi_vault) Filter Guide
+ notes:
+ - >-
+ This filter is the same as reading into the I(_input) dictionary directly,
+ but it provides semantic meaning and automatically works with the differing output of the modules and lookups.
+ See the Filter guide for more information.
+ options:
+ _input:
+ description:
+ - A dictionary matching the structure returned by a login or token creation.
+ type: dict
+ required: true
+ optional_field:
+ description:
+ - >-
+ If this field exists in the input dictionary, then the value of that field is used as the I(_input) value.
+ - >-
+ The default value deals with the difference between the output of lookup plugins,
+ and does not need to be changed in most cases.
+ - See the examples or the Filter guide for more information.
+ type: string
+ default: login
+ author:
+ - Brian Scholer (@briantist)
+
+EXAMPLES: |
+ - name: Set defaults
+ vars:
+ ansible_hashi_vault_url: https://vault:9801/
+ ansible_hashi_vault_auth_method: userpass
+ ansible_hashi_vault_username: user
+ ansible_hashi_vault_password: "{{ lookup('env', 'MY_SECRET_PASSWORD') }}"
+ module_defaults:
+ community.hashi_vault.vault_login:
+ url: '{{ ansible_hashi_vault_url }}'
+ auth_method: '{{ ansible_hashi_vault_auth_method }}'
+ username: '{{ ansible_hashi_vault_username }}'
+ password: '{{ ansible_hashi_vault_password }}'
+ block:
+ - name: Perform a login with a lookup and display the token
+ vars:
+ login_response: "{{ lookup('community.hashi_vault.vault_login') }}"
+ debug:
+ msg: "The token is {{ login_response | community.hashi_vault.vault_login_token }}"
+
+ - name: Perform a login with a module
+ community.hashi_vault.vault_login:
+ register: login_response
+
+ - name: Display the token
+ debug:
+ msg: "The token is {{ login_response | community.hashi_vault.vault_login_token }}"
+
+ - name: Use of optional_field
+ vars:
+ lookup_login_response: "{{ lookup('community.hashi_vault.vault_login') }}"
+ my_data:
+ something: somedata
+ vault_login: "{{ lookup_login_response }}"
+
+ token_from_param: "{{ my_data | community.hashi_vault.vault_login_token(optional_field='vault_login') }}"
+ token_from_deref: "{{ my_data['vault_login'] | community.hashi_vault.vault_login_token }}"
+ # if the optional field doesn't exist, the dictionary itself is still checked
+ unused_optional: "{{ my_data['vault_login'] | community.hashi_vault.vault_login_token(optional_field='missing') }}"
+ block:
+ - name: Display the variables
+ ansible.builtin.debug:
+ var: '{{ item }}'
+ loop:
+ - my_data
+ - token_from_param
+ - token_from_deref
+ - unused_optional
+
+RETURN:
+ _value:
+ description: The token value.
+ returned: always
+ sample: s.nnrpog4i5gjizr6b8g1inwj3
+ type: string
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py b/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py
new file mode 100644
index 00000000..1ea9b2c9
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/hashi_vault.py
@@ -0,0 +1,349 @@
+# (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.set_option('secret_field', field)
+
+ def get(self):
+ '''gets a secret. should always return a list'''
+
+ secret = self.get_option('secret')
+ field = self.get_option('secret_field')
+ 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]]
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_ansible_settings.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_ansible_settings.py
new file mode 100644
index 00000000..f3103a95
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_ansible_settings.py
@@ -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]
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_kv1_get.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_kv1_get.py
new file mode 100644
index 00000000..053150a8
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_kv1_get.py
@@ -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
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_kv2_get.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_kv2_get.py
new file mode 100644
index 00000000..bbd54ef8
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_kv2_get.py
@@ -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
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_list.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_list.py
new file mode 100644
index 00000000..56521c79
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_list.py
@@ -0,0 +1,183 @@
+# (c) 2023, Tom Kivlin (@tomkivlin)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = """
+ name: vault_list
+ version_added: 4.1.0
+ author:
+ - Tom Kivlin (@tomkivlin)
+ short_description: Perform a list operation against HashiCorp Vault
+ requirements:
+ - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html))
+ - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements).
+ description:
+ - Performs a generic list operation against a given path in HashiCorp Vault.
+ seealso:
+ - module: community.hashi_vault.vault_list
+ extends_documentation_fragment:
+ - community.hashi_vault.connection
+ - community.hashi_vault.connection.plugins
+ - community.hashi_vault.auth
+ - community.hashi_vault.auth.plugins
+ options:
+ _terms:
+ description: Vault path(s) to be listed.
+ type: str
+ required: true
+"""
+
+EXAMPLES = """
+- name: List all secrets at a path
+ ansible.builtin.debug:
+ msg: "{{ lookup('community.hashi_vault.vault_list', 'secret/metadata', url='https://vault:8201') }}"
+ # For kv2, the path needs to follow the pattern 'mount_point/metadata' 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
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_login.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_login.py
new file mode 100644
index 00000000..ebb49e4a
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_login.py
@@ -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 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
+ )
+
+ 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]
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_read.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_read.py
new file mode 100644
index 00000000..794262ed
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_read.py
@@ -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
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_token_create.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_token_create.py
new file mode 100644
index 00000000..9b19ae29
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_token_create.py
@@ -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=no) 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 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):
+ 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]
diff --git a/ansible_collections/community/hashi_vault/plugins/lookup/vault_write.py b/ansible_collections/community/hashi_vault/plugins/lookup/vault_write.py
new file mode 100644
index 00000000..97ff0c09
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/lookup/vault_write.py
@@ -0,0 +1,191 @@
+# (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
+ - 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.
+ 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 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)
+
+ 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:
+ 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
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_approle.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_approle.py
new file mode 100644
index 00000000..0c261d3a
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_approle.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase
+
+
+class HashiVaultAuthMethodApprole(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: approle'''
+
+ NAME = 'approle'
+ OPTIONS = ['role_id', 'secret_id', 'mount_point']
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodApprole, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ self.validate_by_required_fields('role_id')
+
+ def authenticate(self, client, use_token=True):
+ params = self._options.get_filled_options(*self.OPTIONS)
+ try:
+ response = client.auth.approle.login(use_token=use_token, **params)
+ except (NotImplementedError, AttributeError):
+ self.warn("HVAC should be updated to version 0.10.6 or higher. Deprecated method 'auth_approle' will be used.")
+ response = client.auth_approle(use_token=use_token, **params)
+
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_aws_iam.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_aws_iam.py
new file mode 100644
index 00000000..e3bb004b
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_aws_iam.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import (
+ HashiVaultAuthMethodBase,
+ HashiVaultValueError,
+)
+
+
+class HashiVaultAuthMethodAwsIam(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: userpass'''
+
+ NAME = 'aws_iam'
+ OPTIONS = [
+ 'aws_profile',
+ 'aws_access_key',
+ 'aws_secret_key',
+ 'aws_security_token',
+ 'region',
+ 'aws_iam_server_id',
+ 'role_id',
+ ]
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodAwsIam, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ params = {
+ 'access_key': self._options.get_option_default('aws_access_key'),
+ 'secret_key': self._options.get_option_default('aws_secret_key'),
+ }
+
+ session_token = self._options.get_option_default('aws_security_token')
+ if session_token:
+ params['session_token'] = session_token
+
+ mount_point = self._options.get_option_default('mount_point')
+ if mount_point:
+ params['mount_point'] = mount_point
+
+ role = self._options.get_option_default('role_id')
+ if role:
+ params['role'] = role
+
+ region = self._options.get_option_default('region')
+ if region:
+ params['region'] = region
+
+ header_value = self._options.get_option_default('aws_iam_server_id')
+ if header_value:
+ params['header_value'] = header_value
+
+ if not (params['access_key'] and params['secret_key']):
+ try:
+ import boto3
+ import botocore
+ except ImportError:
+ raise HashiVaultValueError("boto3 is required for loading a profile or IAM role credentials.")
+
+ profile = self._options.get_option_default('aws_profile')
+ try:
+ session_credentials = boto3.session.Session(profile_name=profile).get_credentials()
+ except botocore.exceptions.ProfileNotFound:
+ raise HashiVaultValueError("The AWS profile '%s' was not found." % profile)
+
+ if not session_credentials:
+ raise HashiVaultValueError("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._auth_aws_iam_login_params = params
+
+ def authenticate(self, client, use_token=True):
+ params = self._auth_aws_iam_login_params
+ try:
+ response = client.auth.aws.iam_login(use_token=use_token, **params)
+ except (NotImplementedError, AttributeError):
+ self.warn("HVAC should be updated to version 0.9.3 or higher. Deprecated method 'auth_aws_iam' will be used.")
+ client.auth_aws_iam(use_token=use_token, **params)
+
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_azure.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_azure.py
new file mode 100644
index 00000000..36f44e07
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_azure.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2022 Junrui Chen (@jchenship)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import (
+ HashiVaultAuthMethodBase,
+ HashiVaultValueError,
+)
+
+
+class HashiVaultAuthMethodAzure(HashiVaultAuthMethodBase):
+ '''HashiVault auth method for Azure'''
+
+ NAME = 'azure'
+ OPTIONS = [
+ 'role_id',
+ 'jwt',
+ 'mount_point',
+ 'azure_tenant_id',
+ 'azure_client_id',
+ 'azure_client_secret',
+ 'azure_resource',
+ ]
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodAzure, self).__init__(
+ option_adapter, warning_callback, deprecate_callback
+ )
+
+ def validate(self):
+ params = {
+ 'role': self._options.get_option_default('role_id'),
+ 'jwt': self._options.get_option_default('jwt'),
+ }
+ if not params['role']:
+ raise HashiVaultValueError(
+ 'role_id is required for azure authentication.'
+ )
+
+ # if mount_point is not provided, it will use the default value defined
+ # in hvac library (e.g. `azure`)
+ mount_point = self._options.get_option_default('mount_point')
+ if mount_point:
+ params['mount_point'] = mount_point
+
+ # if jwt exists, use provided jwt directly, otherwise trying to get jwt
+ # from azure service principal or managed identity
+ if not params['jwt']:
+ azure_tenant_id = self._options.get_option_default('azure_tenant_id')
+ azure_client_id = self._options.get_option_default('azure_client_id')
+ azure_client_secret = self._options.get_option_default('azure_client_secret')
+
+ # the logic of getting azure scope is from this function
+ # https://github.com/Azure/azure-cli/blob/azure-cli-2.39.0/src/azure-cli-core/azure/cli/core/auth/util.py#L72
+ # the reason we expose resource instead of scope is resource is
+ # more aligned with the vault azure auth config here
+ # https://www.vaultproject.io/api-docs/auth/azure#resource
+ azure_resource = self._options.get_option('azure_resource')
+ azure_scope = azure_resource + "/.default"
+
+ try:
+ import azure.identity
+ except ImportError:
+ raise HashiVaultValueError(
+ "azure-identity is required for getting access token from azure service principal or managed identity."
+ )
+
+ if azure_client_id and azure_client_secret:
+ # service principal
+ if not azure_tenant_id:
+ raise HashiVaultValueError(
+ 'azure_tenant_id is required when using azure service principal.'
+ )
+ azure_credentials = azure.identity.ClientSecretCredential(
+ azure_tenant_id, azure_client_id, azure_client_secret
+ )
+ elif azure_client_id:
+ # user assigned managed identity
+ azure_credentials = azure.identity.ManagedIdentityCredential(
+ client_id=azure_client_id
+ )
+ else:
+ # system assigned managed identity
+ azure_credentials = azure.identity.ManagedIdentityCredential()
+
+ params['jwt'] = azure_credentials.get_token(azure_scope).token
+
+ self._auth_azure_login_params = params
+
+ def authenticate(self, client, use_token=True):
+ params = self._auth_azure_login_params
+ response = client.auth.azure.login(use_token=use_token, **params)
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_cert.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_cert.py
new file mode 100644
index 00000000..af5d3bb1
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_cert.py
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Devon Mar (@devon-mar)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase
+
+
+class HashiVaultAuthMethodCert(HashiVaultAuthMethodBase):
+ """HashiVault option group class for auth: cert"""
+
+ NAME = "cert"
+ OPTIONS = ["cert_auth_public_key", "cert_auth_private_key", "mount_point", "role_id"]
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodCert, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ self.validate_by_required_fields("cert_auth_public_key", "cert_auth_private_key")
+
+ def authenticate(self, client, use_token=True):
+ options = self._options.get_filled_options(*self.OPTIONS)
+
+ params = {
+ "cert_pem": options["cert_auth_public_key"],
+ "key_pem": options["cert_auth_private_key"],
+ }
+
+ if "mount_point" in options:
+ params["mount_point"] = options["mount_point"]
+ if "role_id" in options:
+ params["name"] = options["role_id"]
+
+ try:
+ response = client.auth.cert.login(use_token=use_token, **params)
+ except NotImplementedError:
+ raise NotImplementedError("cert authentication requires HVAC version 0.10.12 or higher.")
+
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_jwt.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_jwt.py
new file mode 100644
index 00000000..da291942
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_jwt.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase
+
+
+class HashiVaultAuthMethodJwt(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: jwt'''
+
+ NAME = 'jwt'
+ OPTIONS = ['jwt', 'role_id', 'mount_point']
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodJwt, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ self.validate_by_required_fields('role_id', 'jwt')
+
+ def authenticate(self, client, use_token=True):
+ params = self._options.get_filled_options(*self.OPTIONS)
+ params['role'] = params.pop('role_id')
+
+ if 'mount_point' in params:
+ params['path'] = params.pop('mount_point')
+
+ try:
+ response = client.auth.jwt.jwt_login(**params)
+ except (NotImplementedError, AttributeError):
+ raise NotImplementedError("JWT authentication requires HVAC version 0.10.5 or higher.")
+
+ # must manually set the client token with JWT login
+ # see https://github.com/hvac/hvac/issues/644
+ # fixed in https://github.com/hvac/hvac/pull/746
+ # but we do it manually to maintain compatibilty with older hvac versions.
+ if use_token:
+ client.token = response['auth']['client_token']
+
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_ldap.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_ldap.py
new file mode 100644
index 00000000..7fcb6b38
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_ldap.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase
+
+
+class HashiVaultAuthMethodLdap(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: ldap'''
+
+ NAME = 'ldap'
+ OPTIONS = ['username', 'password', 'mount_point']
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodLdap, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ self.validate_by_required_fields('username', 'password')
+
+ def authenticate(self, client, use_token=True):
+ params = self._options.get_filled_options(*self.OPTIONS)
+ try:
+ response = client.auth.ldap.login(use_token=use_token, **params)
+ except (NotImplementedError, AttributeError):
+ self.warn("HVAC should be updated to version 0.7.0 or higher. Deprecated method 'auth_ldap' will be used.")
+ response = client.auth_ldap(use_token=use_token, **params)
+
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_none.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_none.py
new file mode 100644
index 00000000..22c3e28f
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_none.py
@@ -0,0 +1,33 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase
+
+
+class HashiVaultAuthMethodNone(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: none'''
+
+ NAME = 'none'
+ OPTIONS = []
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodNone, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ pass
+
+ def authenticate(self, client, use_token=False):
+ return None
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_token.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_token.py
new file mode 100644
index 00000000..3b66b193
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_token.py
@@ -0,0 +1,105 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import os
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import (
+ HashiVaultAuthMethodBase,
+ HashiVaultValueError,
+)
+
+
+class HashiVaultAuthMethodToken(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: userpass'''
+
+ NAME = 'token'
+ OPTIONS = ['token', 'token_path', 'token_file', 'token_validate']
+
+ _LATE_BINDING_ENV_VAR_OPTIONS = {
+ 'token': dict(env=['VAULT_TOKEN']),
+ 'token_path': dict(env=['HOME']),
+ }
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodToken, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def _simulate_login_response(self, token, lookup_response=None):
+ '''returns a similar structure to a login method's return, optionally incorporating a lookup-self response'''
+
+ response = {
+ 'auth': {
+ 'client_token': token
+ }
+ }
+
+ if lookup_response is None:
+ return response
+
+ # first merge in the entire response at the top level
+ # but, rather than being missing, the auth field is going to be None,
+ # so we explicitly overwrite that with our original value.
+ response.update(lookup_response, auth=response['auth'])
+
+ # then we'll merge the data field right into the auth field
+ response['auth'].update(lookup_response['data'])
+
+ # and meta->metadata needs a name change
+ metadata = response['auth'].pop('meta', None)
+ if metadata:
+ response['auth']['metadata'] = metadata
+
+ return response
+
+ def validate(self):
+ self.process_late_binding_env_vars(self._LATE_BINDING_ENV_VAR_OPTIONS)
+
+ if self._options.get_option_default('token') is None and self._options.get_option_default('token_path') is not None:
+ token_filename = os.path.join(
+ self._options.get_option('token_path'),
+ self._options.get_option('token_file')
+ )
+ if os.path.exists(token_filename):
+ if not os.path.isfile(token_filename):
+ raise HashiVaultValueError("The Vault token file '%s' was found but is not a file." % token_filename)
+ with open(token_filename) as token_file:
+ self._options.set_option('token', token_file.read().strip())
+
+ if self._options.get_option_default('token') is None:
+ raise HashiVaultValueError("No Vault Token specified or discovered.")
+
+ def authenticate(self, client, use_token=True, lookup_self=False):
+ token = self._stringify(self._options.get_option('token'))
+ validate = self._options.get_option_default('token_validate')
+
+ response = None
+
+ if use_token:
+ client.token = token
+
+ if lookup_self or validate:
+ from hvac import exceptions
+
+ try:
+ try:
+ response = client.auth.token.lookup_self()
+ except (NotImplementedError, AttributeError):
+ # usually we would warn here, but the v1 method doesn't seem to be deprecated (yet?)
+ response = client.lookup_token() # when token=None on this method, it calls lookup-self
+ except (exceptions.Forbidden, exceptions.InvalidPath, exceptions.InvalidRequest):
+ if validate:
+ raise HashiVaultValueError("Invalid Vault Token Specified.")
+
+ return self._simulate_login_response(token, response)
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_userpass.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_userpass.py
new file mode 100644
index 00000000..f9ba58f6
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_auth_method_userpass.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase
+
+
+class HashiVaultAuthMethodUserpass(HashiVaultAuthMethodBase):
+ '''HashiVault option group class for auth: userpass'''
+
+ NAME = 'userpass'
+ OPTIONS = ['username', 'password', 'mount_point']
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodUserpass, self).__init__(option_adapter, warning_callback, deprecate_callback)
+
+ def validate(self):
+ self.validate_by_required_fields('username', 'password')
+
+ def authenticate(self, client, use_token=True):
+ params = self._options.get_filled_options(*self.OPTIONS)
+ try:
+ response = client.auth.userpass.login(**params)
+ except (NotImplementedError, AttributeError):
+ self.warn("HVAC should be updated to version 0.9.6 or higher. Deprecated method 'auth_userpass' will be used.")
+ response = client.auth_userpass(**params)
+
+ # must manually set the client token with userpass login
+ # see https://github.com/hvac/hvac/issues/644
+ # fixed in 0.11.0 (https://github.com/hvac/hvac/pull/733)
+ # but we keep the old behavior to maintain compatibility with older hvac
+ if use_token:
+ client.token = response['auth']['client_token']
+
+ return response
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_authenticator.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_authenticator.py
new file mode 100644
index 00000000..acf574bf
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_authenticator.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+# please keep this list in alphabetical order of auth method name
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_approle import HashiVaultAuthMethodApprole
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_aws_iam import HashiVaultAuthMethodAwsIam
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_azure import HashiVaultAuthMethodAzure
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_cert import HashiVaultAuthMethodCert
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_jwt import HashiVaultAuthMethodJwt
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_ldap import HashiVaultAuthMethodLdap
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_none import HashiVaultAuthMethodNone
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_token import HashiVaultAuthMethodToken
+from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_userpass import HashiVaultAuthMethodUserpass
+
+
+class HashiVaultAuthenticator():
+ ARGSPEC = dict(
+ auth_method=dict(type='str', default='token', choices=[
+ 'token',
+ 'userpass',
+ 'ldap',
+ 'approle',
+ 'aws_iam',
+ 'azure',
+ 'jwt',
+ 'cert',
+ 'none',
+ ]),
+ mount_point=dict(type='str'),
+ token=dict(type='str', no_log=True, default=None),
+ token_path=dict(type='str', default=None, no_log=False),
+ token_file=dict(type='str', default='.vault-token'),
+ token_validate=dict(type='bool', default=False),
+ username=dict(type='str'),
+ password=dict(type='str', no_log=True),
+ role_id=dict(type='str'),
+ secret_id=dict(type='str', no_log=True),
+ jwt=dict(type='str', no_log=True),
+ aws_profile=dict(type='str', aliases=['boto_profile']),
+ aws_access_key=dict(type='str', aliases=['aws_access_key_id'], no_log=False),
+ aws_secret_key=dict(type='str', aliases=['aws_secret_access_key'], no_log=True),
+ aws_security_token=dict(type='str', no_log=False),
+ region=dict(type='str'),
+ aws_iam_server_id=dict(type='str'),
+ azure_tenant_id=dict(type='str'),
+ azure_client_id=dict(type='str'),
+ azure_client_secret=dict(type='str', no_log=True),
+ azure_resource=dict(type='str', default='https://management.azure.com/'),
+ cert_auth_private_key=dict(type='path', no_log=False),
+ cert_auth_public_key=dict(type='path'),
+ )
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ self._options = option_adapter
+ self._selector = {
+ # please keep this list in alphabetical order of auth method name
+ # so that it's easier to scan and see at a glance that a given auth method is present or absent
+ 'approle': HashiVaultAuthMethodApprole(option_adapter, warning_callback, deprecate_callback),
+ 'aws_iam': HashiVaultAuthMethodAwsIam(option_adapter, warning_callback, deprecate_callback),
+ 'azure': HashiVaultAuthMethodAzure(option_adapter, warning_callback, deprecate_callback),
+ 'cert': HashiVaultAuthMethodCert(option_adapter, warning_callback, deprecate_callback),
+ 'jwt': HashiVaultAuthMethodJwt(option_adapter, warning_callback, deprecate_callback),
+ 'ldap': HashiVaultAuthMethodLdap(option_adapter, warning_callback, deprecate_callback),
+ 'none': HashiVaultAuthMethodNone(option_adapter, warning_callback, deprecate_callback),
+ 'token': HashiVaultAuthMethodToken(option_adapter, warning_callback, deprecate_callback),
+ 'userpass': HashiVaultAuthMethodUserpass(option_adapter, warning_callback, deprecate_callback),
+ }
+
+ self.warn = warning_callback
+ self.deprecate = deprecate_callback
+
+ def _get_method_object(self, method=None):
+ if method is None:
+ method = self._options.get_option('auth_method')
+
+ try:
+ o_method = self._selector[method]
+ except KeyError:
+ raise NotImplementedError("auth method '%s' is not implemented in HashiVaultAuthenticator" % method)
+
+ return o_method
+
+ def validate(self, *args, **kwargs):
+ method = self._get_method_object(kwargs.pop('method', None))
+ method.validate(*args, **kwargs)
+
+ def authenticate(self, *args, **kwargs):
+ method = self._get_method_object(kwargs.pop('method', None))
+ return method.authenticate(*args, **kwargs)
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_connection_options.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_connection_options.py
new file mode 100644
index 00000000..f570479d
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_connection_options.py
@@ -0,0 +1,260 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import os
+
+from ansible.module_utils.common.text.converters import to_text
+
+from ansible.module_utils.common.validation import (
+ check_type_dict,
+ check_type_str,
+ check_type_bool,
+ check_type_int,
+)
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultOptionGroupBase
+
+# we implement retries via the urllib3 Retry class
+# https://github.com/ansible-collections/community.hashi_vault/issues/58
+HAS_RETRIES = False
+try:
+ from requests import Session
+ from requests.adapters import HTTPAdapter
+ try:
+ # try for a standalone urllib3
+ import urllib3
+ HAS_RETRIES = True
+ except ImportError:
+ try:
+ # failing that try for a vendored version within requests
+ from requests.packages import urllib3
+ HAS_RETRIES = True
+ except ImportError:
+ pass
+except ImportError:
+ pass
+
+
+class HashiVaultConnectionOptions(HashiVaultOptionGroupBase):
+ '''HashiVault option group class for connection options'''
+
+ OPTIONS = ['url', 'proxies', 'ca_cert', 'validate_certs', 'namespace', 'timeout', 'retries', 'retry_action']
+
+ ARGSPEC = dict(
+ url=dict(type='str', default=None),
+ proxies=dict(type='raw'),
+ ca_cert=dict(type='str', aliases=['cacert'], default=None),
+ validate_certs=dict(type='bool'),
+ namespace=dict(type='str', default=None),
+ timeout=dict(type='int'),
+ retries=dict(type='raw'),
+ retry_action=dict(type='str', choices=['ignore', 'warn'], default='warn'),
+ )
+
+ _LATE_BINDING_ENV_VAR_OPTIONS = {
+ 'url': dict(env=['VAULT_ADDR'], required=True),
+ 'ca_cert': dict(env=['VAULT_CACERT']),
+ 'namespace': dict(env=['VAULT_NAMESPACE']),
+ }
+
+ _RETRIES_DEFAULT_PARAMS = {
+ 'status_forcelist': [
+ # https://www.vaultproject.io/api#http-status-codes
+ # 429 is usually a "too many requests" status, but in Vault it's the default health status response for standby nodes.
+ 412, # Precondition failed. Returned on Enterprise when a request can't be processed yet due to some missing eventually consistent data.
+ # Should be retried, perhaps with a little backoff.
+ 500, # Internal server error. An internal error has occurred, try again later. If the error persists, report a bug.
+ 502, # A request to Vault required Vault making a request to a third party; the third party responded with an error of some kind.
+ 503, # Vault is down for maintenance or is currently sealed. Try again later.
+ ],
+ (
+ # this field name changed in 1.26.0, and in the interest of supporting a wider range of urllib3 versions
+ # we'll use the new name whenever possible, but fall back seamlessly when needed.
+ # See also:
+ # - https://github.com/urllib3/urllib3/issues/2092
+ # - https://github.com/urllib3/urllib3/blob/main/CHANGES.rst#1260-2020-11-10
+ "allowed_methods" if HAS_RETRIES and hasattr(urllib3.util.Retry.DEFAULT, "allowed_methods") else "method_whitelist"
+ ): None, # None allows retries on all methods, including those which may not be considered idempotent, like POST
+ 'backoff_factor': 0.3,
+ }
+
+ def __init__(self, option_adapter, retry_callback_generator=None):
+ super(HashiVaultConnectionOptions, self).__init__(option_adapter)
+ self._retry_callback_generator = retry_callback_generator
+
+ def get_hvac_connection_options(self):
+ '''returns kwargs to be used for constructing an hvac.Client'''
+
+ # validate_certs is only used to optionally change the value of ca_cert
+ def _filter(k, v):
+ return v is not None and k != 'validate_certs'
+
+ # our transformed ca_cert value will become the verify parameter for the hvac client
+ hvopts = self._options.get_filtered_options(_filter, *self.OPTIONS)
+ hvopts['verify'] = hvopts.pop('ca_cert')
+
+ retry_action = hvopts.pop('retry_action')
+ if 'retries' in hvopts:
+ hvopts['session'] = self._get_custom_requests_session(new_callback=self._retry_callback_generator(retry_action), **hvopts.pop('retries'))
+
+ return hvopts
+
+ def process_connection_options(self):
+ '''executes special processing required for certain options'''
+ self.process_late_binding_env_vars(self._LATE_BINDING_ENV_VAR_OPTIONS)
+
+ self._boolean_or_cacert()
+ self._process_option_proxies()
+ self._process_option_retries()
+
+ def _get_custom_requests_session(self, **retry_kwargs):
+ '''returns a requests.Session to pass to hvac (or None)'''
+
+ if not HAS_RETRIES:
+ # because hvac requires requests which requires urllib3 it's unlikely we'll ever reach this condition.
+ raise NotImplementedError("Retries are unavailable. This may indicate very old versions of one or more of the following: hvac, requests, urllib3.")
+
+ # This is defined here because Retry may not be defined if its import failed.
+ # As mentioned above, that's very unlikely, but it'll fail sanity tests nonetheless if defined with other classes.
+ class CallbackRetry(urllib3.util.Retry):
+ def __init__(self, *args, **kwargs):
+ self._newcb = kwargs.pop('new_callback')
+ super(CallbackRetry, self).__init__(*args, **kwargs)
+
+ def new(self, **kwargs):
+ if self._newcb is not None:
+ self._newcb(self)
+
+ kwargs['new_callback'] = self._newcb
+ return super(CallbackRetry, self).new(**kwargs)
+
+ # We don't want the Retry class raising its own exceptions because that will prevent
+ # hvac from raising its own on various response codes.
+ # We set this here, rather than in the defaults, because if the caller sets their own
+ # dict for retries, we use it directly, but we don't want them to have to remember to always
+ # set raise_on_status=False themselves to get proper error handling.
+ # On the off chance someone does set it, we leave it alone, even though it's probably a mistake.
+ # That will be mentioned in the parameter docs.
+ if 'raise_on_status' not in retry_kwargs:
+ retry_kwargs['raise_on_status'] = False
+ # needs urllib 1.15+ https://github.com/urllib3/urllib3/blob/main/CHANGES.rst#115-2016-04-06
+ # but we should always have newer ones via requests, via hvac
+
+ retry = CallbackRetry(**retry_kwargs)
+
+ adapter = HTTPAdapter(max_retries=retry)
+ sess = Session()
+ sess.mount("https://", adapter)
+ sess.mount("http://", adapter)
+
+ return sess
+
+ def _process_option_retries(self):
+ '''check if retries option is int or dict and interpret it appropriately'''
+ # this method focuses on validating the option, and setting a valid Retry object construction dict
+ # it intentionally does not build the Session object, which will be done elsewhere
+
+ retries_opt = self._options.get_option('retries')
+
+ if retries_opt is None:
+ return
+
+ # we'll start with a copy of our defaults
+ retries = self._RETRIES_DEFAULT_PARAMS.copy()
+
+ try:
+ # try int
+ # on int, retry the specified number of times, and use the defaults for everything else
+ # on zero, disable retries
+ retries_int = check_type_int(retries_opt)
+
+ if retries_int < 0:
+ raise ValueError("Number of retries must be >= 0 (got %i)" % retries_int)
+ elif retries_int == 0:
+ retries = None
+ else:
+ retries['total'] = retries_int
+
+ except TypeError:
+ try:
+ # try dict
+ # on dict, use the value directly (will be used as the kwargs to initialize the Retry instance)
+ retries = check_type_dict(retries_opt)
+ except TypeError:
+ raise TypeError("retries option must be interpretable as int or dict. Got: %r" % retries_opt)
+
+ self._options.set_option('retries', retries)
+
+ def _process_option_proxies(self):
+ '''check if 'proxies' option is dict or str and set it appropriately'''
+
+ proxies_opt = self._options.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._options.set_option('proxies', proxies)
+
+ 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._options.get_option('ca_cert')
+
+ validate_certs = self._options.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 = check_type_bool(vault_skip_verify)
+ except TypeError:
+ # Not a boolean value fallback to default value (True)
+ validate_certs = True
+ else:
+ # Use the inverse of VAULT_SKIP_VERIFY
+ validate_certs = not vault_skip_verify
+ else:
+ validate_certs = True
+
+ if not (validate_certs and ca_cert):
+ self._options.set_option('ca_cert', validate_certs)
+ else:
+ self._options.set_option('ca_cert', to_text(ca_cert, errors='surrogate_or_strict'))
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_common.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_common.py
new file mode 100644
index 00000000..b39431c0
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_common.py
@@ -0,0 +1,302 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+'''Python versions supported: >=3.6'''
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+import os
+
+
+HAS_HVAC = False
+try:
+ import hvac
+ HAS_HVAC = True
+except ImportError:
+ HAS_HVAC = False
+
+
+def _stringify(input):
+ '''
+ This method is primarily used to Un-Unsafe values that come from Ansible.
+ We want to remove the Unsafe context so that libraries don't get confused
+ by the values.
+ '''
+
+ # Since this is a module_util, and will be used by both plugins and modules,
+ # we cannot import the AnsibleUnsafe* types, because they are controller-only.
+ # However, they subclass the native types, so we can check for that.
+
+ # bytes is the only consistent type to check against in both py2 and py3
+ if isinstance(input, bytes):
+ # seems redundant, but this will give us a regular bytes object even
+ # when the input is AnsibleUnsafeBytes
+ return bytes(input)
+ else:
+ # instead of checking for py2 vs. py3 to cast to str or unicode,
+ # let's get the type from the literal.
+ return type(u'')(input)
+
+
+class HashiVaultValueError(ValueError):
+ '''Use in common code to raise an Exception that can be turned into AnsibleError or used to fail_json()'''
+
+
+class HashiVaultHelper():
+
+ STRINGIFY_CANDIDATES = set([
+ 'token', # Token will end up in a header, requests requires headers to be str or bytes,
+ # and newer versions of requests stopped converting automatically. Because our
+ # token could have been passed in from a previous lookup call, it could be one
+ # of the AnsibleUnsafe types instead, causing a failure. Tokens should always
+ # be strings, so we will convert them.
+ 'namespace', # namespace is also set in a header
+ ])
+
+ def __init__(self):
+ # TODO move hvac checking here?
+ pass
+
+ @staticmethod
+ def _stringify(input):
+ return _stringify(input)
+
+ def get_vault_client(
+ self,
+ hashi_vault_logout_inferred_token=True, hashi_vault_revoke_on_logout=False, hashi_vault_stringify_args=True,
+ **kwargs
+ ):
+ '''
+ creates a Vault client with the given kwargs
+
+ :param hashi_vault_logout_inferred_token: if True performs "logout" after creation to remove any token that
+ the hvac library itself may have read in. Only used if "token" is not included in kwargs.
+ :type hashi_vault_logout_implied_token: bool
+
+ :param hashi_vault_revoke_on_logout: if True revokes any current token on logout. Only used if a logout is performed. Not recommended.
+ :type hashi_vault_revoke_on_logout: bool
+
+ :param hashi_vault_stringify_args: if True converts a specific set of defined kwargs to a string type.
+ :type hashi_vault_stringify_args: bool
+ '''
+
+ if hashi_vault_stringify_args:
+ for key in kwargs.keys():
+ if key in self.STRINGIFY_CANDIDATES:
+ kwargs[key] = self._stringify(kwargs[key])
+
+ client = hvac.Client(**kwargs)
+
+ # logout to prevent accidental use of inferred tokens
+ # https://github.com/ansible-collections/community.hashi_vault/issues/13
+ if hashi_vault_logout_inferred_token and 'token' not in kwargs:
+ client.logout(revoke_token=hashi_vault_revoke_on_logout)
+
+ return client
+
+
+class HashiVaultOptionAdapter(object):
+ '''
+ The purpose of this class is to provide a standard interface for option getting/setting
+ within module_utils code, since the processes are so different in plugins and modules.
+
+ Attention is paid to ensuring that in plugins we use the methods provided by Config Manager,
+ but to allow flexibility to create an adapter to work with other sources, hence the design
+ of defining specific methods exposed, and having them call provided function references.
+ '''
+ # More context on the need to call Config Manager methods:
+ #
+ # Some issues raised around deprecations in plugins not being processed led to comments
+ # from core maintainers around the need to use Config Manager and also to ensure any
+ # option needed is always retrieved using AnsiblePlugin.get_option(). At the time of this
+ # writing, based on the way Config Manager is implemented, that's not actually necessary,
+ # and calling AnsiblePlugin.set_options() to initialize them is enough. But that's not
+ # guaranteed to stay that way, if get_option() is used to "trigger" internal events.
+ #
+ # More reading:
+ # - https://github.com/ansible-collections/community.hashi_vault/issues/35
+ # - https://github.com/ansible/ansible/issues/73051
+ # - https://github.com/ansible/ansible/pull/73058
+ # - https://github.com/ansible/ansible/pull/73239
+ # - https://github.com/ansible/ansible/pull/73240
+
+ @classmethod
+ def from_dict(cls, dict):
+ return cls(
+ getter=dict.__getitem__,
+ setter=dict.__setitem__,
+ haver=lambda key: key in dict,
+ updater=dict.update,
+ defaultsetter=dict.setdefault,
+ defaultgetter=dict.get,
+ )
+
+ @classmethod
+ def from_ansible_plugin(cls, plugin):
+ return cls(
+ getter=plugin.get_option,
+ setter=plugin.set_option,
+ haver=plugin.has_option if hasattr(plugin, 'has_option') else None,
+ # AnsiblePlugin.has_option was added in 2.10, see https://github.com/ansible/ansible/pull/61078
+ )
+
+ @classmethod
+ def from_ansible_module(cls, module):
+ return cls.from_dict(module.params)
+
+ def __init__(
+ self,
+ getter, setter,
+ haver=None, updater=None, getitems=None, getfiltereditems=None, getfilleditems=None, defaultsetter=None, defaultgetter=None):
+
+ def _default_default_setter(key, default=None):
+ try:
+ value = self.get_option(key)
+ return value
+ except KeyError:
+ self.set_option(key, default)
+ return default
+
+ def _default_updater(**kwargs):
+ for key, value in kwargs.items():
+ self.set_option(key, value)
+
+ def _default_haver(key):
+ try:
+ self.get_option(key)
+ return True
+ except KeyError:
+ return False
+
+ def _default_getitems(*args):
+ return dict((key, self.get_option(key)) for key in args)
+
+ def _default_getfiltereditems(filter, *args):
+ return dict((key, value) for key, value in self.get_options(*args).items() if filter(key, value))
+
+ def _default_getfilleditems(*args):
+ return self.get_filtered_options(lambda k, v: v is not None, *args)
+
+ def _default_default_getter(key, default):
+ try:
+ return self.get_option(key)
+ except KeyError:
+ return default
+
+ self._getter = getter
+ self._setter = setter
+
+ self._haver = haver or _default_haver
+ self._updater = updater or _default_updater
+ self._getitems = getitems or _default_getitems
+ self._getfiltereditems = getfiltereditems or _default_getfiltereditems
+ self._getfilleditems = getfilleditems or _default_getfilleditems
+ self._defaultsetter = defaultsetter or _default_default_setter
+ self._defaultgetter = defaultgetter or _default_default_getter
+
+ def get_option(self, key):
+ return self._getter(key)
+
+ def get_option_default(self, key, default=None):
+ return self._defaultgetter(key, default)
+
+ def set_option(self, key, value):
+ return self._setter(key, value)
+
+ def set_option_default(self, key, default=None):
+ return self._defaultsetter(key, default)
+
+ def has_option(self, key):
+ return self._haver(key)
+
+ def set_options(self, **kwargs):
+ return self._updater(**kwargs)
+
+ def get_options(self, *args):
+ return self._getitems(*args)
+
+ def get_filtered_options(self, filter, *args):
+ return self._getfiltereditems(filter, *args)
+
+ def get_filled_options(self, *args):
+ return self._getfilleditems(*args)
+
+
+class HashiVaultOptionGroupBase:
+ '''A base class for class option group classes'''
+
+ def __init__(self, option_adapter):
+ self._options = option_adapter
+
+ def process_late_binding_env_vars(self, option_vars):
+ '''looks through a set of options, and if empty/None, looks for a value in specified env vars, or sets an optional default'''
+ # 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 = dict with "env" key which is a list of env vars (in order of those checked first; process stops when value is found),
+ # and an optional "default" key whose value will be set if none of the env vars are found.
+ # An optional boolean "required" key can be used to specify that a value is required, so raise if one is not found.
+
+ for opt, config in option_vars.items():
+ for env in config['env']:
+ # we use has_option + get_option rather than get_option_default
+ # because we will only override if the option exists and
+ # is None, not if it's missing. For plugins, that is the usual,
+ # but for modules, they may have to set the default to None
+ # in the argspec if it has late binding env vars.
+ if self._options.has_option(opt) and self._options.get_option(opt) is None:
+ self._options.set_option(opt, os.environ.get(env))
+
+ if 'default' in config and self._options.has_option(opt) and self._options.get_option(opt) is None:
+ self._options.set_option(opt, config['default'])
+
+ if 'required' in config and self._options.get_option_default(opt) is None:
+ raise HashiVaultValueError("Required option %s was not set." % opt)
+
+
+class HashiVaultAuthMethodBase(HashiVaultOptionGroupBase):
+ '''Base class for individual auth method implementations'''
+
+ def __init__(self, option_adapter, warning_callback, deprecate_callback):
+ super(HashiVaultAuthMethodBase, self).__init__(option_adapter)
+ self._warner = warning_callback
+ self._deprecator = deprecate_callback
+
+ def validate(self):
+ '''Validates the given auth method as much as possible without calling Vault.'''
+ raise NotImplementedError('validate must be implemented')
+
+ def authenticate(self, client, use_token=True):
+ '''Authenticates against Vault, returns a token.'''
+ raise NotImplementedError('authenticate must be implemented')
+
+ def validate_by_required_fields(self, *field_names):
+ missing = [field for field in field_names if self._options.get_option_default(field) is None]
+
+ if missing:
+ raise HashiVaultValueError("Authentication method %s requires options %r to be set, but these are missing: %r" % (self.NAME, field_names, missing))
+
+ def warn(self, message):
+ self._warner(message)
+
+ def deprecate(self, message, version=None, date=None, collection_name=None):
+ self._deprecator(message, version=version, date=date, collection_name=collection_name)
+
+ @staticmethod
+ def _stringify(input):
+ return _stringify(input)
diff --git a/ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_module.py b/ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_module.py
new file mode 100644
index 00000000..5d2dfafc
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/module_utils/_hashi_vault_module.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+# Copyright (c) 2021 Brian Scholer (@briantist)
+# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause)
+# SPDX-License-Identifier: BSD-2-Clause
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import (
+ HashiVaultHelper,
+ HashiVaultOptionAdapter,
+)
+from ansible_collections.community.hashi_vault.plugins.module_utils._connection_options import HashiVaultConnectionOptions
+from ansible_collections.community.hashi_vault.plugins.module_utils._authenticator import HashiVaultAuthenticator
+
+
+class HashiVaultModule(AnsibleModule):
+ def __init__(self, *args, **kwargs):
+ if 'hashi_vault_custom_retry_callback' in kwargs:
+ callback = kwargs.pop('hashi_vault_custom_retry_callback')
+ else:
+ callback = self._generate_retry_callback
+
+ super(HashiVaultModule, self).__init__(*args, **kwargs)
+
+ self.helper = HashiVaultHelper()
+ self.adapter = HashiVaultOptionAdapter.from_dict(self.params)
+ self.connection_options = HashiVaultConnectionOptions(option_adapter=self.adapter, retry_callback_generator=callback)
+ self.authenticator = HashiVaultAuthenticator(option_adapter=self.adapter, warning_callback=self.warn, deprecate_callback=self.deprecate)
+
+ @classmethod
+ def generate_argspec(cls, **kwargs):
+ spec = HashiVaultConnectionOptions.ARGSPEC.copy()
+ spec.update(HashiVaultAuthenticator.ARGSPEC.copy())
+ spec.update(**kwargs)
+
+ return spec
+
+ def _generate_retry_callback(self, retry_action):
+ '''returns a Retry callback function for modules'''
+ def _on_retry(retry_obj):
+ if retry_obj.total > 0:
+ if retry_action == 'warn':
+ self.warn('community.hashi_vault: %i %s remaining.' % (retry_obj.total, 'retry' if retry_obj.total == 1 else 'retries'))
+ else:
+ pass
+
+ return _on_retry
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_kv1_get.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv1_get.py
new file mode 100644
index 00000000..e21f4a81
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv1_get.py
@@ -0,0 +1,197 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (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'''
+module: 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:
+ - 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_kv2_get
+ - 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.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.attributes.check_mode_read_only
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ - community.hashi_vault.engine_mount
+options:
+ engine_mount_point:
+ default: kv
+ path:
+ description:
+ - Vault KV path to be read.
+ - This is relative to the I(engine_mount_point), so the mount path should not be included.
+ type: str
+ required: True
+'''
+
+EXAMPLES = r'''
+- name: Read a kv1 secret from Vault via the remote host with userpass auth
+ community.hashi_vault.vault_kv1_get:
+ url: https://vault:8201
+ path: hello
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: response
+ # 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 secret from kv1 with a different mount via the remote host
+ community.hashi_vault.vault_kv1_get:
+ url: https://vault:8201
+ engine_mount_point: custom/kv1/mount
+ path: hello
+ register: response
+ # 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 }}"
+'''
+
+RETURN = r'''
+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
+'''
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ engine_mount_point=dict(type='str', default='kv'),
+ path=dict(type='str', required=True),
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ engine_mount_point = module.params.get('engine_mount_point')
+ path = module.params.get('path')
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ raw = client.secrets.kv.v1.read_secret(path=path, mount_point=engine_mount_point)
+ except hvac.exceptions.Forbidden as e:
+ module.fail_json(msg="Forbidden: Permission Denied to path ['%s']." % path, exception=traceback.format_exc())
+ except hvac.exceptions.InvalidPath as e:
+ if 'Invalid path for a versioned K/V secrets engine' in to_native(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']."
+
+ module.fail_json(msg=msg % (path,), exception=traceback.format_exc())
+
+ metadata = raw.copy()
+ data = metadata.pop('data')
+ module.exit_json(raw=raw, data=data, secret=data, metadata=metadata)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_delete.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_delete.py
new file mode 100644
index 00000000..ac4d5925
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_delete.py
@@ -0,0 +1,179 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (c) 2022, Isaac Wagner (@idwagner)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = '''
+module: vault_kv2_delete
+version_added: 3.4.0
+author:
+ - Isaac Wagner (@idwagner)
+short_description: Delete one or more versions of 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:
+ - Delete one or more versions of a secret from HashiCorp Vault's KV version 2 secret store.
+notes:
+ - This module always reports C(changed) status because it cannot guarantee idempotence.
+ - Use C(changed_when) to control that in cases where the operation is known to not change state.
+attributes:
+ check_mode:
+ support: partial
+ details:
+ - In check mode, the module returns C(changed) status without contacting Vault.
+ - Consider using M(community.hashi_vault.vault_kv2_get) to verify the existence of the secret first.
+seealso:
+ - module: community.hashi_vault.vault_kv2_get
+ - 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.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ - community.hashi_vault.engine_mount
+options:
+ engine_mount_point:
+ default: secret
+ path:
+ description:
+ - Vault KV path to be deleted.
+ - This is relative to the I(engine_mount_point), so the mount path should not be included.
+ - For kv2, do not include C(/data/) or C(/metadata/).
+ type: str
+ required: True
+ versions:
+ description:
+ - One or more versions of the secret to delete.
+ - When omitted, the latest version of the secret is deleted.
+ type: list
+ elements: int
+ required: False
+'''
+
+EXAMPLES = """
+- name: Delete the latest version of the secret/mysecret secret.
+ community.hashi_vault.vault_kv2_delete:
+ url: https://vault:8201
+ path: secret/mysecret
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: result
+
+- name: Delete versions 1 and 3 of the secret/mysecret secret.
+ community.hashi_vault.vault_kv2_delete:
+ url: https://vault:8201
+ path: secret/mysecret
+ versions: [1, 3]
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+"""
+
+RETURN = """
+data:
+ description:
+ - The raw result of the delete against the given path.
+ - This is usually empty, but may contain warnings or other information.
+ returned: success
+ type: dict
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+
+ argspec = HashiVaultModule.generate_argspec(
+ engine_mount_point=dict(type='str', default='secret'),
+ path=dict(type='str', required=True),
+ versions=dict(type='list', elements='int', required=False)
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ engine_mount_point = module.params.get('engine_mount_point')
+ path = module.params.get('path')
+ versions = module.params.get('versions')
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ # Vault has two separate methods, one for delete latest version,
+ # and delete specific versions.
+ if module.check_mode:
+ response = {}
+ elif not versions:
+ response = client.secrets.kv.v2.delete_latest_version_of_secret(
+ path=path, mount_point=engine_mount_point)
+ else:
+ response = client.secrets.kv.v2.delete_secret_versions(
+ path=path, versions=versions, mount_point=engine_mount_point)
+
+ except hvac.exceptions.Forbidden as e:
+ module.fail_json(msg="Forbidden: Permission Denied to path ['%s']." % path, exception=traceback.format_exc())
+
+ # 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:
+ module.warn(
+ 'Vault returned status code %i and an unparsable body.' % response.status_code)
+ output = response.content
+ else:
+ output = response
+
+ module.exit_json(changed=True, data=output)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_get.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_get.py
new file mode 100644
index 00000000..5ff5903f
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_get.py
@@ -0,0 +1,212 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (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'''
+module: 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:
+ - 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_kv1_get
+ - 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.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.attributes.check_mode_read_only
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ - community.hashi_vault.engine_mount
+options:
+ engine_mount_point:
+ default: secret
+ path:
+ description:
+ - Vault KV path to be read.
+ - This is relative to the I(engine_mount_point), so the mount path should not be included.
+ - For kv2, do not include C(/data/) or C(/metadata/).
+ type: str
+ required: True
+ version:
+ description: Specifies the version to return. If not set the latest version is returned.
+ type: int
+'''
+
+EXAMPLES = r'''
+- name: Read the latest version of a kv2 secret from Vault via the remote host with userpass auth
+ community.hashi_vault.vault_kv2_get:
+ url: https://vault:8201
+ path: hello
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: response
+ # equivalent API path 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 secret from kv2 with a different mount via the remote host
+ community.hashi_vault.vault_kv2_get:
+ url: https://vault:8201
+ engine_mount_point: custom/kv2/mount
+ path: hello
+ version: 5
+ register: response
+ # 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
+'''
+
+RETURN = r'''
+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
+'''
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ engine_mount_point=dict(type='str', default='secret'),
+ path=dict(type='str', required=True),
+ version=dict(type='int'),
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ engine_mount_point = module.params.get('engine_mount_point')
+ path = module.params.get('path')
+ version = module.params.get('version')
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ raw = client.secrets.kv.v2.read_secret_version(path=path, version=version, mount_point=engine_mount_point)
+ except hvac.exceptions.Forbidden as e:
+ module.fail_json(msg="Forbidden: Permission Denied to path ['%s']." % path, exception=traceback.format_exc())
+ except hvac.exceptions.InvalidPath as e:
+ module.fail_json(
+ msg="Invalid or missing path ['%s'] with secret version '%s'. Check the path or secret version." % (path, version or 'latest'),
+ exception=traceback.format_exc()
+ )
+
+ data = raw['data']
+ metadata = data['metadata']
+ secret = data['data']
+ module.exit_json(raw=raw, data=data, secret=secret, metadata=metadata)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_list.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_list.py
new file mode 100644
index 00000000..a0823dc2
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_list.py
@@ -0,0 +1,134 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (c) 2023, Tom Kivlin (@tomkivlin)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = """
+ module: vault_list
+ version_added: 4.1.0
+ author:
+ - Tom Kivlin (@tomkivlin)
+ short_description: Perform a list operation against HashiCorp Vault
+ requirements:
+ - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html))
+ - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements).
+ description:
+ - Performs a generic list operation against a given path in HashiCorp Vault.
+ seealso:
+ - ref: community.hashi_vault.vault_list lookup <ansible_collections.community.hashi_vault.vault_list_lookup>
+ description: The official documentation for the C(community.hashi_vault.vault_list) lookup plugin.
+ extends_documentation_fragment:
+ - community.hashi_vault.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.attributes.check_mode_read_only
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ options:
+ path:
+ description: Vault path to be listed.
+ type: str
+ required: true
+"""
+
+EXAMPLES = """
+- name: List kv2 secrets from Vault via the remote host with userpass auth
+ community.hashi_vault.vault_list:
+ url: https://vault:8201
+ path: secret/metadata
+ # For kv2, the path needs to follow the pattern 'mount_point/metadata' or 'mount_point/metadata/path' to list all secrets in that path
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: secret
+
+- name: Display the secrets found at the path provided above
+ ansible.builtin.debug:
+ msg: "{{ secret.data.data['keys'] }}"
+ # Note that secret.data.data.keys won't work as 'keys' is a built-in method
+
+- name: List access policies from Vault via the remote host
+ community.hashi_vault.vault_list:
+ url: https://vault:8201
+ path: sys/policies/acl
+ register: policies
+
+- name: Display the policy names
+ ansible.builtin.debug:
+ msg: "{{ policies.data.data['keys'] }}"
+ # Note that secret.data.data.keys won't work as 'keys' is a built-in method
+"""
+
+RETURN = """
+data:
+ description: The raw result of the list against the given path.
+ returned: success
+ type: dict
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ path=dict(type='str', required=True),
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ path = module.params.get('path')
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ data = client.list(path)
+ except hvac.exceptions.Forbidden as e:
+ module.fail_json(msg="Forbidden: Permission Denied to path '%s'." % path, exception=traceback.format_exc())
+
+ if data is None:
+ module.fail_json(msg="The path '%s' doesn't seem to exist." % path)
+
+ module.exit_json(data=data)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_login.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_login.py
new file mode 100644
index 00000000..c52e969e
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_login.py
@@ -0,0 +1,177 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (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 = """
+ module: 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:
+ - 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.
+ - 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.
+ extends_documentation_fragment:
+ - community.hashi_vault.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ notes:
+ - "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 module 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."
+ attributes:
+ check_mode:
+ support: partial
+ details:
+ - In check mode, this module will not perform a login, 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 C(check_mode=false) in order to have a valid token that can be used.
+ options:
+ token_validate:
+ default: true
+"""
+
+EXAMPLES = """
+- name: Login and use the resulting token
+ community.hashi_vault.vault_login:
+ url: https://vault:8201
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: login_data
+
+- 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.login.auth.client_token }}'
+ path: auth/approle/role/role-name/role-id
+ register: approle_id
+"""
+
+RETURN = """
+login:
+ description: The result of the login against the given auth method.
+ returned: success
+ type: 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
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+# we don't actually need to import hvac directly in this module
+# because all of the hvac calls happen in module utils, but
+# we would like to control the error message here for consistency.
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ # we override this from the shared argspec in order to turn off no_log
+ # otherwise we would not be able to return the input token value
+ token=dict(type='str', no_log=False, default=None),
+
+ # we override this from the shared argspec because the default for
+ # this module should be True, which differs from the rest of the
+ # collection since 4.0.0.
+ token_validate=dict(type='bool', default=True)
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ # a login is technically a write operation, using storage and resources
+ changed = True
+ auth_method = module.params.get('auth_method')
+
+ if auth_method == 'none':
+ module.fail_json(msg="The 'none' auth method is not valid for this module.")
+
+ if auth_method == 'token':
+ # with the token auth method, we don't actually perform a login operation
+ # nor change the state of Vault; it's read-only (to lookup the token's info)
+ changed = False
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ if module.check_mode:
+ response = {'auth': {'client_token': None}}
+ else:
+ response = module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ module.exit_json(changed=changed, login=response)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_pki_generate_certificate.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_pki_generate_certificate.py
new file mode 100644
index 00000000..66b9190b
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_pki_generate_certificate.py
@@ -0,0 +1,296 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (c) 2022, Florent David (@Ripolin)
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+DOCUMENTATION = """
+ module: vault_pki_generate_certificate
+ version_added: 2.3.0
+ author:
+ - Florent David (@Ripolin)
+ short_description: Generates a new set of credentials (private key and certificate) using HashiCorp Vault PKI
+ requirements:
+ - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/changelog.html#may-25th-2019)) version C(0.9.1) or higher
+ - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements).
+ description:
+ - Generates a new set of credentials (private key and certificate) based on a Vault PKI role.
+ seealso:
+ - name: HashiCorp Vault PKI Secrets Engine API
+ description: API documentation for the HashiCorp Vault PKI secrets engine.
+ link: https://www.vaultproject.io/api/secret/pki#generate-certificate
+ - name: HVAC library reference
+ description: HVAC library reference about the PKI engine.
+ link: https://hvac.readthedocs.io/en/stable/usage/secrets_engines/pki.html#generate-certificate
+ extends_documentation_fragment:
+ - community.hashi_vault.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ - community.hashi_vault.engine_mount
+ attributes:
+ check_mode:
+ support: partial
+ details:
+ - In check mode, this module will not contact Vault and will return an empty C(data) field and C(changed) status.
+ options:
+ alt_names:
+ description:
+ - Specifies requested Subject Alternative Names.
+ - These can be host names or email addresses; they will be parsed into their respective fields.
+ - If any requested names do not match role policy, the entire request will be denied.
+ type: list
+ elements: str
+ default: []
+ common_name:
+ description:
+ - Specifies the requested CN for the certificate.
+ - If the CN is allowed by role policy, it will be issued.
+ type: str
+ required: true
+ exclude_cn_from_sans:
+ description:
+ - If true, the given I(common_name) will not be included in DNS or Email Subject Alternate Names (as appropriate).
+ - Useful if the CN is not a hostname or email address, but is instead some human-readable identifier.
+ type: bool
+ default: False
+ format:
+ description:
+ - Specifies the format for returned data.
+ - Can be C(pem), C(der), or C(pem_bundle).
+ - If C(der), the output is base64 encoded.
+ - >-
+ If C(pem_bundle), the C(certificate) field will contain the private key and certificate, concatenated. If the issuing CA is not a Vault-derived
+ self-signed root, this will be included as well.
+ type: str
+ choices: [pem, der, pem_bundle]
+ default: pem
+ ip_sans:
+ description:
+ - Specifies requested IP Subject Alternative Names.
+ - Only valid if the role allows IP SANs (which is the default).
+ type: list
+ elements: str
+ default: []
+ role_name:
+ description:
+ - Specifies the name of the role to create the certificate against.
+ type: str
+ required: true
+ other_sans:
+ description:
+ - Specifies custom OID/UTF8-string SANs.
+ - These must match values specified on the role in C(allowed_other_sans).
+ - "The format is the same as OpenSSL: C(<oid>;<type>:<value>) where the only current valid type is C(UTF8)."
+ type: list
+ elements: str
+ default: []
+ engine_mount_point:
+ description:
+ - Specify the mount point used by the PKI engine.
+ - Defaults to the default used by C(hvac).
+ private_key_format:
+ description:
+ - Specifies the format for marshaling the private key.
+ - Defaults to C(der) which will return either base64-encoded DER or PEM-encoded DER, depending on the value of I(format).
+ - The other option is C(pkcs8) which will return the key marshalled as PEM-encoded PKCS8.
+ type: str
+ choices: [der, pkcs8]
+ default: der
+ ttl:
+ description:
+ - Specifies requested Time To Live.
+ - Cannot be greater than the role's C(max_ttl) value.
+ - If not provided, the role's C(ttl) value will be used.
+ - Note that the role values default to system values if not explicitly set.
+ type: str
+ uri_sans:
+ description:
+ - Specifies the requested URI Subject Alternative Names.
+ type: list
+ elements: str
+ default: []
+"""
+
+EXAMPLES = """
+- name: Login and use the resulting token
+ community.hashi_vault.vault_login:
+ url: https://localhost:8200
+ auth_method: ldap
+ username: "john.doe"
+ password: "{{ user_passwd }}"
+ register: login_data
+
+- name: Generate a certificate with an existing token
+ community.hashi_vault.vault_pki_generate_certificate:
+ role_name: test.example.org
+ common_name: test.example.org
+ ttl: 8760h
+ alt_names:
+ - test2.example.org
+ - test3.example.org
+ url: https://vault:8201
+ auth_method: token
+ token: "{{ login_data.login.auth.client_token }}"
+ register: cert_data
+
+- name: Display generated certificate
+ debug:
+ msg: "{{ cert_data.data.data.certificate }}"
+"""
+
+RETURN = """
+data:
+ description: Information about newly generated certificate
+ returned: success
+ type: complex
+ contains:
+ lease_id:
+ description: Vault lease attached to certificate.
+ returned: success
+ type: str
+ sample: pki/issue/test/7ad6cfa5-f04f-c62a-d477-f33210475d05
+ renewable:
+ description: True if certificate is renewable.
+ returned: success
+ type: bool
+ sample: false
+ lease_duration:
+ description: Vault lease duration.
+ returned: success
+ type: int
+ sample: 21600
+ data:
+ description: Payload
+ returned: success
+ type: complex
+ contains:
+ certificate:
+ description: Generated certificate.
+ returned: success
+ type: str
+ sample: "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"
+ issuing_ca:
+ description: CA certificate.
+ returned: success
+ type: str
+ sample: "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"
+ ca_chain:
+ description: Linked list of CA certificates.
+ returned: success
+ type: list
+ elements: str
+ sample: ["-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"]
+ private_key:
+ description: Private key used to generate certificate.
+ returned: success
+ type: str
+ sample: "-----BEGIN RSA PRIVATE KEY-----...-----END RSA PRIVATE KEY-----"
+ private_key_type:
+ description: Private key algorithm.
+ returned: success
+ type: str
+ sample: rsa
+ serial_number:
+ description: Certificate's serial number.
+ returned: success
+ type: str
+ sample: 39:dd:2e:90:b7:23:1f:8d:d3:7d:31:c5:1b:da:84:d0:5b:65:31:58
+ warning:
+ description: Warnings returned by Vault during generation.
+ returned: success
+ type: str
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+HAS_HVAC = False
+try:
+ import hvac
+ from hvac.api.secrets_engines.pki import DEFAULT_MOUNT_POINT
+except ImportError:
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+ HAS_HVAC = False
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ role_name=dict(type='str', required=True),
+ common_name=dict(type='str', required=True),
+ alt_names=dict(type='list', elements='str', required=False, default=[]),
+ ip_sans=dict(type='list', elements='str', required=False, default=[]),
+ uri_sans=dict(type='list', elements='str', required=False, default=[]),
+ other_sans=dict(type='list', elements='str', required=False, default=[]),
+ ttl=dict(type='str', required=False, default=None),
+ format=dict(type='str', required=False, choices=['pem', 'der', 'pem_bundle'], default='pem'),
+ private_key_format=dict(type='str', required=False, choices=['der', 'pkcs8'], default='der'),
+ exclude_cn_from_sans=dict(type='bool', required=False, default=False),
+ engine_mount_point=dict(type='str', required=False)
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(msg=missing_required_lib('hvac'), exception=HVAC_IMPORT_ERROR)
+
+ role_name = module.params.get('role_name')
+ common_name = module.params.get('common_name')
+ engine_mount_point = module.params.get('engine_mount_point') or DEFAULT_MOUNT_POINT
+
+ extra_params = {
+ 'alt_names': ','.join(module.params.get('alt_names')),
+ 'ip_sans': ','.join(module.params.get('ip_sans')),
+ 'uri_sans': ','.join(module.params.get('uri_sans')),
+ 'other_sans': ','.join(module.params.get('other_sans')),
+ 'ttl': module.params.get('ttl'),
+ 'format': module.params.get('format'),
+ 'private_key_format': module.params.get('private_key_format'),
+ 'exclude_cn_from_sans': module.params.get('exclude_cn_from_sans')
+ }
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ if module.check_mode:
+ data = {}
+ else:
+ data = client.secrets.pki.generate_certificate(
+ name=role_name, common_name=common_name,
+ extra_params=extra_params, mount_point=engine_mount_point
+ )
+ except hvac.exceptions.VaultError as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ # generate_certificate is a write operation which always return a new certificate
+ module.exit_json(changed=True, data=data)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_read.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_read.py
new file mode 100644
index 00000000..6b6b209d
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_read.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (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 = """
+ module: 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:
+ - 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.
+ - 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.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.attributes.check_mode_read_only
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ options:
+ path:
+ description: Vault path to be read.
+ type: str
+ required: True
+"""
+
+EXAMPLES = """
+- name: Read a kv2 secret from Vault via the remote host with userpass auth
+ community.hashi_vault.vault_read:
+ url: https://vault:8201
+ path: secret/data/hello
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: secret
+
+- name: Display the secret data
+ ansible.builtin.debug:
+ msg: "{{ secret.data.data.data }}"
+
+- name: Retrieve an approle role ID from Vault via the remote host
+ community.hashi_vault.vault_read:
+ url: https://vault:8201
+ path: auth/approle/role/role-name/role-id
+ register: approle_id
+
+- name: Display the role ID
+ ansible.builtin.debug:
+ msg: "{{ approle_id.data.data.role_id }}"
+"""
+
+RETURN = """
+data:
+ description: The raw result of the read against the given path.
+ returned: success
+ type: dict
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ path=dict(type='str', required=True),
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ path = module.params.get('path')
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ data = client.read(path)
+ except hvac.exceptions.Forbidden as e:
+ module.fail_json(msg="Forbidden: Permission Denied to path '%s'." % path, exception=traceback.format_exc())
+
+ if data is None:
+ module.fail_json(msg="The path '%s' doesn't seem to exist." % path)
+
+ module.exit_json(data=data)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_token_create.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_token_create.py
new file mode 100644
index 00000000..c2d19422
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_token_create.py
@@ -0,0 +1,223 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (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 = """
+ module: 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:
+ - ref: community.hashi_vault.vault_token_create lookup <ansible_collections.community.hashi_vault.vault_token_create_lookup>
+ description: The official documentation for the C(community.hashi_vault.vault_token_create) lookup plugin.
+ - module: community.hashi_vault.vault_login
+ - 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.
+ - 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.
+ extends_documentation_fragment:
+ - community.hashi_vault.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ - community.hashi_vault.token_create
+ - community.hashi_vault.wrapping
+ 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.
+ attributes:
+ check_mode:
+ support: partial
+ details:
+ - 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.
+ options: {}
+"""
+
+EXAMPLES = """
+- name: Login via userpass and create a child token
+ community.hashi_vault.vault_token_create:
+ url: https://vault:8201
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: token_data
+
+- 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 using the child token (token via direct dict access)
+ community.hashi_vault.vault_read:
+ url: https://vault:8201
+ auth_method: token
+ token: '{{ token_data.login.auth.client_token }}'
+ path: auth/approle/role/role-name/role-id
+ register: approle_id
+
+# implicitly uses token auth with a token from the environment
+- name: Create an orphaned token with a short TTL
+ community.hashi_vault.vault_token_create:
+ url: https://vault:8201
+ orphan: true
+ ttl: 60s
+ register: token_data
+
+- name: Display the full response
+ ansible.builtin.debug:
+ var: token_data.login
+"""
+
+RETURN = """
+login:
+ 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
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+
+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_module():
+ argspec = HashiVaultModule.generate_argspec(
+ orphan=dict(type='bool', default=False),
+ no_parent=dict(type='bool'),
+ no_default_policy=dict(type='bool'),
+ policies=dict(type='list', elements='str'),
+ id=dict(type='str'),
+ role_name=dict(type='str'),
+ meta=dict(type='dict'),
+ renewable=dict(type='bool'),
+ ttl=dict(type='str'),
+ type=dict(type='str', choices=['batch', 'service']),
+ explicit_max_ttl=dict(type='str'),
+ display_name=dict(type='str'),
+ num_uses=dict(type='int'),
+ period=dict(type='str'),
+ entity_alias=dict(type='str'),
+ wrap_ttl=dict(type='str'),
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ pass_thru_options = module.adapter.get_filled_options(*PASS_THRU_OPTION_NAMES)
+
+ orphan_options = pass_thru_options.copy()
+
+ for key in pass_thru_options.keys():
+ if key in ORPHAN_OPTION_TRANSLATION:
+ orphan_options[ORPHAN_OPTION_TRANSLATION[key]] = orphan_options.pop(key)
+
+ # token creation is a write operation, using storage and resources
+ changed = True
+ response = None
+
+ if module.check_mode:
+ module.exit_json(changed=changed, login={'auth': {'client_token': None}})
+
+ if module.adapter.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:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+ else:
+ try:
+ response = client.auth.token.create(**pass_thru_options)
+ except Exception as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ module.exit_json(changed=changed, login=response)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_write.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_write.py
new file mode 100644
index 00000000..35c7fcb6
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_write.py
@@ -0,0 +1,191 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (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 = """
+ module: 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.
+ notes:
+ - C(vault_write) is a generic module to do operations that do not yet have a dedicated module. Where a specific module exists, that should be used instead.
+ - The I(data) option is not treated as secret and may be logged. Use the C(no_log) keyword if I(data) contains sensitive values.
+ - This module always reports C(changed) status because it cannot guarantee idempotence.
+ - Use C(changed_when) to control that in cases where the operation is known to not change state.
+ attributes:
+ check_mode:
+ support: partial
+ details:
+ - In check mode, an empty response will be returned and the write will not be performed.
+ seealso:
+ - ref: community.hashi_vault.vault_write lookup <ansible_collections.community.hashi_vault.vault_write_lookup>
+ description: The official documentation for the C(community.hashi_vault.vault_write) lookup plugin.
+ - module: community.hashi_vault.vault_read
+ - 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.
+ extends_documentation_fragment:
+ - community.hashi_vault.attributes
+ - community.hashi_vault.attributes.action_group
+ - community.hashi_vault.connection
+ - community.hashi_vault.auth
+ - community.hashi_vault.wrapping
+ options:
+ path:
+ description: Vault path to be written to.
+ type: str
+ required: True
+ data:
+ description: A dictionary to be serialized to JSON and then sent as the request body.
+ type: dict
+ required: false
+ default: {}
+"""
+
+EXAMPLES = """
+- name: Write a value to the cubbyhole via the remote host with userpass auth
+ community.hashi_vault.vault_write:
+ url: https://vault:8201
+ path: cubbyhole/mysecret
+ data:
+ key1: val1
+ key2: val2
+ auth_method: userpass
+ username: user
+ password: '{{ passwd }}'
+ register: result
+
+- name: Display the result of the write (this can be empty)
+ ansible.builtin.debug:
+ msg: "{{ result.data }}"
+
+- name: Write secret to Vault using key value V2 engine
+ community.hashi_vault.vault_write:
+ path: secret/data/mysecret
+ data:
+ data:
+ key1: val1
+ key2: val2
+
+- name: Retrieve an approle role ID from Vault via the remote host
+ community.hashi_vault.vault_read:
+ url: https://vault:8201
+ path: auth/approle/role/role-name/role-id
+ register: approle_id
+
+- name: Generate a secret-id for the given approle
+ community.hashi_vault.vault_write:
+ url: https://vault:8201
+ path: auth/approle/role/role-name/secret-id
+ register: secret_id
+
+- name: Display the role ID and secret ID
+ ansible.builtin.debug:
+ msg:
+ - "role-id: {{ approle_id.data.data.role_id }}"
+ - "secret-id: {{ secret_id.data.data.secret_id }}"
+"""
+
+RETURN = """
+data:
+ description: The raw result of the write against the given path.
+ returned: success
+ type: dict
+"""
+
+import traceback
+
+from ansible.module_utils._text import to_native
+from ansible.module_utils.basic import missing_required_lib
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError
+
+try:
+ import hvac
+except ImportError:
+ HAS_HVAC = False
+ HVAC_IMPORT_ERROR = traceback.format_exc()
+else:
+ HVAC_IMPORT_ERROR = None
+ HAS_HVAC = True
+
+
+def run_module():
+ argspec = HashiVaultModule.generate_argspec(
+ path=dict(type='str', required=True),
+ data=dict(type='dict', required=False, default={}),
+ wrap_ttl=dict(type='str'),
+ )
+
+ module = HashiVaultModule(
+ argument_spec=argspec,
+ supports_check_mode=True
+ )
+
+ if not HAS_HVAC:
+ module.fail_json(
+ msg=missing_required_lib('hvac'),
+ exception=HVAC_IMPORT_ERROR
+ )
+
+ path = module.params.get('path')
+ data = module.params.get('data')
+ wrap_ttl = module.params.get('wrap_ttl')
+
+ module.connection_options.process_connection_options()
+ client_args = module.connection_options.get_hvac_connection_options()
+ client = module.helper.get_vault_client(**client_args)
+
+ try:
+ module.authenticator.validate()
+ module.authenticator.authenticate(client)
+ except (NotImplementedError, HashiVaultValueError) as e:
+ module.fail_json(msg=to_native(e), exception=traceback.format_exc())
+
+ try:
+ if module.check_mode:
+ response = {}
+ else:
+ response = client.write(path=path, wrap_ttl=wrap_ttl, **data)
+ except hvac.exceptions.Forbidden:
+ module.fail_json(msg="Forbidden: Permission Denied to path '%s'." % path, exception=traceback.format_exc())
+ except hvac.exceptions.InvalidPath:
+ module.fail_json(msg="The path '%s' doesn't seem to exist." % path, exception=traceback.format_exc())
+ except hvac.exceptions.InternalServerError as e:
+ module.fail_json(msg="Internal Server Error: %s" % to_native(e), exception=traceback.format_exc())
+
+ # 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:
+ module.warn('Vault returned status code %i and an unparsable body.' % response.status_code)
+ output = response.content
+ else:
+ output = response
+
+ module.exit_json(changed=True, data=output)
+
+
+def main():
+ run_module()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_lookup_base.py b/ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_lookup_base.py
new file mode 100644
index 00000000..5feb6fcf
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_lookup_base.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+# Copyright (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
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.errors import AnsibleError
+from ansible.plugins.lookup import LookupBase
+from ansible.utils.display import Display
+
+from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_plugin import HashiVaultPlugin
+
+display = Display()
+
+
+class HashiVaultLookupBase(HashiVaultPlugin, LookupBase):
+
+ def __init__(self, loader=None, templar=None, **kwargs):
+ HashiVaultPlugin.__init__(self)
+ LookupBase.__init__(self, loader=loader, templar=templar, **kwargs)
+
+ def parse_kev_term(self, term, plugin_name, first_unqualified=None):
+ '''parses a term string into a dictionary'''
+ param_dict = {}
+
+ for i, param in enumerate(term.split()):
+ try:
+ key, value = param.split('=', 1)
+ except ValueError:
+ if i == 0 and first_unqualified is not None:
+ # allow first item to be specified as value only and assign to assumed option name
+ key = first_unqualified
+ value = param
+ else:
+ raise AnsibleError("%s lookup plugin needs key=value pairs, but received %s" % (plugin_name, term))
+
+ param_dict[key] = value
+
+ return param_dict
diff --git a/ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_plugin.py b/ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_plugin.py
new file mode 100644
index 00000000..65c17c27
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/plugin_utils/_hashi_vault_plugin.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+# Copyright (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
+
+# FOR INTERNAL COLLECTION USE ONLY
+# The interfaces in this file are meant for use within the community.hashi_vault collection
+# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release.
+# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686
+# Please open an issue if you have questions about this.
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+from ansible.plugins import AnsiblePlugin
+from ansible import constants as C
+from ansible.utils.display import Display
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import (
+ HashiVaultHelper,
+ HashiVaultOptionAdapter,
+)
+
+from ansible_collections.community.hashi_vault.plugins.module_utils._connection_options import HashiVaultConnectionOptions
+from ansible_collections.community.hashi_vault.plugins.module_utils._authenticator import HashiVaultAuthenticator
+
+
+display = Display()
+
+
+class HashiVaultPlugin(AnsiblePlugin):
+ def __init__(self):
+ super(HashiVaultPlugin, self).__init__()
+
+ self.helper = HashiVaultHelper()
+ self._options_adapter = HashiVaultOptionAdapter.from_ansible_plugin(self)
+ self.connection_options = HashiVaultConnectionOptions(self._options_adapter, self._generate_retry_callback)
+ self.authenticator = HashiVaultAuthenticator(self._options_adapter, display.warning, display.deprecated)
+
+ def _generate_retry_callback(self, retry_action):
+ '''returns a Retry callback function for plugins'''
+ def _on_retry(retry_obj):
+ if retry_obj.total > 0:
+ if retry_action == 'warn':
+ display.warning('community.hashi_vault: %i %s remaining.' % (retry_obj.total, 'retry' if retry_obj.total == 1 else 'retries'))
+ else:
+ pass
+
+ return _on_retry
+
+ def process_deprecations(self, collection_name='community.hashi_vault'):
+ '''processes deprecations related to the collection'''
+
+ # TODO: this is a workaround for deprecations not being shown in lookups
+ # See:
+ # - https://github.com/ansible/ansible/issues/73051
+ # - https://github.com/ansible/ansible/pull/73058
+ # - https://github.com/ansible/ansible/pull/73239
+ # - https://github.com/ansible/ansible/pull/73240
+ #
+ # If a fix is backported to 2.9, this should be removed.
+ # Otherwise, we'll have to test with fixes that are available and see how we
+ # can determine whether to execute this conditionally.
+
+ # 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)