From 7fec0b69a082aaeec72fee0612766aa42f6b1b4d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 18 Apr 2024 07:52:35 +0200 Subject: Merging upstream version 9.4.0+dfsg. Signed-off-by: Daniel Baumann --- .../modules/vault_database_connection_configure.py | 193 +++++++++++++++++++ .../modules/vault_database_connection_delete.py | 146 +++++++++++++++ .../modules/vault_database_connection_read.py | 162 ++++++++++++++++ .../modules/vault_database_connection_reset.py | 145 +++++++++++++++ .../modules/vault_database_connections_list.py | 176 ++++++++++++++++++ .../plugins/modules/vault_database_role_create.py | 207 +++++++++++++++++++++ .../plugins/modules/vault_database_role_delete.py | 146 +++++++++++++++ .../plugins/modules/vault_database_role_read.py | 175 +++++++++++++++++ .../plugins/modules/vault_database_roles_list.py | 173 +++++++++++++++++ .../vault_database_rotate_root_credentials.py | 150 +++++++++++++++ .../modules/vault_database_static_role_create.py | 188 +++++++++++++++++++ .../vault_database_static_role_get_credentials.py | 167 +++++++++++++++++ .../modules/vault_database_static_role_read.py | 171 +++++++++++++++++ ...ault_database_static_role_rotate_credentials.py | 152 +++++++++++++++ .../modules/vault_database_static_roles_list.py | 174 +++++++++++++++++ .../hashi_vault/plugins/modules/vault_write.py | 19 +- 16 files changed, 2540 insertions(+), 4 deletions(-) create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_configure.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_delete.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_read.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_reset.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_connections_list.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_create.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_delete.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_read.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_roles_list.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_rotate_root_credentials.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_create.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_get_credentials.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_read.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_rotate_credentials.py create mode 100644 ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_roles_list.py (limited to 'ansible_collections/community/hashi_vault/plugins/modules') diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_configure.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_configure.py new file mode 100644 index 000000000..035f2c4f5 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_configure.py @@ -0,0 +1,193 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_connection_configure +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Configures the database engine +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 L(new database connection for a database secrets engine,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#configuration), + identified by its O(engine_mount_point) in HashiCorp Vault. +notes: + - The database needs to be created and available to connect before you can configure the database secrets engine using the above configure method. + - 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, a sample response will be returned, but the create / update will not be performed in Hashicorp Vault. +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: + connection_name: + description: Name of the database connection. + type: str + required: True + plugin_name: + description: Plugin name used to connect to the database + type: str + required: True + allowed_roles: + description: Allowed roles + type: list + elements: str + required: True + connection_url: + description: Connection URL to the database + type: str + required: True + connection_username: + description: Username to connect to the database + type: str + required: True + connection_password: + description: Password to connect to the database + type: str + required: True +""" + +EXAMPLES = r""" +- name: Create a new Database Connection with the default mount point + community.hashi_vault.vault_database_connection_configure: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: MyName + plugin_name: postgresql-database-plugin + connection_url: postgresql://{{'{{username}}'}}:{{'{{password}}'}}@postgres:5432/postgres?sslmode=disable + connection_username: SomeUser + connection_password: SomePass + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + + +- name: Create a new Database Connection with a custom mount point + community.hashi_vault.vault_database_connection_configure: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + connection_name: MyName + plugin_name: postgresql-database-plugin + connection_url: postgresql://{{'{{username}}'}}:{{'{{password}}'}}@postgres:5432/postgres?sslmode=disable + connection_username: SomeUser + connection_password: SomePass + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + plugin_name=dict(type="str", required=True), + allowed_roles=dict(type="list", required=True, elements="str"), + connection_name=dict(type="str", required=True), + connection_url=dict(type="str", required=True), + connection_username=dict(type="str", required=True), + connection_password=dict(type="str", required=True, no_log=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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["plugin_name"] = module.params.get("plugin_name") + parameters["allowed_roles"] = module.params.get("allowed_roles") + parameters["connection_url"] = module.params.get("connection_url") + parameters["name"] = module.params.get("connection_name") + parameters["username"] = module.params.get("connection_username") + parameters["password"] = module.params.get("connection_password") + + 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: + client.secrets.database.configure(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/config/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidRequest as e: + module.fail_json( + msg="Error creating database connection ['%s/config/%s']. Please analyze the traceback for further details." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_delete.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_delete.py new file mode 100644 index 000000000..4579d15f8 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_delete.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_connection_delete +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Delete a Database Connection +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: + - Deletes a L(Database Connection,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#delete-connection), + identified by its O(connection_name) from HashiCorp Vault. +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, a sample response will be returned, but the deletion will not be performed in Hashicorp Vault. +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: + connection_name: + description: The connection name to be deleted. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Delete a Database Connection with the default mount point + community.hashi_vault.vault_database_connection_delete: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Delete a Database Connection with a custom mount point + community.hashi_vault.vault_database_connection_delete: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + connection_name=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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("connection_name") + + 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: + client.secrets.database.delete_connection(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/config/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_read.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_read.py new file mode 100644 index 000000000..4d4fdb0f5 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_read.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_connection_read +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Returns the configuration settings for a O(connection_name) +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: + - L(Reads a Database Connection,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#read-configuration), + identified by its O(connection_name) from Hashcorp Vault. +notes: + - This module always reports C(changed) as False as it is a read operation that doesn't modify data. + - Use C(changed_when) to control that in cases where the operation is known to not change state. +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: + connection_name: + description: The connection name to be read. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Read a Database Connection with the default mount point + community.hashi_vault.vault_database_connection_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Read a Database Connection with a custom mount point + community.hashi_vault.vault_database_connection_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r""" +data: &data + description: The C(data) field of the RV(raw) result. This can also be accessed via RV(raw.data). + returned: success + type: dict + sample: &data_sample + allowed_roles: [] + connection_details: + connection_url: "postgresql://{{username}}:{{password}}@postgres:5432/postgres?sslmode=disable" + username: "UserName" + password_policy": "" + plugin_name": "postgresql-database-plugin" + plugin_version": "" + root_credentials_rotate_statements": [] +raw: + description: The raw result of the operation + returned: success + type: dict + contains: + data: *data + sample: + auth: null, + data: *data_sample +""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + connection_name=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) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("connection_name") + + 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.database.read_connection(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/config/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + + data = raw["data"] + module.exit_json(raw=raw, data=data, changed=False) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_reset.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_reset.py new file mode 100644 index 000000000..858fdfc7f --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connection_reset.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_connection_reset +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Closes a O(connection_name) and its underlying plugin and restarts it with the configuration stored +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: + - L(Resets a Database Connection,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#reset-connection). +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, a sample response will be returned, but the reset will not be performed in Hashicorp Vault. +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: + connection_name: + description: The connection name to be resetted. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Reset a Database Connection with the default mount point + community.hashi_vault.vault_database_connection_reset: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Reset a Database Connection with a custom mount point + community.hashi_vault.vault_database_connection_reset: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + connection_name=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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("connection_name") + + 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: + client.secrets.database.reset_connection(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/reset/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connections_list.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connections_list.py new file mode 100644 index 000000000..f570947d5 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_connections_list.py @@ -0,0 +1,176 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_connections_list +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Returns a list of available connections +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: + - L(List Database Connections,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#list-connections). +notes: + - This module always reports C(changed) as False as it is a read operation that doesn't modify data. + - Use C(changed_when) to control that in cases where the operation is known to not change state. +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 +""" + +EXAMPLES = r""" +- name: List Database Connections with the default mount point + community.hashi_vault.vault_database_connections_list: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: List Database Connections with a custom mount point + community.hashi_vault.vault_database_connections_list: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r""" +data: + description: The C(data) field of raw result. This can also be accessed via RV(raw.data). + returned: success + type: dict + contains: &data_contains + keys: + description: The list of database connections. + returned: success + type: list + elements: str + sample: &sample_connections ["role1", "role2", "role3"] + sample: + keys: *sample_connections +connections: + description: The list of database connections or en empty list. This can also be accessed via RV(data.keys) or RV(raw.data.keys). + returned: success + type: list + elements: str + sample: *sample_connections +raw: + description: The raw result of the operation. + returned: success + type: dict + contains: + data: + description: The data field of the API response. + returned: success + type: dict + contains: *data_contains + sample: + auth: null + data: + keys: *sample_connections + lease_duration": 0 + lease_id: "" + renewable: false + request_id: "123456" + 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_module import HashiVaultModule +from ..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", 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) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + + 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.database.list_connections(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/config']." + % (engine_mount_point or "database"), + exception=traceback.format_exc(), + ) + + data = raw.get("data", {"keys": []}) + connections = data["keys"] + module.exit_json( + raw=raw, + connections=connections, + data=data, + changed=False, + ) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_create.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_create.py new file mode 100644 index 000000000..c005e27e7 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_create.py @@ -0,0 +1,207 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_role_create +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Creates or updates a (dynamic) role definition +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: + - L(Creates or updates a dynamic role definition,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#create-static-role). +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, a sample response will be returned, but the role creation will not be performed in Hashicorp Vault. +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: + connection_name: + description: The connection name under which the role should be created. + type: str + required: True + role_name: + description: The name of the role that should be created. + type: str + required: True + creation_statements: + description: Specifies the database statements executed to create and configure a user. + type: list + required: True + elements: str + revocation_statements: + description: Specifies the database statements to be executed to revoke a user. + type: list + required: False + elements: str + rollback_statements: + description: Specifies the database statements to be executed to rollback a create operation in the event of an error. + type: list + required: False + elements: str + renew_statements: + description: Specifies the database statements to be executed to renew a user + type: list + required: False + elements: str + default_ttl: + description: Default TTL for the role. + type: int + required: False + default: 3600 + max_ttl: + description: Max TTL for the role. + type: int + required: False + default: 86400 +""" + +EXAMPLES = r""" +- name: Generate creation statement + ansible.builtin.set_fact: + creation_statements = [ + "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';", + "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" + ] + +- name: Create / update Role with the default mount point + community.hashi_vault.vault_database_role_create: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: SomeConnection + role_name: SomeRole + db_username: '{{ db_username}}' + creation_statements: '{{ creation_statements }}' + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Create / update Role with a custom mount point + community.hashi_vault.vault_database_role_create: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + connection_name: SomeConnection + role_name: SomeRole + db_username: '{{ db_username}}' + creation_statements: '{{ creation_statements }}' + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + connection_name=dict(type="str", required=True), + role_name=dict(type="str", required=True), + creation_statements=dict(type="list", required=True, elements="str"), + revocation_statements=dict(type="list", required=False, elements="str"), + rollback_statements=dict(type="list", required=False, elements="str"), + renew_statements=dict(type="list", required=False, elements="str"), + default_ttl=dict(type="int", required=False, default=3600), + max_ttl=dict(type="int", required=False, default=86400), + ) + + 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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = { + "name": module.params.get("role_name"), + "db_name": module.params.get("connection_name"), + "creation_statements": module.params.get("creation_statements"), + "revocation_statements": module.params.get("revocation_statements"), + "rollback_statements": module.params.get("rollback_statements"), + "renew_statements": module.params.get("renew_statements"), + "default_ttl": module.params.get("default_ttl"), + "max_ttl": module.params.get("max_ttl"), + } + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + + 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: + client.secrets.database.create_role(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/roles/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_delete.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_delete.py new file mode 100644 index 000000000..7f20e3bba --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_delete.py @@ -0,0 +1,146 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_role_delete +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Delete a role definition +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: + - L(Delete a role definition,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#delete-a-role). +notes: + - Applies to both static and dynamic roles. + - 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, a sample response will be returned, but the deletion will not be performed in Hashicorp Vault. +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: + role_name: + description: The name of the role to rotate credentials for. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Delete a Role with the default mount point + community.hashi_vault.vault_database_role_delete: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Delete a Role with a custom mount point + community.hashi_vault.vault_database_role_delete: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_path: db1 + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + role_name=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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("role_name") + + 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: + client.secrets.database.delete_role(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/roles/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_read.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_read.py new file mode 100644 index 000000000..68157dce1 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_role_read.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_role_read +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Queries a dynamic role definition +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: + - L(Queries a role definition,L(reads a static role,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#read-a-role), + identified by its O(role_name). +notes: + - This module always reports C(changed) as False as it is a read operation that doesn't modify data. + - Use C(changed_when) to control that in cases where the operation is known to not change state. +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: + role_name: + description: The role name to be read from Hashicorp Vault. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Read Role with a default mount point + community.hashi_vault.vault_database_role_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Read Static Role with a custom moint point + community.hashi_vault.vault_database_role_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +""" + +RETURN = r""" +data: &data + description: The C(data) field of raw result. This can also be accessed via RV(raw.data). + returned: success + type: dict + sample: &data_sample + creation_statements: [ + "CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';", + "GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" + ] + credential_type: "password" + db_name: "database" + default_ttl: 3600 + max_ttl: 86400 + renew_statements: [] + revocation_statements: [] + rollback_statements: [] +raw: + description: The raw result of the operation. + returned: success + type: dict + contains: + data: *data + sample: + auth: null + data: *data_sample + username: "SomeUser" + lease_duration": 0 + lease_id: "" + renewable: false + request_id: "123456" + 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_module import HashiVaultModule +from ..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", required=False), + role_name=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) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("role_name") + + 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.database.read_role(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/roles/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + + data = raw["data"] + + module.exit_json(data=data, raw=raw, changed=False) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_roles_list.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_roles_list.py new file mode 100644 index 000000000..a0dc7e472 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_roles_list.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_roles_list +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Returns a list of available (dynamic) roles +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: + - Returns a list of available (dynamic) roles. +notes: + - This API returns a member named C(keys). + - In Ansible, accessing RV(data.keys) or RV(raw.data.keys) will not work because the dict object contains a method named C(keys). + - Instead, use RV(roles) to access the list of roles, or use the syntax C(data["keys"]) or C(raw.data["keys"]) to access the list via dict member. +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 +""" + +EXAMPLES = r""" +- name: List all roles with the default mount point + community.hashi_vault.vault_database_roles_list: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: List all roles with a custom mount point + community.hashi_vault.vault_database_roles_list: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r""" +data: + description: The C(data) field of raw result. This can also be accessed via RV(raw.data). + returned: success + type: dict + contains: &data_contains + keys: + description: The list of dynamic role names. + returned: success + type: list + elements: str + sample: &sample_roles ["dyn_role1", "dyn_role2", "dyn_role3"] + sample: + keys: *sample_roles +roles: + description: The list of dynamic roles or en empty list. This can also be accessed via RV(data.keys) or RV(raw.data.keys). + returned: success + type: list + elements: str + sample: *sample_roles +raw: + description: The raw result of the operation. + returned: success + type: dict + contains: + data: + description: The data field of the API response. + returned: success + type: dict + contains: *data_contains + sample: + auth: null + data: + keys: *sample_roles + username: "SomeUser" + lease_duration": 0 + lease_id: "" + renewable: false + request_id: "123456" + 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_module import HashiVaultModule +from ..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", 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) + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + + 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.database.list_roles(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/roles']." + % (engine_mount_point or "database"), + exception=traceback.format_exc(), + ) + + data = raw.get("data", {"keys": []}) + roles = data["keys"] + + module.exit_json(data=data, roles=roles, raw=raw, changed=False) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_rotate_root_credentials.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_rotate_root_credentials.py new file mode 100644 index 000000000..f5b58de1e --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_rotate_root_credentials.py @@ -0,0 +1,150 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_rotate_root_credentials +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Rotates the root credentials stored for the database connection. This user must have permissions to update its own password. +requirements: + - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html)) >= 2.0.0 + - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements). +description: + - Trigger L(root credential rotation,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#rotate-root-credentials) + of a Database Connection identified by its O(connection_name). +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, a sample response will be returned, but the credential rotation will not be performed in Hashicorp Vault. +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: + connection_name: + description: The connection name where the root credential rotation should be triggered. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Trigger root credentiaL rotation with the default mount point + community.hashi_vault.vault_database_rotate_root_credentials: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Trigger root credential rotation with the default mount point + community.hashi_vault.vault_database_rotate_root_credentials: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + connection_name: SomeName + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + connection_name=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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("connection_name") + + 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: + client.secrets.database.rotate_root_credentials(**parameters) + except AttributeError as e: + module.fail_json( + msg="hvac>=2.0.0 is required", exception=traceback.format_exc() + ) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/rotate-root/%s']" + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_create.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_create.py new file mode 100644 index 000000000..5dd57ccae --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_create.py @@ -0,0 +1,188 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_static_role_create +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Create or update a static role +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: + - L(Creates a new or updates an existing static role,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#create-static-role). +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, a sample response will be returned, but the role creation will not be performed in Hashicorp Vault. +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: + connection_name: + description: The connection name under which the role should be created. + type: str + required: True + role_name: + description: The name of the role that should be created. + type: str + required: True + db_username: + description: The database username - Note that the user must exist in the target database! + type: str + required: True + rotation_statements: + description: SQL statements to rotate the password for the given O(db_username) + type: list + required: True + elements: str + rotation_period: + description: Password rotation period in seconds (defaults to 24hs) + type: int + required: False + default: 86400 +""" + +EXAMPLES = r""" +- name: Generate rotation statement + ansible.builtin.set_fact: + rotation_statements = ["ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';"] + +- name: Create / update Static Role with the default mount point + community.hashi_vault.vault_database_static_role_create: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + connection_name: SomeConnection + role_name: SomeRole + db_username: '{{ db_username}}' + rotation_statements: '{{ rotation_statements }}' + register: response + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Create / update Static Role with a custom mount point + community.hashi_vault.vault_database_static_role_create: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + connection_name: SomeConnection + role_name: SomeRole + db_username: '{{ db_username}}' + rotation_statements: '{{ rotation_statements }}' + register: response + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + connection_name=dict(type="str", required=True), + role_name=dict(type="str", required=True), + db_username=dict(type="str", required=True), + rotation_statements=dict(type="list", required=True, elements="str"), + rotation_period=dict(type="int", required=False, default=86400), + ) + + 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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["db_name"] = module.params.get("connection_name") + parameters["name"] = module.params.get("role_name") + parameters["username"] = module.params.get("db_username") + parameters["rotation_statements"] = module.params.get("rotation_statements") + rotation_period = module.params.get("rotation_period", None) + parameters["rotation_period"] = rotation_period + + 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: + client.secrets.database.create_static_role(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/static-roles/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidRequest as e: + module.fail_json( + msg="Cannot update static role ['%s/static-roles/%s']. Please verify that the user exists on the database." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_get_credentials.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_get_credentials.py new file mode 100644 index 000000000..a8a5cc9cc --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_get_credentials.py @@ -0,0 +1,167 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_static_role_get_credentials +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Returns the current credentials based on the named static role +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: + - Returns the + L(current credentials based of the named static role,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#get-static-credentials), + identified by its O(role_name). +notes: + - This module always reports C(changed) as False as it is a read operation that doesn't modify data. + - Use C(changed_when) to control that in cases where the operation is known to not change state. +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: + role_name: + description: The role name from which the credentials should be retrieved. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Returns the current credentials based on the named static role with the default mount point + community.hashi_vault.vault_database_static_role_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Returns the current credentials based on the named static role with a custom mount point + community.hashi_vault.vault_database_static_role_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r""" +data: &data + description: The C(data) field of raw result. This can also be accessed via RV(raw.data). + returned: success + type: dict + sample: &data_sample + last_vault_rotation": "2024-01-01T09:00:00+01:00" + password": "Th3_$3cr3t_P@ss!" + rotation_period": 86400 + ttl": 123456 + username: "SomeUser" +raw: + description: The raw result of the operation. + returned: success + type: dict + contains: + data: *data + sample: + auth: null, + data: *data_sample + lease_duration: 0 + lease_id: "" + renewable: false + request_id: "123456" + 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_module import HashiVaultModule +from ..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", required=False), + role_name=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) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("role_name") + + 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.database.get_static_credentials(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point, + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/static-creds/%s']" + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + + data = raw["data"] + + module.exit_json(data=data, raw=raw, changed=False) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_read.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_read.py new file mode 100644 index 000000000..f2ad972b6 --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_read.py @@ -0,0 +1,171 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_static_role_read +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Queries a static role definition +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: + - L(Queries a static role definition,L(reads a static role,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#read-static-role), + - identified by its O(role_name) +notes: + - This module always reports C(changed) as False as it is a read operation that doesn't modify data. + - Use C(changed_when) to control that in cases where the operation is known to not change state. +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: + role_name: + description: The role name to be read from Hashicorp Vault. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Read Static Role with a default mount point + community.hashi_vault.vault_database_static_role_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Read Static Role with a custom moint point + community.hashi_vault.vault_database_static_role_read: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +""" + +RETURN = r""" +data: &data + description: The C(data) field of raw result. This can also be accessed via RV(raw.data). + returned: success + type: dict + sample: &data_sample + credential_type: "password" + db_name: "SomeConnection" + last_vault_rotation": "2024-01-01T09:00:00 +01:00" + rotation_period": 86400 + rotation_statements": [ + "ALTER USER \"{{name}}\" WITH PASSWORD '{{password}}';" + ] +raw: + description: The raw result of the operation. + returned: success + type: dict + contains: + data: *data + sample: + auth: null + data: *data_sample + username: "SomeUser" + lease_duration": 0 + lease_id: "" + renewable: false + request_id: "123456" + 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_module import HashiVaultModule +from ..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", required=False), + role_name=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) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("role_name") + + 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.database.read_static_role(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/static-roles/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + + data = raw["data"] + + module.exit_json(data=data, raw=raw, changed=False) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_rotate_credentials.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_rotate_credentials.py new file mode 100644 index 000000000..660725b0b --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_role_rotate_credentials.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_static_role_rotate_credentials +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Trigger the credential rotation for a static role +requirements: + - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html)) >= 2.0.0 + - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements). +description: + - This endpoint is used to + - L(rotate the Static Role credentials,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#rotate-static-role-credentials) + - stored for a given role name. While Static Roles are rotated automatically by Vault at configured rotation periods, + - users can use this endpoint to manually trigger a rotation to change the stored password and reset the TTL of the Static Role's password. +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, a sample response will be returned, but the credential rotation will not be performed in Hashicorp Vault. +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: + role_name: + description: The name of the role to rotate credentials for. + type: str + required: True +""" + +EXAMPLES = r""" +- name: Rotate credentials of a static role with the default mount point + community.hashi_vault.vault_database_static_role_rotate_credentials: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" + +- name: Rotate credentials of a static role with a custom mount point + community.hashi_vault.vault_database_static_role_rotate_credentials: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + role_name: SomeRole + register: result + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ result }}" +""" + +RETURN = r"""""" + +import traceback + +from ansible.module_utils._text import to_native +from ansible.module_utils.basic import missing_required_lib + +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..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", required=False), + role_name=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) + + if module.check_mode is True: + module.exit_json(changed=True) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + parameters["name"] = module.params.get("role_name") + + 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.database.rotate_static_role_credentials(**parameters) + except AttributeError as e: + module.fail_json( + msg="hvac>=2.0.0 is required", exception=traceback.format_exc() + ) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/rotate-role/%s']." + % (engine_mount_point or "database", parameters["name"]), + exception=traceback.format_exc(), + ) + else: + module.exit_json(changed=True) + + +def main(): + run_module() + + +if __name__ == "__main__": + main() diff --git a/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_roles_list.py b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_roles_list.py new file mode 100644 index 000000000..36a35337e --- /dev/null +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_database_static_roles_list.py @@ -0,0 +1,174 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# (c) 2024, Martin Chmielewski (@M4rt1nCh) +# 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_database_static_roles_list +version_added: 6.2.0 +author: + - Martin Chmielewski (@M4rt1nCh) +short_description: Returns a list of available static roles +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: + - L(Returns a list of available static roles,https://hvac.readthedocs.io/en/stable/usage/secrets_engines/database.html#list-static-roles). +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 +notes: + - This API returns a member named C(keys). + - In Ansible, accessing RV(data.keys) or RV(raw.data.keys) will not work because the dict object contains a method named C(keys). + - Instead, use RV(roles) to access the list of roles, or use the syntax C(data["keys"]) or C(raw.data["keys"]) to access the list via dict member. +""" + +EXAMPLES = r""" +- name: List static roles with the default mount point + community.hashi_vault.vault_database_static_roles_list: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + register: response + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ response }}" + +- name: List static roles with a custom mount point + community.hashi_vault.vault_database_static_roles_list: + url: https://vault:8201 + auth_method: userpass + username: '{{ user }}' + password: '{{ passwd }}' + engine_mount_point: db1 + register: response + +- name: Display the result of the operation + ansible.builtin.debug: + msg: "{{ response }}" +""" + +RETURN = r""" +data: + description: The C(data) field of raw result. This can also be accessed via RV(raw.data). + returned: success + type: dict + contains: &data_contains + keys: + description: The list of role names. + returned: success + type: list + elements: str + sample: &sample_roles ["role1", "role2", "role3"] + sample: + keys: *sample_roles +roles: + description: The list of roles or en empty list. This can also be accessed via RV(data.keys) or RV(raw.data.keys). + returned: success + type: list + elements: str + sample: *sample_roles +raw: + description: The raw result of the operation. + returned: success + type: dict + contains: + data: + description: The data field of the API response. + returned: success + type: dict + contains: *data_contains + sample: + auth: null + data: + keys: *sample_roles + username: "SomeUser" + lease_duration": 0 + lease_id: "" + renewable: false + request_id: "123456" + 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_module import HashiVaultModule +from ..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", 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) + + parameters = {} + engine_mount_point = module.params.get("engine_mount_point", None) + if engine_mount_point is not None: + parameters["mount_point"] = engine_mount_point + + 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.database.list_static_roles(**parameters) + except hvac.exceptions.Forbidden as e: + module.fail_json( + msg="Forbidden: Permission Denied to path ['%s']." % engine_mount_point + or "database", + exception=traceback.format_exc(), + ) + except hvac.exceptions.InvalidPath as e: + module.fail_json( + msg="Invalid or missing path ['%s/static-roles']." + % (engine_mount_point or "database"), + exception=traceback.format_exc(), + ) + + data = raw.get("data", {"keys": []}) + roles = data["keys"] + + module.exit_json(data=data, roles=roles, raw=raw, changed=False) + + +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 index 35c7fcb60..76cf4f8e9 100644 --- a/ansible_collections/community/hashi_vault/plugins/modules/vault_write.py +++ b/ansible_collections/community/hashi_vault/plugins/modules/vault_write.py @@ -46,7 +46,9 @@ DOCUMENTATION = """ type: str required: True data: - description: A dictionary to be serialized to JSON and then sent as the request body. + description: + - A dictionary to be serialized to JSON and then sent as the request body. + - If the dictionary contains keys named C(path) or C(wrap_ttl), the call will fail with C(hvac<1.2). type: dict required: false default: {} @@ -108,8 +110,8 @@ 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 +from ..module_utils._hashi_vault_module import HashiVaultModule +from ..module_utils._hashi_vault_common import HashiVaultValueError try: import hvac @@ -157,7 +159,16 @@ def run_module(): if module.check_mode: response = {} else: - response = client.write(path=path, wrap_ttl=wrap_ttl, **data) + try: + # TODO: write_data will eventually turn back into write + # see: https://github.com/hvac/hvac/issues/1034 + response = client.write_data(path=path, wrap_ttl=wrap_ttl, data=data) + except AttributeError: + # https://github.com/ansible-collections/community.hashi_vault/issues/389 + if "path" in data or "wrap_ttl" in data: + module.fail_json("To use 'path' or 'wrap_ttl' as data keys, use hvac >= 1.2") + 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: -- cgit v1.2.3