summaryrefslogtreecommitdiffstats
path: root/ansible_collections/community/hashi_vault/plugins/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/community/hashi_vault/plugins/modules
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/community/hashi_vault/plugins/modules')
-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.py180
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_get.py213
-rw-r--r--ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_write.py278
-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
10 files changed, 2022 insertions, 0 deletions
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 000000000..e21f4a813
--- /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 000000000..3145e4a5d
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_delete.py
@@ -0,0 +1,180 @@
+#!/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
+ - module: community.hashi_vault.vault_kv2_write
+ - 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 000000000..04a549d59
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_get.py
@@ -0,0 +1,213 @@
+#!/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
+ - module: community.hashi_vault.vault_kv2_write
+ - 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_kv2_write.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_write.py
new file mode 100644
index 000000000..d226987c6
--- /dev/null
+++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_kv2_write.py
@@ -0,0 +1,278 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# (c) 2023, Devon Mar (@devon-mar)
+# 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_write
+version_added: 4.2.0
+author:
+ - Devon Mar (@devon-mar)
+short_description: Perform a write operation against a KVv2 secret in HashiCorp Vault
+description:
+ - Perform a write operation against a KVv2 secret in 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).
+seealso:
+ - module: community.hashi_vault.vault_write
+ - module: community.hashi_vault.vault_kv2_get
+ - module: community.hashi_vault.vault_kv2_delete
+ - 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.
+ - 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
+attributes:
+ check_mode:
+ support: partial
+ details:
+ - If I(read_before_write) is C(true), full check mode functionality is supported.
+ - If I(read_before_write) is C(false), the status will always be C(changed) but a write will not be performed in check mode.
+options:
+ engine_mount_point:
+ type: str
+ default: secret
+ path:
+ type: str
+ required: true
+ description:
+ - Vault KVv2 path to be written to.
+ - This is relative to the I(engine_mount_point), so the mount path should not be included.
+ data:
+ type: dict
+ required: true
+ description:
+ - KVv2 secret data to write.
+ cas:
+ type: int
+ description:
+ - Perform a check-and-set operation.
+ read_before_write:
+ type: bool
+ default: false
+ description:
+ - Read the secret first and write only when I(data) differs from the read data.
+ - Requires C(read) permission on the secret if C(true).
+ - If C(false), this module will always write to I(path) when not in check mode.
+"""
+
+EXAMPLES = r"""
+- name: Write/create a secret
+ community.hashi_vault.vault_kv2_write:
+ url: https://vault:8200
+ path: hello
+ data:
+ foo: bar
+
+- name: Create a secret with CAS (the secret must not exist)
+ community.hashi_vault.vault_kv2_write:
+ url: https://vault:8200
+ path: caspath
+ cas: 0
+ data:
+ foo: bar
+
+- name: Update a secret with CAS
+ community.hashi_vault.vault_kv2_write:
+ url: https://vault:8200
+ path: caspath
+ cas: 2
+ data:
+ hello: world
+
+# This module does not have patch capability built in.
+# Patching can be achieved with multiple tasks.
+
+- name: Retrieve current secret
+ register: current
+ community.hashi_vault.vault_kv2_get:
+ url: https://vault:8200
+ path: hello
+
+## patch without CAS
+- name: Update the secret
+ vars:
+ values_to_update:
+ foo: baz
+ hello: goodbye
+ community.hashi_vault.vault_kv2_write:
+ url: https://vault:8200
+ path: hello
+ data: >-
+ {{
+ current.secret
+ | combine(values_to_update)
+ }}
+
+## patch with CAS
+- name: Update the secret
+ vars:
+ values_to_update:
+ foo: baz
+ hello: goodbye
+ community.hashi_vault.vault_kv2_write:
+ url: https://vault:8200
+ path: hello
+ cas: '{{ current.metadata.version | int }}'
+ data: >-
+ {{
+ current.secret
+ | combine(values_to_update)
+ }}
+"""
+
+RETURN = r"""
+raw:
+ type: dict
+ description: The raw Vault response.
+ returned: changed
+ sample:
+ auth:
+ data:
+ created_time: "2023-02-21T19:51:50.801757862Z"
+ custom_metadata:
+ deletion_time: ""
+ destroyed: false
+ version: 1
+ lease_duration: 0
+ lease_id: ""
+ renewable: false
+ request_id: 52eb1aa7-5a38-9a02-9246-efc5bf9581ec
+ 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 ..module_utils._hashi_vault_common import (
+ HashiVaultValueError,
+)
+from ..module_utils._hashi_vault_module import (
+ HashiVaultModule,
+)
+
+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),
+ data=dict(type="dict", required=True, no_log=True),
+ cas=dict(type="int"),
+ read_before_write=dict(type="bool", default=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,
+ )
+
+ mount_point = module.params.get("engine_mount_point")
+ path = module.params.get("path")
+ cas = module.params.get("cas")
+ data = module.params.get("data")
+ read_before_write = module.params.get("read_before_write")
+
+ 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())
+
+ if read_before_write is True:
+ try:
+ response = client.secrets.kv.v2.read_secret_version(
+ path=path, mount_point=mount_point
+ )
+ if "data" not in response or "data" not in response["data"]:
+ module.fail_json(
+ msg="Vault response did not contain data: %s" % response
+ )
+ current_data = response["data"]["data"]
+ except hvac.exceptions.InvalidPath:
+ current_data = {}
+ except hvac.exceptions.Forbidden:
+ module.fail_json(
+ msg="Permission denied reading %s" % path,
+ exception=traceback.format_exc(),
+ )
+ except hvac.exceptions.VaultError:
+ module.fail_json(
+ msg="VaultError reading %s" % path,
+ exception=traceback.format_exc(),
+ )
+ else:
+ current_data = {}
+
+ changed = current_data != data
+
+ if changed is True and module.check_mode is False:
+ args = {
+ "path": path,
+ "secret": data,
+ "mount_point": mount_point,
+ }
+ if cas is not None:
+ args["cas"] = cas
+
+ try:
+ raw = client.secrets.kv.v2.create_or_update_secret(**args)
+ except hvac.exceptions.InvalidRequest:
+ module.fail_json(
+ msg="InvalidRequest writing to '%s'" % path,
+ exception=traceback.format_exc(),
+ )
+ except hvac.exceptions.InvalidPath:
+ module.fail_json(
+ msg="InvalidPath writing to '%s'" % path,
+ exception=traceback.format_exc(),
+ )
+ except hvac.exceptions.Forbidden:
+ module.fail_json(
+ msg="Permission denied writing to '%s'" % path,
+ exception=traceback.format_exc(),
+ )
+
+ module.exit_json(changed=True, raw=raw)
+
+ module.exit_json(changed=changed)
+
+
+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 000000000..a0823dc2d
--- /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 000000000..fe0408da2
--- /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 ...plugins.module_utils._hashi_vault_module import HashiVaultModule
+from ...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 # pylint: disable=unused-import
+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 000000000..66b9190b4
--- /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 000000000..6b6b209d5
--- /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 000000000..c2d19422f
--- /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 000000000..35c7fcb60
--- /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()