From 06daf8c640bc8fbdbffdeaef4c82d3307995c6ff Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 20 Aug 2022 11:42:34 -0400 Subject: [PATCH 01/45] Sanity/use before assignment (#297) * fix use before assignment false positive * add changelog fragment --- changelogs/fragments/296-use-before-assignment.yml | 3 +++ plugins/modules/vault_kv1_get.py | 1 + plugins/modules/vault_kv2_get.py | 1 + plugins/modules/vault_login.py | 1 + plugins/modules/vault_pki_generate_certificate.py | 4 +++- plugins/modules/vault_read.py | 1 + plugins/modules/vault_write.py | 1 + 7 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/296-use-before-assignment.yml diff --git a/changelogs/fragments/296-use-before-assignment.yml b/changelogs/fragments/296-use-before-assignment.yml new file mode 100644 index 000000000..5bdd33f4e --- /dev/null +++ b/changelogs/fragments/296-use-before-assignment.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - modules - fix a "variable used before assignment" that cannot be reached but causes sanity test failures (https://github.com/ansible-collections/community.hashi_vault/issues/296). diff --git a/plugins/modules/vault_kv1_get.py b/plugins/modules/vault_kv1_get.py index 348447411..667a11378 100644 --- a/plugins/modules/vault_kv1_get.py +++ b/plugins/modules/vault_kv1_get.py @@ -135,6 +135,7 @@ HAS_HVAC = False HVAC_IMPORT_ERROR = traceback.format_exc() else: + HVAC_IMPORT_ERROR = None HAS_HVAC = True diff --git a/plugins/modules/vault_kv2_get.py b/plugins/modules/vault_kv2_get.py index d1f03787d..343da2658 100644 --- a/plugins/modules/vault_kv2_get.py +++ b/plugins/modules/vault_kv2_get.py @@ -149,6 +149,7 @@ HAS_HVAC = False HVAC_IMPORT_ERROR = traceback.format_exc() else: + HVAC_IMPORT_ERROR = None HAS_HVAC = True diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index 34a284168..46df8e0e6 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -112,6 +112,7 @@ HAS_HVAC = False HVAC_IMPORT_ERROR = traceback.format_exc() else: + HVAC_IMPORT_ERROR = None HAS_HVAC = True diff --git a/plugins/modules/vault_pki_generate_certificate.py b/plugins/modules/vault_pki_generate_certificate.py index 53e36ad03..b67d1231e 100644 --- a/plugins/modules/vault_pki_generate_certificate.py +++ b/plugins/modules/vault_pki_generate_certificate.py @@ -206,10 +206,12 @@ try: import hvac from hvac.api.secrets_engines.pki import DEFAULT_MOUNT_POINT - HAS_HVAC = True except ImportError: HVAC_IMPORT_ERROR = traceback.format_exc() HAS_HVAC = False +else: + HVAC_IMPORT_ERROR = None + HAS_HVAC = True def run_module(): diff --git a/plugins/modules/vault_read.py b/plugins/modules/vault_read.py index 0e0e90e38..5bbda8dec 100644 --- a/plugins/modules/vault_read.py +++ b/plugins/modules/vault_read.py @@ -79,6 +79,7 @@ HAS_HVAC = False HVAC_IMPORT_ERROR = traceback.format_exc() else: + HVAC_IMPORT_ERROR = None HAS_HVAC = True diff --git a/plugins/modules/vault_write.py b/plugins/modules/vault_write.py index 54c56cdd0..e0833da94 100644 --- a/plugins/modules/vault_write.py +++ b/plugins/modules/vault_write.py @@ -110,6 +110,7 @@ HAS_HVAC = False HVAC_IMPORT_ERROR = traceback.format_exc() else: + HVAC_IMPORT_ERROR = None HAS_HVAC = True From 2939aadd4ab04a1af9c1a95598788a93a7e13004 Mon Sep 17 00:00:00 2001 From: Junrui Chen Date: Mon, 22 Aug 2022 10:03:11 +1000 Subject: [PATCH 02/45] Add support for azure auth method (#293) * Add support for azure auth method * fix unit tests * add integration tests * add changelog fragment, fix copyright, add version_added * fix sanity tests * Apply suggestions from code review * add env, ini, and vars plugins entries for azure auth options * make role_id explicitly required * validate method cleanup, unit test additions * changelog nits * docs and requirements * add more copyright/license headers * fix sanity line length * Update plugins/doc_fragments/auth.py forgot to commit this one before * remove unused fixture Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> --- .../293-support-azure-auth-method.yml | 3 + codecov.yml | 4 + docs/docsite/rst/user_guide.rst | 3 +- meta/ee-requirements.txt | 2 + plugins/doc_fragments/auth.py | 62 ++++- plugins/module_utils/_auth_method_azure.py | 105 ++++++++ plugins/module_utils/_authenticator.py | 7 + tests/integration/requirements.txt | 4 + tests/integration/targets/auth_azure/aliases | 2 + .../targets/auth_azure/defaults/main.yml | 10 + .../targets/auth_azure/meta/main.yml | 7 + .../tasks/azure_test_controller.yml | 48 ++++ .../auth_azure/tasks/azure_test_target.yml | 54 +++++ .../targets/auth_azure/tasks/main.yml | 26 ++ .../mmock/azure_login_alt_mount.yml.j2 | 46 ++++ .../mmock/azure_login_bad_request.yml.j2 | 22 ++ .../mmock/azure_login_default_mount.yml.j2 | 46 ++++ tests/unit/fixtures/azure_login_response.json | 33 +++ .../authentication/test_auth_azure.py | 224 ++++++++++++++++++ tests/unit/requirements.txt | 5 +- 20 files changed, 710 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/293-support-azure-auth-method.yml create mode 100644 plugins/module_utils/_auth_method_azure.py create mode 100644 tests/integration/targets/auth_azure/aliases create mode 100644 tests/integration/targets/auth_azure/defaults/main.yml create mode 100644 tests/integration/targets/auth_azure/meta/main.yml create mode 100644 tests/integration/targets/auth_azure/tasks/azure_test_controller.yml create mode 100644 tests/integration/targets/auth_azure/tasks/azure_test_target.yml create mode 100644 tests/integration/targets/auth_azure/tasks/main.yml create mode 100644 tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_alt_mount.yml.j2 create mode 100644 tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_bad_request.yml.j2 create mode 100644 tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_default_mount.yml.j2 create mode 100644 tests/unit/fixtures/azure_login_response.json create mode 100644 tests/unit/plugins/module_utils/authentication/test_auth_azure.py diff --git a/changelogs/fragments/293-support-azure-auth-method.yml b/changelogs/fragments/293-support-azure-auth-method.yml new file mode 100644 index 000000000..33fa4db94 --- /dev/null +++ b/changelogs/fragments/293-support-azure-auth-method.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - community.hashi_vault collection - add support for ``azure`` auth method, for Azure service pricinpal, managed identity, or plain JWT access token (https://github.com/ansible-collections/community.hashi_vault/issues/293). diff --git a/codecov.yml b/codecov.yml index bb14293b7..8dafd8ba1 100644 --- a/codecov.yml +++ b/codecov.yml @@ -68,6 +68,10 @@ flags: paths: - plugins/module_utils/_auth_method_aws_iam.py + target_auth_azure: + paths: + - plugins/module_utils/_auth_method_azure.py + target_auth_cert: paths: - plugins/module_utils/_auth_method_cert.py diff --git a/docs/docsite/rst/user_guide.rst b/docs/docsite/rst/user_guide.rst index fe997e579..a3f417800 100644 --- a/docs/docsite/rst/user_guide.rst +++ b/docs/docsite/rst/user_guide.rst @@ -31,7 +31,7 @@ The content in ``community.hashi_vault`` requires the `hvac = 1.15 boto3 # these are only needed if inferring AWS credentials or botocore # using a boto profile; including for completeness + +azure-identity # only needed when using a servide principal or managed identity diff --git a/plugins/doc_fragments/auth.py b/plugins/doc_fragments/auth.py index b02436d35..a13dc71c4 100644 --- a/plugins/doc_fragments/auth.py +++ b/plugins/doc_fragments/auth.py @@ -18,12 +18,14 @@ class ModuleDocFragment(object): - C(none) auth method was added in collection version C(1.2.0). - C(cert) auth method was added in collection version C(1.4.0). - C(aws_iam_login) was renamed C(aws_iam) in collection version C(2.1.0) and was removed in C(3.0.0). + - C(azure) auth method was added in collection version C(3.2.0). choices: - token - userpass - ldap - approle - aws_iam + - azure - jwt - cert - none @@ -64,8 +66,9 @@ class ModuleDocFragment(object): type: str role_id: description: - - Vault Role ID or name. Used in C(approle), C(aws_iam), and C(cert) auth methods. + - Vault Role ID or name. Used in C(approle), C(aws_iam), C(azure) and C(cert) auth methods. - For C(cert) auth, if no I(role_id) is supplied, the default behavior is to try all certificate roles and return any one that matches. + - For C(azure) auth, I(role_id) is required. type: str secret_id: description: Secret ID to be used for Vault AppRole authentication. @@ -96,6 +99,34 @@ class ModuleDocFragment(object): required: False type: str version_added: '0.2.0' + azure_tenant_id: + description: + - The Azure Active Directory Tenant ID (also known as the Directory ID) of the service principal. Should be a UUID. + - >- + Required when using a service principal to authenticate to Vault, + e.g. required when both I(azure_client_id) and I(azure_client_secret) are specified. + - Optional when using managed identity to authenticate to Vault. + required: False + type: str + version_added: '3.2.0' + azure_client_id: + description: + - The client ID (also known as application ID) of the Azure AD service principal or managed identity. Should be a UUID. + - If not specified, will use the system assigned managed identity. + required: False + type: str + version_added: '3.2.0' + azure_client_secret: + description: The client secret of the Azure AD service principal. + required: False + type: str + version_added: '3.2.0' + azure_resource: + description: The resource URL for the application registered in Azure Active Directory. Usually should not be changed from the default. + required: False + type: str + default: https://management.azure.com/ + version_added: '3.2.0' cert_auth_public_key: description: For C(cert) auth, path to the certificate file to authenticate with, in PEM format. type: path @@ -234,6 +265,35 @@ class ModuleDocFragment(object): - section: hashi_vault_collection key: aws_iam_server_id version_added: 1.4.0 + azure_tenant_id: + env: + - name: ANSIBLE_HASHI_VAULT_AZURE_TENANT_ID + ini: + - section: hashi_vault_collection + key: azure_tenant_id + vars: + - name: ansible_hashi_vault_azure_tenant_id + azure_client_id: + env: + - name: ANSIBLE_HASHI_VAULT_AZURE_CLIENT_ID + ini: + - section: hashi_vault_collection + key: azure_client_id + vars: + - name: ansible_hashi_vault_azure_client_id + azure_client_secret: + env: + - name: ANSIBLE_HASHI_VAULT_AZURE_CLIENT_SECRET + vars: + - name: ansible_hashi_vault_azure_client_secret + azure_resource: + env: + - name: ANSIBLE_HASHI_VAULT_AZURE_RESOURCE + ini: + - section: hashi_vault_collection + key: azure_resource + vars: + - name: ansible_hashi_vault_azure_resource cert_auth_public_key: env: - name: ANSIBLE_HASHI_VAULT_CERT_AUTH_PUBLIC_KEY diff --git a/plugins/module_utils/_auth_method_azure.py b/plugins/module_utils/_auth_method_azure.py new file mode 100644 index 000000000..36f44e07c --- /dev/null +++ b/plugins/module_utils/_auth_method_azure.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Junrui Chen (@jchenship) +# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) +# SPDX-License-Identifier: BSD-2-Clause + +'''Python versions supported: >=3.6''' + +# FOR INTERNAL COLLECTION USE ONLY +# The interfaces in this file are meant for use within the community.hashi_vault collection +# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release. +# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686 +# Please open an issue if you have questions about this. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import ( + HashiVaultAuthMethodBase, + HashiVaultValueError, +) + + +class HashiVaultAuthMethodAzure(HashiVaultAuthMethodBase): + '''HashiVault auth method for Azure''' + + NAME = 'azure' + OPTIONS = [ + 'role_id', + 'jwt', + 'mount_point', + 'azure_tenant_id', + 'azure_client_id', + 'azure_client_secret', + 'azure_resource', + ] + + def __init__(self, option_adapter, warning_callback, deprecate_callback): + super(HashiVaultAuthMethodAzure, self).__init__( + option_adapter, warning_callback, deprecate_callback + ) + + def validate(self): + params = { + 'role': self._options.get_option_default('role_id'), + 'jwt': self._options.get_option_default('jwt'), + } + if not params['role']: + raise HashiVaultValueError( + 'role_id is required for azure authentication.' + ) + + # if mount_point is not provided, it will use the default value defined + # in hvac library (e.g. `azure`) + mount_point = self._options.get_option_default('mount_point') + if mount_point: + params['mount_point'] = mount_point + + # if jwt exists, use provided jwt directly, otherwise trying to get jwt + # from azure service principal or managed identity + if not params['jwt']: + azure_tenant_id = self._options.get_option_default('azure_tenant_id') + azure_client_id = self._options.get_option_default('azure_client_id') + azure_client_secret = self._options.get_option_default('azure_client_secret') + + # the logic of getting azure scope is from this function + # https://github.com/Azure/azure-cli/blob/azure-cli-2.39.0/src/azure-cli-core/azure/cli/core/auth/util.py#L72 + # the reason we expose resource instead of scope is resource is + # more aligned with the vault azure auth config here + # https://www.vaultproject.io/api-docs/auth/azure#resource + azure_resource = self._options.get_option('azure_resource') + azure_scope = azure_resource + "/.default" + + try: + import azure.identity + except ImportError: + raise HashiVaultValueError( + "azure-identity is required for getting access token from azure service principal or managed identity." + ) + + if azure_client_id and azure_client_secret: + # service principal + if not azure_tenant_id: + raise HashiVaultValueError( + 'azure_tenant_id is required when using azure service principal.' + ) + azure_credentials = azure.identity.ClientSecretCredential( + azure_tenant_id, azure_client_id, azure_client_secret + ) + elif azure_client_id: + # user assigned managed identity + azure_credentials = azure.identity.ManagedIdentityCredential( + client_id=azure_client_id + ) + else: + # system assigned managed identity + azure_credentials = azure.identity.ManagedIdentityCredential() + + params['jwt'] = azure_credentials.get_token(azure_scope).token + + self._auth_azure_login_params = params + + def authenticate(self, client, use_token=True): + params = self._auth_azure_login_params + response = client.auth.azure.login(use_token=use_token, **params) + return response diff --git a/plugins/module_utils/_authenticator.py b/plugins/module_utils/_authenticator.py index 85d03d12d..2292d5b6b 100644 --- a/plugins/module_utils/_authenticator.py +++ b/plugins/module_utils/_authenticator.py @@ -17,6 +17,7 @@ # please keep this list in alphabetical order of auth method name from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_approle import HashiVaultAuthMethodApprole from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_aws_iam import HashiVaultAuthMethodAwsIam +from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_azure import HashiVaultAuthMethodAzure from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_cert import HashiVaultAuthMethodCert from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_jwt import HashiVaultAuthMethodJwt from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_ldap import HashiVaultAuthMethodLdap @@ -33,6 +34,7 @@ class HashiVaultAuthenticator(): 'ldap', 'approle', 'aws_iam', + 'azure', 'jwt', 'cert', 'none', @@ -54,6 +56,10 @@ class HashiVaultAuthenticator(): aws_security_token=dict(type='str', no_log=False), region=dict(type='str'), aws_iam_server_id=dict(type='str'), + azure_tenant_id=dict(type='str'), + azure_client_id=dict(type='str'), + azure_client_secret=dict(type='str', no_log=True), + azure_resource=dict(type='str', default='https://management.azure.com/'), cert_auth_private_key=dict(type='path', no_log=False), cert_auth_public_key=dict(type='path'), ) @@ -65,6 +71,7 @@ def __init__(self, option_adapter, warning_callback, deprecate_callback): # so that it's easier to scan and see at a glance that a given auth method is present or absent 'approle': HashiVaultAuthMethodApprole(option_adapter, warning_callback, deprecate_callback), 'aws_iam': HashiVaultAuthMethodAwsIam(option_adapter, warning_callback, deprecate_callback), + 'azure': HashiVaultAuthMethodAzure(option_adapter, warning_callback, deprecate_callback), 'cert': HashiVaultAuthMethodCert(option_adapter, warning_callback, deprecate_callback), 'jwt': HashiVaultAuthMethodJwt(option_adapter, warning_callback, deprecate_callback), 'ldap': HashiVaultAuthMethodLdap(option_adapter, warning_callback, deprecate_callback), diff --git a/tests/integration/requirements.txt b/tests/integration/requirements.txt index 7a9aaae0b..033733f7c 100644 --- a/tests/integration/requirements.txt +++ b/tests/integration/requirements.txt @@ -10,3 +10,7 @@ hvac >= 0.10.6 ; python_version >= '3.6' # these should be satisfied naturally by the requests versions required by hvac anyway urllib3 >= 1.15 ; python_version >= '3.6' # we need raise_on_status for retry support to raise the correct exceptions https://github.com/urllib3/urllib3/blob/main/CHANGES.rst#115-2016-04-06 urllib3 >= 1.15, <2.0.0 ; python_version < '3.6' # https://urllib3.readthedocs.io/en/latest/v2-roadmap.html#optimized-for-python-3-6 + +# azure-identity 1.7.0 depends on cryptography 2.5 which drops python 2.6 support +azure-identity < 1.7.0; python_version < '2.7' +azure-identity; python_version >= '2.7' diff --git a/tests/integration/targets/auth_azure/aliases b/tests/integration/targets/auth_azure/aliases new file mode 100644 index 000000000..32ccc0d36 --- /dev/null +++ b/tests/integration/targets/auth_azure/aliases @@ -0,0 +1,2 @@ +vault/auth/azure +context/target diff --git a/tests/integration/targets/auth_azure/defaults/main.yml b/tests/integration/targets/auth_azure/defaults/main.yml new file mode 100644 index 000000000..16540fbcd --- /dev/null +++ b/tests/integration/targets/auth_azure/defaults/main.yml @@ -0,0 +1,10 @@ +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +ansible_hashi_vault_url: '{{ vault_mmock_server_http }}' +ansible_hashi_vault_auth_method: azure + +auth_paths: + - azure + - azure-alt diff --git a/tests/integration/targets/auth_azure/meta/main.yml b/tests/integration/targets/auth_azure/meta/main.yml new file mode 100644 index 000000000..fa4fc1c74 --- /dev/null +++ b/tests/integration/targets/auth_azure/meta/main.yml @@ -0,0 +1,7 @@ +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +dependencies: + - setup_vault_test_plugins + - setup_vault_configure diff --git a/tests/integration/targets/auth_azure/tasks/azure_test_controller.yml b/tests/integration/targets/auth_azure/tasks/azure_test_controller.yml new file mode 100644 index 000000000..8b38c0d73 --- /dev/null +++ b/tests/integration/targets/auth_azure/tasks/azure_test_controller.yml @@ -0,0 +1,48 @@ +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +- name: "Test block" + vars: + is_default_path: "{{ this_path == default_path }}" + kwargs_mount: "{{ {} if is_default_path else {'mount_point': this_path} }}" + kwargs_common: + jwt: azure-jwt + kwargs: "{{ kwargs_common | combine(kwargs_mount) }}" + block: + # the purpose of this test is to catch when the plugin accepts mount_point but does not pass it into hvac + # we set the policy of the default mount to deny access to this secret and so we expect failure when the mount + # is default, and success when the mount is alternate + - name: Check auth mount differing result + set_fact: + response: "{{ lookup('vault_test_auth', role_id='not-important', **kwargs) }}" + + - assert: + fail_msg: "A token from mount path '{{ this_path }}' had the wrong policy: {{ response.login.auth.policies }}" + that: + - ('azure-sample-policy' in response.login.auth.policies) | bool == is_default_path + - ('azure-sample-policy' not in response.login.auth.policies) | bool != is_default_path + - ('azure-alt-sample-policy' in response.login.auth.policies) | bool != is_default_path + - ('azure-alt-sample-policy' not in response.login.auth.policies) | bool == is_default_path + + - name: Failure expected when something goes wrong (simulated) + set_fact: + response: "{{ lookup('vault_test_auth', role_id='fail-me-role', want_exception=true, **kwargs) }}" + + - assert: + fail_msg: "An invalid request somehow did not cause a failure." + that: + - response is failed + - response.msg is search('expected audience .+ got .+') + + - name: Failure expected when role_id is not given + set_fact: + response: "{{ lookup('vault_test_auth', want_exception=true, **kwargs) }}" + + - assert: + fail_msg: | + Missing role_id did not cause an expected failure. + {{ response }} + that: + - response is failed + - response.msg is search('^role_id is required for azure authentication\.$') diff --git a/tests/integration/targets/auth_azure/tasks/azure_test_target.yml b/tests/integration/targets/auth_azure/tasks/azure_test_target.yml new file mode 100644 index 000000000..0c637c706 --- /dev/null +++ b/tests/integration/targets/auth_azure/tasks/azure_test_target.yml @@ -0,0 +1,54 @@ +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +- name: "Test block" + vars: + is_default_path: "{{ this_path == default_path }}" + module_defaults: + vault_test_auth: + url: '{{ ansible_hashi_vault_url }}' + auth_method: '{{ ansible_hashi_vault_auth_method }}' + mount_point: '{{ omit if is_default_path else this_path }}' + jwt: azure-jwt + block: + # the purpose of this test is to catch when the plugin accepts mount_point but does not pass it into hvac + # we set the policy of the default mount to deny access to this secret and so we expect failure when the mount + # is default, and success when the mount is alternate + - name: Check auth mount differing result + register: response + vault_test_auth: + role_id: not-important + + - assert: + fail_msg: "A token from mount path '{{ this_path }}' had the wrong policy: {{ response.login.auth.policies }}" + that: + - ('azure-sample-policy' in response.login.auth.policies) | bool == is_default_path + - ('azure-sample-policy' not in response.login.auth.policies) | bool != is_default_path + - ('azure-alt-sample-policy' in response.login.auth.policies) | bool != is_default_path + - ('azure-alt-sample-policy' not in response.login.auth.policies) | bool == is_default_path + + - name: Failure expected when something goes wrong (simulated) + register: response + vault_test_auth: + role_id: fail-me-role + want_exception: yes + + - assert: + fail_msg: "An invalid request somehow did not cause a failure." + that: + - response.inner is failed + - response.msg is search('expected audience .+ got .+') + + - name: Failure expected when role_id is not given + register: response + vault_test_auth: + want_exception: yes + + - assert: + fail_msg: | + Missing role_id did not cause an expected failure. + {{ response }} + that: + - response.inner is failed + - response.msg is search('^role_id is required for azure authentication\.$') diff --git a/tests/integration/targets/auth_azure/tasks/main.yml b/tests/integration/targets/auth_azure/tasks/main.yml new file mode 100644 index 000000000..95bde76e5 --- /dev/null +++ b/tests/integration/targets/auth_azure/tasks/main.yml @@ -0,0 +1,26 @@ +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +# task vars are not templated when used as vars, so we'll need to set_fact this evaluate the template +# see: https://github.com/ansible/ansible/issues/73268 +- name: Persist defaults + set_fact: + '{{ item.key }}': "{{ lookup('vars', item.key) }}" + loop: "{{ lookup('file', role_path ~ '/defaults/main.yml') | from_yaml | dict2items }}" + loop_control: + label: '{{ item.key }}' + +# there's no setup for this auth method because its API is mocked + +- name: Run azure tests + loop: '{{ auth_paths | product(["target", "controller"]) | list }}' + include_tasks: + file: azure_test_{{ item[1] }}.yml + apply: + vars: + default_path: azure + this_path: '{{ item[0] }}' + module_defaults: + assert: + quiet: yes diff --git a/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_alt_mount.yml.j2 b/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_alt_mount.yml.j2 new file mode 100644 index 000000000..b1588fd6e --- /dev/null +++ b/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_alt_mount.yml.j2 @@ -0,0 +1,46 @@ +#jinja2:variable_start_string:'[%', variable_end_string:'%]' +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +request: + method: POST|PUT + path: "/v1/auth/azure-alt/login" +control: + priority: 10 +response: + statusCode: 200 + headers: + Content-Type: + - application/json + body: >- + { + "request_id": "{{fake.UUID}}", + "lease_id": "", + "lease_duration": 0, + "renewable": false, + "data": null, + "warnings": null, + "auth": { + "client_token": "s.{{fake.CharactersN(24)}}", + "accessor": "{{fake.CharactersN(24)}}", + "policies": [ + "default", + "azure-alt-sample-policy" + ], + "token_policies": [ + "default", + "azure-alt-sample-policy" + ], + "identity_policies": null, + "metadata": { + "role": "vault-role", + "resource_group_name": "", + "subscription_id": "" + }, + "orphan": true, + "entity_id": "{{fake.UUID}}", + "lease_duration": 1800, + "renewable": true + } + } diff --git a/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_bad_request.yml.j2 b/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_bad_request.yml.j2 new file mode 100644 index 000000000..d447dd015 --- /dev/null +++ b/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_bad_request.yml.j2 @@ -0,0 +1,22 @@ +#jinja2:variable_start_string:'[%', variable_end_string:'%]' +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +request: + method: POST|PUT + path: "/v1/auth/azure*/login" + body: '*fail-me-role*' +control: + priority: 11 +response: + statusCode: 400 + headers: + Content-Type: + - application/json + body: >- + { + "errors": [ + "oidc: expected audience \"https://management.azure.com/\" got [\"https://management.azure.com\"]" + ] + } diff --git a/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_default_mount.yml.j2 b/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_default_mount.yml.j2 new file mode 100644 index 000000000..af26ada83 --- /dev/null +++ b/tests/integration/targets/setup_localenv_docker/templates/mmock/azure_login_default_mount.yml.j2 @@ -0,0 +1,46 @@ +#jinja2:variable_start_string:'[%', variable_end_string:'%]' +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 +--- +request: + method: POST|PUT + path: "/v1/auth/azure/login" +control: + priority: 10 +response: + statusCode: 200 + headers: + Content-Type: + - application/json + body: >- + { + "request_id": "{{fake.UUID}}", + "lease_id": "", + "lease_duration": 0, + "renewable": false, + "data": null, + "warnings": null, + "auth": { + "client_token": "s.{{fake.CharactersN(24)}}", + "accessor": "{{fake.CharactersN(24)}}", + "policies": [ + "default", + "azure-sample-policy" + ], + "token_policies": [ + "default", + "azure-sample-policy" + ], + "identity_policies": null, + "metadata": { + "role": "vault-role", + "resource_group_name": "", + "subscription_id": "" + }, + "orphan": true, + "entity_id": "{{fake.UUID}}", + "lease_duration": 1800, + "renewable": true + } + } diff --git a/tests/unit/fixtures/azure_login_response.json b/tests/unit/fixtures/azure_login_response.json new file mode 100644 index 000000000..f1d1302e5 --- /dev/null +++ b/tests/unit/fixtures/azure_login_response.json @@ -0,0 +1,33 @@ +{ + "request_id": "cbfb16b9-4cf6-917d-182b-170801fc5a4e", + "lease_id": "", + "renewable": false, + "lease_duration": 0, + "data": null, + "wrap_info": null, + "warnings": null, + "auth": { + "client_token": "hvs.CAESIH6iy4yyvKMpk-vcaaVvU8nGfZFRCcH92hVa24lGNxHNGh4KHGh2cy5qU29Ua1FscTJIQ3BBY1AwTDM4dzNpR0E", + "accessor": "60U0DvUOIMOIGI7kzAneeD2x", + "policies": [ + "default", + "azure-sample-policy" + ], + "token_policies": [ + "default", + "azure-sample-policy" + ], + "metadata": { + "resource_group_name": "", + "role": "msi-vault", + "subscription_id": "" + }, + "lease_duration": 2764800, + "renewable": true, + "entity_id": "ff6a9d66-c2eb-6b78-e463-b3192243b5c1", + "token_type": "service", + "orphan": true, + "mfa_requirement": null, + "num_uses": 0 + } +} diff --git a/tests/unit/plugins/module_utils/authentication/test_auth_azure.py b/tests/unit/plugins/module_utils/authentication/test_auth_azure.py new file mode 100644 index 000000000..747a432df --- /dev/null +++ b/tests/unit/plugins/module_utils/authentication/test_auth_azure.py @@ -0,0 +1,224 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Junrui Chen (@jchenship) +# 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 + +import pytest + +from ansible_collections.community.hashi_vault.tests.unit.compat import mock + +from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_azure import ( + HashiVaultAuthMethodAzure, +) + +from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import ( + HashiVaultAuthMethodBase, + HashiVaultValueError, +) + + +@pytest.fixture +def option_dict(): + return { + 'auth_method': 'azure', + 'role_id': 'vault-role', + 'mount_point': None, + 'jwt': None, + 'azure_tenant_id': None, + 'azure_client_id': None, + 'azure_client_secret': None, + 'azure_resource': 'https://management.azure.com/', + } + + +@pytest.fixture +def azure_client_id(): + return 'client-id' + + +@pytest.fixture +def azure_client_secret(): + return 'client-secret' + + +@pytest.fixture +def jwt(): + return 'jwt-token' + + +@pytest.fixture +def auth_azure(adapter, warner, deprecator): + return HashiVaultAuthMethodAzure(adapter, warner, deprecator) + + +@pytest.fixture +def azure_login_response(fixture_loader): + return fixture_loader('azure_login_response.json') + + +class TestAuthAzure(object): + def test_auth_azure_is_auth_method_base(self, auth_azure): + assert isinstance(auth_azure, HashiVaultAuthMethodAzure) + assert issubclass(HashiVaultAuthMethodAzure, HashiVaultAuthMethodBase) + + def test_auth_azure_validate_role_id(self, auth_azure, adapter): + adapter.set_options(role_id=None) + with pytest.raises(HashiVaultValueError, match=r'^role_id is required for azure authentication\.$'): + auth_azure.validate() + + @pytest.mark.parametrize('mount_point', [None, 'other'], ids=lambda x: 'mount_point=%s' % x) + @pytest.mark.parametrize('role_id', ['role1', 'role2'], ids=lambda x: 'role_id=%s' % x) + @pytest.mark.parametrize('jwt', ['jwt1', 'jwt2'], ids=lambda x: 'jwt=%s' % x) + def test_auth_azure_validate_use_jwt( + self, auth_azure, adapter, role_id, mount_point, jwt + ): + adapter.set_options( + role_id=role_id, + mount_point=mount_point, + jwt=jwt, + ) + + auth_azure.validate() + + params = auth_azure._auth_azure_login_params + + assert (mount_point is None and 'mount_point' not in params) or params['mount_point'] == mount_point + assert params['role'] == role_id + assert params['jwt'] == jwt + + @pytest.mark.parametrize('mount_point', [None, 'other'], ids=lambda x: 'mount_point=%s' % x) + @pytest.mark.parametrize('use_token', [True, False], ids=lambda x: 'use_token=%s' % x) + def test_auth_azure_authenticate_use_jwt( + self, + auth_azure, + client, + adapter, + mount_point, + jwt, + use_token, + azure_login_response, + ): + adapter.set_options( + mount_point=mount_point, + jwt=jwt, + ) + + auth_azure.validate() + + params = auth_azure._auth_azure_login_params.copy() + + with mock.patch.object( + client.auth.azure, 'login', return_value=azure_login_response + ) as azure_login: + response = auth_azure.authenticate(client, use_token=use_token) + azure_login.assert_called_once_with(use_token=use_token, **params) + + assert ( + response['auth']['client_token'] + == azure_login_response['auth']['client_token'] + ) + + def test_auth_azure_validate_use_identity_no_azure_identity_lib( + self, auth_azure, mock_import_error, adapter + ): + adapter.set_options() + with mock_import_error('azure.identity'): + with pytest.raises( + HashiVaultValueError, match=r'azure-identity is required' + ): + auth_azure.validate() + + @pytest.mark.parametrize('azure_tenant_id', ['tenant1', 'tenant2'], ids=lambda x: 'azure_tenant_id=%s' % x) + @pytest.mark.parametrize('azure_client_id', ['client1', 'client2'], ids=lambda x: 'azure_client_id=%s' % x) + @pytest.mark.parametrize('azure_client_secret', ['secret1', 'secret2'], ids=lambda x: 'azure_client_secret=%s' % x) + @pytest.mark.parametrize('jwt', ['jwt1', 'jwt2'], ids=lambda x: 'jwt=%s' % x) + def test_auth_azure_validate_use_service_principal( + self, + auth_azure, + adapter, + jwt, + azure_tenant_id, + azure_client_id, + azure_client_secret, + ): + adapter.set_options( + azure_tenant_id=azure_tenant_id, + azure_client_id=azure_client_id, + azure_client_secret=azure_client_secret, + ) + + with mock.patch( + 'azure.identity.ClientSecretCredential' + ) as mocked_credential_class: + credential = mocked_credential_class.return_value + credential.get_token.return_value.token = jwt + auth_azure.validate() + + assert mocked_credential_class.called_once_with( + azure_tenant_id, azure_client_id, azure_client_secret + ) + assert credential.get_token.called_once_with( + 'https://management.azure.com//.default' + ) + + params = auth_azure._auth_azure_login_params + assert params['jwt'] == jwt + + def test_auth_azure_validate_use_service_principal_no_tenant_id( + self, auth_azure, adapter, azure_client_id, azure_client_secret + ): + adapter.set_options( + azure_client_id=azure_client_id, + azure_client_secret=azure_client_secret, + ) + + with pytest.raises(HashiVaultValueError, match='azure_tenant_id is required'): + auth_azure.validate() + + @pytest.mark.parametrize('azure_client_id', ['client1', 'client2'], ids=lambda x: 'azure_client_id=%s' % x) + @pytest.mark.parametrize('jwt', ['jwt1', 'jwt2'], ids=lambda x: 'jwt=%s' % x) + def test_auth_azure_validate_use_user_managed_identity( + self, auth_azure, adapter, jwt, azure_client_id + ): + adapter.set_options( + azure_client_id=azure_client_id, + ) + + with mock.patch( + 'azure.identity.ManagedIdentityCredential' + ) as mocked_credential_class: + credential = mocked_credential_class.return_value + credential.get_token.return_value.token = jwt + auth_azure.validate() + + assert mocked_credential_class.called_once_with(azure_client_id) + assert credential.get_token.called_once_with( + 'https://management.azure.com//.default' + ) + + params = auth_azure._auth_azure_login_params + assert params['jwt'] == jwt + + @pytest.mark.parametrize('jwt', ['jwt1', 'jwt2'], ids=lambda x: 'jwt=%s' % x) + def test_auth_azure_validate_use_system_managed_identity( + self, auth_azure, adapter, jwt + ): + adapter.set_options() + + with mock.patch( + 'azure.identity.ManagedIdentityCredential' + ) as mocked_credential_class: + credential = mocked_credential_class.return_value + credential.get_token.return_value.token = jwt + auth_azure.validate() + + assert mocked_credential_class.called_once_with() + assert credential.get_token.called_once_with( + 'https://management.azure.com//.default' + ) + + params = auth_azure._auth_azure_login_params + assert params['jwt'] == jwt diff --git a/tests/unit/requirements.txt b/tests/unit/requirements.txt index 7a9aaae0b..d8844e35b 100644 --- a/tests/unit/requirements.txt +++ b/tests/unit/requirements.txt @@ -2,7 +2,6 @@ # earlier python versions are still needed for Ansible < 2.12 which doesn't # support tests/config.yml, so that unit tests (which will be skipped) won't # choke on installing requirements. - hvac >= 0.10.6, != 0.10.12, != 0.10.13, < 1.0.0 ; python_version == '2.7' # bugs in 0.10.12 and 0.10.13 prevent it from working in Python 2 hvac >= 0.10.6, < 1.0.0 ; python_version == '3.5' # py3.5 support will be dropped in 1.0.0 hvac >= 0.10.6 ; python_version >= '3.6' @@ -10,3 +9,7 @@ hvac >= 0.10.6 ; python_version >= '3.6' # these should be satisfied naturally by the requests versions required by hvac anyway urllib3 >= 1.15 ; python_version >= '3.6' # we need raise_on_status for retry support to raise the correct exceptions https://github.com/urllib3/urllib3/blob/main/CHANGES.rst#115-2016-04-06 urllib3 >= 1.15, <2.0.0 ; python_version < '3.6' # https://urllib3.readthedocs.io/en/latest/v2-roadmap.html#optimized-for-python-3-6 + +# azure-identity 1.7.0 depends on cryptography 2.5 which drops python 2.6 support +azure-identity < 1.7.0; python_version < '2.7' +azure-identity; python_version >= '2.7' From 00a95fb583e7ac47076bc3c3474e76bc50341033 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 21 Aug 2022 20:45:29 -0400 Subject: [PATCH 03/45] Release/3.2.0 (#298) * update release version * add release summary for 3.2.0 * fix changelog typo * prepare 3.2.0 release --- CHANGELOG.rst | 20 +++++++++++++++ changelogs/changelog.yaml | 25 +++++++++++++++++++ .../fragments/289-handle-unsafe-strings.yml | 3 --- changelogs/fragments/290-retry-http-412.yml | 3 --- .../293-support-azure-auth-method.yml | 3 --- .../fragments/296-use-before-assignment.yml | 3 --- galaxy.yml | 2 +- 7 files changed, 46 insertions(+), 13 deletions(-) delete mode 100644 changelogs/fragments/289-handle-unsafe-strings.yml delete mode 100644 changelogs/fragments/290-retry-http-412.yml delete mode 100644 changelogs/fragments/293-support-azure-auth-method.yml delete mode 100644 changelogs/fragments/296-use-before-assignment.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 5cdaffc2c..16bfc8439 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,26 @@ community.hashi_vault Release Notes .. contents:: Topics +v3.2.0 +====== + +Release Summary +--------------- + +This release brings support for the ``azure`` auth method, adds ``412`` to the default list of HTTP status codes to be retried, and fixes a bug that causes failures in token auth with ``requests>=2.28.0``. + +Minor Changes +------------- + +- community.hashi_vault collection - add support for ``azure`` auth method, for Azure service principal, managed identity, or plain JWT access token (https://github.com/ansible-collections/community.hashi_vault/issues/293). +- community.hashi_vault retries - `HTTP status code 412 `__ has been added to the default list of codes to be retried, for the new `Server Side Consistent Token feature `__ in Vault Enterprise (https://github.com/ansible-collections/community.hashi_vault/issues/290). + +Bugfixes +-------- + +- community.hashi_vault plugins - tokens will be cast to a string type before being sent to ``hvac`` to prevent errors in ``requests`` when values are ``AnsibleUnsafe`` (https://github.com/ansible-collections/community.hashi_vault/issues/289). +- modules - fix a "variable used before assignment" that cannot be reached but causes sanity test failures (https://github.com/ansible-collections/community.hashi_vault/issues/296). + v3.1.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 9b841cc3a..4e7ca01e8 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -501,3 +501,28 @@ releases: - 3.1.0.yml - licensing.yml release_date: '2022-07-17' + 3.2.0: + changes: + bugfixes: + - community.hashi_vault plugins - tokens will be cast to a string type before + being sent to ``hvac`` to prevent errors in ``requests`` when values are ``AnsibleUnsafe`` + (https://github.com/ansible-collections/community.hashi_vault/issues/289). + - modules - fix a "variable used before assignment" that cannot be reached but + causes sanity test failures (https://github.com/ansible-collections/community.hashi_vault/issues/296). + minor_changes: + - community.hashi_vault collection - add support for ``azure`` auth method, + for Azure service principal, managed identity, or plain JWT access token (https://github.com/ansible-collections/community.hashi_vault/issues/293). + - community.hashi_vault retries - `HTTP status code 412 `__ + has been added to the default list of codes to be retried, for the new `Server + Side Consistent Token feature `__ + in Vault Enterprise (https://github.com/ansible-collections/community.hashi_vault/issues/290). + release_summary: This release brings support for the ``azure`` auth method, + adds ``412`` to the default list of HTTP status codes to be retried, and fixes + a bug that causes failures in token auth with ``requests>=2.28.0``. + fragments: + - 289-handle-unsafe-strings.yml + - 290-retry-http-412.yml + - 293-support-azure-auth-method.yml + - 296-use-before-assignment.yml + - 3.2.0.yml + release_date: '2022-08-21' diff --git a/changelogs/fragments/289-handle-unsafe-strings.yml b/changelogs/fragments/289-handle-unsafe-strings.yml deleted file mode 100644 index d067fc256..000000000 --- a/changelogs/fragments/289-handle-unsafe-strings.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -bugfixes: - - community.hashi_vault plugins - tokens will be cast to a string type before being sent to ``hvac`` to prevent errors in ``requests`` when values are ``AnsibleUnsafe`` (https://github.com/ansible-collections/community.hashi_vault/issues/289). diff --git a/changelogs/fragments/290-retry-http-412.yml b/changelogs/fragments/290-retry-http-412.yml deleted file mode 100644 index 9a1629426..000000000 --- a/changelogs/fragments/290-retry-http-412.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - community.hashi_vault retries - `HTTP status code 412 `__ has been added to the default list of codes to be retried, for the new `Server Side Consistent Token feature `__ in Vault Enterprise (https://github.com/ansible-collections/community.hashi_vault/issues/290). diff --git a/changelogs/fragments/293-support-azure-auth-method.yml b/changelogs/fragments/293-support-azure-auth-method.yml deleted file mode 100644 index 33fa4db94..000000000 --- a/changelogs/fragments/293-support-azure-auth-method.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - community.hashi_vault collection - add support for ``azure`` auth method, for Azure service pricinpal, managed identity, or plain JWT access token (https://github.com/ansible-collections/community.hashi_vault/issues/293). diff --git a/changelogs/fragments/296-use-before-assignment.yml b/changelogs/fragments/296-use-before-assignment.yml deleted file mode 100644 index 5bdd33f4e..000000000 --- a/changelogs/fragments/296-use-before-assignment.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -bugfixes: - - modules - fix a "variable used before assignment" that cannot be reached but causes sanity test failures (https://github.com/ansible-collections/community.hashi_vault/issues/296). diff --git a/galaxy.yml b/galaxy.yml index d59d96f55..3a87a57c1 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.1.0 +version: 3.2.0 readme: README.md authors: - Julie Davila (@juliedavila) From 1f0ffdc01634395f2b4d9e19cade7748c87ad590 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 27 Aug 2022 10:12:16 -0400 Subject: [PATCH 04/45] 3.3.0 next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 3a87a57c1..f3f9a527c 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.2.0 +version: 3.3.0 readme: README.md authors: - Julie Davila (@juliedavila) From 74835abb22dc08cd363725d32a77bc1b1a181131 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Tue, 30 Aug 2022 18:22:24 -0400 Subject: [PATCH 05/45] add the changelog to the collection docsite (#299) * add the changelog to the collection docsite * update the README too --- README.md | 2 +- docs/docsite/extra-docs.yml | 3 +++ docs/docsite/rst/CHANGELOG.rst | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) create mode 120000 docs/docsite/rst/CHANGELOG.rst diff --git a/README.md b/README.md index cf9e8adff..f1b4db97d 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Once the new collection is published and the Zuul job is finished, add a release ## Release notes -See the [changelog](https://github.com/ansible-collections/community.hashi_vault/tree/main/CHANGELOG.rst). +See the [rendered changelog](https://ansible-collections.github.io/community.hashi_vault/branch/main/collections/community/hashi_vault/docsite/CHANGELOG.html) or the [raw generated changelog](https://github.com/ansible-collections/community.hashi_vault/tree/main/CHANGELOG.rst). ## FAQ diff --git a/docs/docsite/extra-docs.yml b/docs/docsite/extra-docs.yml index 916fb2dce..82e329fcf 100644 --- a/docs/docsite/extra-docs.yml +++ b/docs/docsite/extra-docs.yml @@ -1,5 +1,8 @@ --- sections: + - title: Changelog + toctree: + - CHANGELOG - title: Guides toctree: - filter_guide diff --git a/docs/docsite/rst/CHANGELOG.rst b/docs/docsite/rst/CHANGELOG.rst new file mode 120000 index 000000000..a3432b01d --- /dev/null +++ b/docs/docsite/rst/CHANGELOG.rst @@ -0,0 +1 @@ +../../../CHANGELOG.rst \ No newline at end of file From 4523207c9cc4e2020a1d510a5cbfeff9d6de0085 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Mon, 19 Sep 2022 16:19:25 -0400 Subject: [PATCH 06/45] `vault_token_create` -- use new hvac method (#303) * use new hvac orphan token creation method * fix lookup units * lookup units tweaks * fix module units * fix integration token creator policy * fix lookup integration * fix module integration * update the documentation * fix docs formatting * lookup test auths * module test auths * add changelog fragment for #303 * fix changelog * Update plugins/doc_fragments/token_create.py Co-authored-by: Alicia Cozine <879121+acozine@users.noreply.github.com> * add tests for exception handling * fix lookup and module exception handling * test cleanup Co-authored-by: Alicia Cozine <879121+acozine@users.noreply.github.com> --- .../fragments/301-orphan-token-handling.yml | 3 + plugins/doc_fragments/token_create.py | 7 +- plugins/lookup/vault_token_create.py | 28 ++-- plugins/modules/vault_token_create.py | 28 ++-- .../tasks/lookup_vault_token_create_test.yml | 2 +- .../tasks/module_vault_token_create_test.yml | 2 +- .../setup_vault_configure/vars/main.yml | 3 - .../plugins/lookup/test_vault_token_create.py | 119 +++++++++------ .../modules/test_vault_token_create.py | 136 +++++++++++++----- 9 files changed, 211 insertions(+), 117 deletions(-) create mode 100644 changelogs/fragments/301-orphan-token-handling.yml diff --git a/changelogs/fragments/301-orphan-token-handling.yml b/changelogs/fragments/301-orphan-token-handling.yml new file mode 100644 index 000000000..dd99838f7 --- /dev/null +++ b/changelogs/fragments/301-orphan-token-handling.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vault_token_create - creation or orphan tokens uses ``hvac``'s new v1 method for creating orphans, or falls back to the v0 method if needed (https://github.com/ansible-collections/community.hashi_vault/issues/301). diff --git a/plugins/doc_fragments/token_create.py b/plugins/doc_fragments/token_create.py index bd1dfd519..30031f076 100644 --- a/plugins/doc_fragments/token_create.py +++ b/plugins/doc_fragments/token_create.py @@ -15,15 +15,12 @@ class ModuleDocFragment(object): orphan: description: - When C(true), uses the C(/create-orphan) API endpoint, which requires C(sudo) (but not C(root)) to create an orphan. - - Implies I(no_parent=true). - - B(NOTE:) as of this writing, the underlying endpoint in the C(hvac) library to support this is deprecated and scheduled for removal in C(v1.0.0). - - If I(orphan=true) and we cannot access the intended endpoint, the call will be attempted with the C(/create) endpoint, which requires root. - - If a replacement is provided in C(hvac), we will add support for it. + - With C(hvac>=1.0.0), requires collection version C(>=3.3.0). type: bool default: false no_parent: description: - - This option only has effect if used by a C(root) or C(sudo) caller, or in combination with I(orphan=true). + - This option only has effect if used by a C(root) or C(sudo) caller and only when I(orphan=false). - When C(true), the token created will not have a parent. type: bool no_default_policy: diff --git a/plugins/lookup/vault_token_create.py b/plugins/lookup/vault_token_create.py index 06757f657..f876c1cc6 100644 --- a/plugins/lookup/vault_token_create.py +++ b/plugins/lookup/vault_token_create.py @@ -134,7 +134,7 @@ class LookupModule(HashiVaultLookupBase): 'wrap_ttl', ] - LEGACY_OPTION_TRANSLATION = { + ORPHAN_OPTION_TRANSLATION = { 'id': 'token_id', 'role_name': 'role', 'type': 'token_type', @@ -166,29 +166,27 @@ def run(self, terms, variables=None, **kwargs): pass_thru_options = self._options_adapter.get_filled_options(*self.PASS_THRU_OPTION_NAMES) - if self.get_option('orphan'): - pass_thru_options['no_parent'] = True - - legacy_options = pass_thru_options.copy() + orphan_options = pass_thru_options.copy() for key in pass_thru_options.keys(): - if key in self.LEGACY_OPTION_TRANSLATION: - legacy_options[self.LEGACY_OPTION_TRANSLATION[key]] = legacy_options.pop(key) + if key in self.ORPHAN_OPTION_TRANSLATION: + orphan_options[self.ORPHAN_OPTION_TRANSLATION[key]] = orphan_options.pop(key) response = None if self.get_option('orphan'): - # this method is deprecated, but it's the only way through hvac to get - # at the /create-orphan endpoint at this time. - # See: https://github.com/hvac/hvac/issues/758 try: - response = client.create_token(orphan=True, **legacy_options) - except AttributeError: - display.warning("'create_token' method was not found. Attempting method that requires root privileges.") + try: + # this method was added in hvac 1.0.0 + # See: https://github.com/hvac/hvac/pull/869 + response = client.auth.token.create_orphan(**orphan_options) + except AttributeError: + # this method was removed in hvac 1.0.0 + # See: https://github.com/hvac/hvac/issues/758 + response = client.create_token(orphan=True, **orphan_options) except Exception as e: raise AnsibleError(e) - - if response is None: + else: try: response = client.auth.token.create(**pass_thru_options) except Exception as e: diff --git a/plugins/modules/vault_token_create.py b/plugins/modules/vault_token_create.py index ffdaffb53..052f67f78 100644 --- a/plugins/modules/vault_token_create.py +++ b/plugins/modules/vault_token_create.py @@ -131,7 +131,7 @@ ] -LEGACY_OPTION_TRANSLATION = { +ORPHAN_OPTION_TRANSLATION = { 'id': 'token_id', 'role_name': 'role', 'type': 'token_type', @@ -175,14 +175,11 @@ def run_module(): pass_thru_options = module.adapter.get_filled_options(*PASS_THRU_OPTION_NAMES) - if module.adapter.get_option('orphan'): - pass_thru_options['no_parent'] = True - - legacy_options = pass_thru_options.copy() + orphan_options = pass_thru_options.copy() for key in pass_thru_options.keys(): - if key in LEGACY_OPTION_TRANSLATION: - legacy_options[LEGACY_OPTION_TRANSLATION[key]] = legacy_options.pop(key) + 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 @@ -192,17 +189,18 @@ def run_module(): module.exit_json(changed=changed, login={'auth': {'client_token': None}}) if module.adapter.get_option('orphan'): - # this method is deprecated, but it's the only way through hvac to get - # at the /create-orphan endpoint at this time. - # See: https://github.com/hvac/hvac/issues/758 try: - response = client.create_token(orphan=True, **legacy_options) - except AttributeError: - module.warn("'create_token' method was not found. Attempting method that requires root privileges.") + 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()) - - if response is None: + else: try: response = client.auth.token.create(**pass_thru_options) except Exception as e: diff --git a/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml b/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml index f32de9996..de2c9cd7a 100644 --- a/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml +++ b/tests/integration/targets/lookup_vault_token_create/tasks/lookup_vault_token_create_test.yml @@ -35,7 +35,7 @@ - assert: that: - orphan_result is failed - - orphan_result.msg is search('root or sudo privileges required to create orphan token') + - orphan_result.msg is search('permission denied') - name: (xfail) Create an orphan token with no_parent=true vars: diff --git a/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml b/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml index 2294aa331..947d530b5 100644 --- a/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml +++ b/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml @@ -37,7 +37,7 @@ - assert: that: - orphan_result is failed - - orphan_result.msg is search('root or sudo privileges required to create orphan token') + - orphan_result.msg is search('permission denied') - name: (xfail) Create an orphan token with no_parent=true register: no_parent_result diff --git a/tests/integration/targets/setup_vault_configure/vars/main.yml b/tests/integration/targets/setup_vault_configure/vars/main.yml index b6d369379..ee09e3704 100644 --- a/tests/integration/targets/setup_vault_configure/vars/main.yml +++ b/tests/integration/targets/setup_vault_configure/vars/main.yml @@ -60,9 +60,6 @@ vault_token_creator_policy: | path "auth/token/create/*" { capabilities = ["create", "update"] } - path "auth/token/create-orphan" { - capabilities = ["create", "update"] - } vault_orphan_creator_policy: | path "auth/token/create" { diff --git a/tests/unit/plugins/lookup/test_vault_token_create.py b/tests/unit/plugins/lookup/test_vault_token_create.py index 0407a475b..05a74f8b4 100644 --- a/tests/unit/plugins/lookup/test_vault_token_create.py +++ b/tests/unit/plugins/lookup/test_vault_token_create.py @@ -17,6 +17,7 @@ from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase from .....plugins.lookup import vault_token_create +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError pytest.importorskip('hvac') @@ -55,7 +56,7 @@ def pass_thru_options(): @pytest.fixture -def legacy_option_translation(): +def orphan_option_translation(): return { 'id': 'token_id', 'role_name': 'role', @@ -78,6 +79,20 @@ def test_vault_token_create_no_hvac(self, vault_token_create_lookup, minimal_var with pytest.raises(AnsibleError, match=r"This plugin requires the 'hvac' Python library"): vault_token_create_lookup.run(terms='fake', variables=minimal_vars) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_authentication_error(self, vault_token_create_lookup, minimal_vars, authenticator, exc): + authenticator.authenticate.side_effect = exc + + with pytest.raises(AnsibleError, match=r'throwaway msg'): + vault_token_create_lookup.run(terms='fake', variables=minimal_vars) + + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_auth_validation_error(self, vault_token_create_lookup, minimal_vars, authenticator, exc): + authenticator.validate.side_effect = exc + + with pytest.raises(AnsibleError, match=r'throwaway msg'): + vault_token_create_lookup.run(terms='fake', variables=minimal_vars) + def test_vault_token_create_extra_terms(self, vault_token_create_lookup, authenticator, minimal_vars): with mock.patch('ansible_collections.community.hashi_vault.plugins.lookup.vault_token_create.display.warning') as warning: with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): @@ -97,24 +112,24 @@ def test_vault_token_create_passthru_options_expected(self, vault_token_create_l ) ) - def test_vault_token_create_legacy_options_expected(self, vault_token_create_lookup, legacy_option_translation, pass_thru_options): - # designed to catch the case where new legacy translations differ between tests and lookup + def test_vault_token_create_orphan_options_expected(self, vault_token_create_lookup, orphan_option_translation, pass_thru_options): + # designed to catch the case where new orphan translations differ between tests and lookup # and that all listed translations are present in passthru options - lookup_set = set(vault_token_create_lookup.LEGACY_OPTION_TRANSLATION.items()) - test_set = set(legacy_option_translation.items()) + lookup_set = set(vault_token_create_lookup.ORPHAN_OPTION_TRANSLATION.items()) + test_set = set(orphan_option_translation.items()) - lookup_key_set = set(vault_token_create_lookup.LEGACY_OPTION_TRANSLATION.keys()) + lookup_key_set = set(vault_token_create_lookup.ORPHAN_OPTION_TRANSLATION.keys()) pass_thru_key_set = set(pass_thru_options.keys()) assert lookup_set == test_set, ( - "Legacy options in lookup do not match legacy options in test:\nlookup: %r\ntest: %r" % ( + "Orphan options in lookup do not match orphan options in test:\nlookup: %r\ntest: %r" % ( dict(lookup_set - test_set), dict(test_set - lookup_set), ) ) - assert vault_token_create_lookup.LEGACY_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( - "Legacy option translation keys must exist in passthru options: %r" % ( + assert vault_token_create_lookup.ORPHAN_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( + "Orphan option translation keys must exist in passthru options: %r" % ( list(lookup_key_set - pass_thru_key_set), ) ) @@ -141,19 +156,20 @@ def test_vault_token_create_passthru_options(self, vault_token_create_lookup, au else: assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() - def test_vault_token_create_legacy_options( - self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, legacy_option_translation, token_create_response + def test_vault_token_create_orphan_options( + self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, orphan_option_translation, token_create_response ): client = mock.MagicMock() - client.create_token.return_value = token_create_response + client.auth.token.create_orphan.return_value = token_create_response with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): result = vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) client.auth.token.create.assert_not_called() - client.create_token.assert_called_once() + client.auth.token.create_orphan.assert_called_once() + client.create_token.assert_not_called() assert result[0] == token_create_response, ( "lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response) @@ -161,46 +177,65 @@ def test_vault_token_create_legacy_options( if sys.version_info < (3, 8): # TODO: remove when python < 3.8 is dropped - call_kwargs = client.create_token.call_args[1] + call_kwargs = client.auth.token.create_orphan.call_args[1] else: - call_kwargs = client.create_token.call_args.kwargs + call_kwargs = client.auth.token.create_orphan.call_args.kwargs - for name, legacy in legacy_option_translation.items(): + for name, orphan in orphan_option_translation.items(): assert name not in call_kwargs, ( - "'%s' was found in call to legacy method, should be '%s'" % (name, legacy) + "'%s' was found in call to orphan method, should be '%s'" % (name, orphan) ) - assert legacy in call_kwargs, ( - "'%s' (from '%s') was not found in call to legacy method" % (legacy, name) + assert orphan in call_kwargs, ( + "'%s' (from '%s') was not found in call to orphan method" % (orphan, name) ) - assert call_kwargs[legacy] == pass_thru_options.get(name), ( - "Expected legacy param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( - legacy, - call_kwargs.get(legacy), + assert call_kwargs[orphan] == pass_thru_options.get(name), ( + "Expected orphan param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( + orphan, + call_kwargs.get(orphan), pass_thru_options.get(name), ) ) - def test_vault_token_create_legacy_fallback(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, token_create_response): + def test_vault_token_create_orphan_fallback(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options, token_create_response): client = mock.MagicMock() - client.create_token.side_effect = AttributeError - client.auth.token.create.return_value = token_create_response + client.create_token.return_value = token_create_response + client.auth.token.create_orphan.side_effect = AttributeError - with mock.patch('ansible_collections.community.hashi_vault.plugins.lookup.vault_token_create.display.warning') as warning: - with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): - with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): - result = vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + result = vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) - warning.assert_called_once_with("'create_token' method was not found. Attempting method that requires root privileges.") - client.auth.token.create.assert_called_once() + client.auth.token.create_orphan.assert_called_once() + client.create_token.assert_called_once() - assert result[0] == token_create_response, ( - "lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response) - ) + assert result[0] == token_create_response, ( + "lookup result did not match expected result:\nlookup: %r\nexpected: %r" % (result, token_create_response) + ) + + def test_vault_token_create_exception_handling_standard(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options): + client = mock.MagicMock() + client.auth.token.create.side_effect = Exception('side_effect') + + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + with pytest.raises(AnsibleError, match=r'^side_effect$'): + vault_token_create_lookup.run(terms=[], variables=minimal_vars, **pass_thru_options) + + def test_vault_token_create_exception_handling_orphan(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options): + client = mock.MagicMock() + client.auth.token.create_orphan.side_effect = Exception('side_effect') + + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + with pytest.raises(AnsibleError, match=r'^side_effect$'): + vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) - # we're retesting that expected options were passed, even though there's a separate test for that, - # to ensure that nothing in the original legacy attempt mutates the non-legacy options during fallback - if sys.version_info < (3, 8): - # TODO: remove when python < 3.8 is dropped - assert pass_thru_options.items() <= client.auth.token.create.call_args[1].items() - else: - assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() + def test_vault_token_create_exception_handling_orphan_fallback(self, vault_token_create_lookup, authenticator, minimal_vars, pass_thru_options): + client = mock.MagicMock() + client.create_token.side_effect = Exception('side_effect') + client.auth.token.create_orphan.side_effect = AttributeError + + with mock.patch.object(vault_token_create_lookup, 'authenticator', new=authenticator): + with mock.patch.object(vault_token_create_lookup.helper, 'get_vault_client', return_value=client): + with pytest.raises(AnsibleError, match=r'^side_effect$'): + vault_token_create_lookup.run(terms=[], variables=minimal_vars, orphan=True, **pass_thru_options) diff --git a/tests/unit/plugins/modules/test_vault_token_create.py b/tests/unit/plugins/modules/test_vault_token_create.py index f23cf9e4a..24e44b800 100644 --- a/tests/unit/plugins/modules/test_vault_token_create.py +++ b/tests/unit/plugins/modules/test_vault_token_create.py @@ -12,6 +12,7 @@ import json from .....plugins.modules import vault_token_create +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError pytestmark = pytest.mark.usefixtures( 'patch_ansible_module', @@ -24,7 +25,7 @@ def _connection_options(): return { 'auth_method': 'token', 'url': 'http://myvault', - 'token': 'throwaway', + 'token': 'rando', } @@ -61,7 +62,7 @@ def pass_thru_options(): @pytest.fixture -def legacy_option_translation(): +def orphan_option_translation(): return { 'id': 'token_id', 'role_name': 'role', @@ -76,6 +77,34 @@ def token_create_response(fixture_loader): class TestModuleVaultTokenCreate(): + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_authentication_error(self, authenticator, exc, capfd): + authenticator.authenticate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg', "result: %r" % result + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_token_create_auth_validation_error(self, authenticator, exc, capfd): + authenticator.validate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg' + @pytest.mark.no_ansible_module_patch def test_vault_token_create_passthru_options_expected(self, pass_thru_options): # designed to catch the case where new passthru options differ between tests and module @@ -90,24 +119,24 @@ def test_vault_token_create_passthru_options_expected(self, pass_thru_options): ) @pytest.mark.no_ansible_module_patch - def test_vault_token_create_legacy_options_expected(self, legacy_option_translation, pass_thru_options): - # designed to catch the case where new legacy translations differ between tests and module + def test_vault_token_create_orphan_options_expected(self, orphan_option_translation, pass_thru_options): + # designed to catch the case where new orphan translations differ between tests and module # and that all listed translations are present in passthru options - module_set = set(vault_token_create.LEGACY_OPTION_TRANSLATION.items()) - test_set = set(legacy_option_translation.items()) + module_set = set(vault_token_create.ORPHAN_OPTION_TRANSLATION.items()) + test_set = set(orphan_option_translation.items()) - module_key_set = set(vault_token_create.LEGACY_OPTION_TRANSLATION.keys()) + module_key_set = set(vault_token_create.ORPHAN_OPTION_TRANSLATION.keys()) pass_thru_key_set = set(pass_thru_options.keys()) assert module_set == test_set, ( - "Legacy options in module do not match legacy options in test:\nmodule: %r\ntest: %r" % ( + "Orphan options in module do not match orphan options in test:\nmodule: %r\ntest: %r" % ( dict(module_set - test_set), dict(test_set - module_set), ) ) - assert vault_token_create.LEGACY_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( - "Legacy option translation keys must exist in passthru options: %r" % ( + assert vault_token_create.ORPHAN_OPTION_TRANSLATION.keys() <= pass_thru_options.keys(), ( + "Orphan option translation keys must exist in passthru options: %r" % ( list(module_key_set - pass_thru_key_set), ) ) @@ -124,6 +153,7 @@ def test_vault_token_create_passthru_options(self, pass_thru_options, token_crea result = json.loads(out) client.create_token.assert_not_called() + client.auth.token.create_orphan.assert_not_called() client.auth.token.create.assert_called_once() assert result['login'] == token_create_response, ( @@ -137,9 +167,9 @@ def test_vault_token_create_passthru_options(self, pass_thru_options, token_crea assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) - def test_vault_token_create_legacy_options(self, pass_thru_options, legacy_option_translation, token_create_response, vault_client, capfd): + def test_vault_token_create_orphan_options(self, pass_thru_options, orphan_option_translation, token_create_response, vault_client, capfd): client = vault_client - client.create_token.return_value = token_create_response + client.auth.token.create_orphan.return_value = token_create_response with pytest.raises(SystemExit): vault_token_create.main() @@ -147,8 +177,9 @@ def test_vault_token_create_legacy_options(self, pass_thru_options, legacy_optio out, err = capfd.readouterr() result = json.loads(out) + client.create_token.assert_not_called() client.auth.token.create.assert_not_called() - client.create_token.assert_called_once() + client.auth.token.create_orphan.assert_called_once() assert result['login'] == token_create_response, ( "module result did not match expected result:\nmodule: %r\nexpected: %r" % (result['module'], token_create_response) @@ -156,30 +187,30 @@ def test_vault_token_create_legacy_options(self, pass_thru_options, legacy_optio if sys.version_info < (3, 8): # TODO: remove when python < 3.8 is dropped - call_kwargs = client.create_token.call_args[1] + call_kwargs = client.auth.token.create_orphan.call_args[1] else: - call_kwargs = client.create_token.call_args.kwargs + call_kwargs = client.auth.token.create_orphan.call_args.kwargs - for name, legacy in legacy_option_translation.items(): + for name, orphan in orphan_option_translation.items(): assert name not in call_kwargs, ( - "'%s' was found in call to legacy method, should be '%s'" % (name, legacy) + "'%s' was found in call to orphan method, should be '%s'" % (name, orphan) ) - assert legacy in call_kwargs, ( - "'%s' (from '%s') was not found in call to legacy method" % (legacy, name) + assert orphan in call_kwargs, ( + "'%s' (from '%s') was not found in call to orphan method" % (orphan, name) ) - assert call_kwargs[legacy] == pass_thru_options.get(name), ( - "Expected legacy param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( - legacy, - call_kwargs.get(legacy), + assert call_kwargs[orphan] == pass_thru_options.get(name), ( + "Expected orphan param '%s' not found or value did not match:\nvalue: %r\nexpected: %r" % ( + orphan, + call_kwargs.get(orphan), pass_thru_options.get(name), ) ) @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) - def test_vault_token_create_legacy_fallback(self, pass_thru_options, token_create_response, vault_client, module_warn, capfd): + def test_vault_token_create_orphan_fallback(self, token_create_response, vault_client, capfd): client = vault_client - client.create_token.side_effect = AttributeError - client.auth.token.create.return_value = token_create_response + client.create_token.return_value = token_create_response + client.auth.token.create_orphan.side_effect = AttributeError with pytest.raises(SystemExit): vault_token_create.main() @@ -187,17 +218,52 @@ def test_vault_token_create_legacy_fallback(self, pass_thru_options, token_creat out, err = capfd.readouterr() result = json.loads(out) - module_warn.assert_called_once_with("'create_token' method was not found. Attempting method that requires root privileges.") - client.auth.token.create.assert_called_once() + client.auth.token.create_orphan.assert_called_once() + client.create_token.assert_called_once() assert result['login'] == token_create_response, ( "module result did not match expected result:\nmodule: %r\nexpected: %r" % (result['login'], token_create_response) ) - # we're retesting that expected options were passed, even though there's a separate test for that, - # to ensure that nothing in the original legacy attempt mutates the non-legacy options during fallback - if sys.version_info < (3, 8): - # TODO: remove when python < 3.8 is dropped - assert pass_thru_options.items() <= client.auth.token.create.call_args[1].items() - else: - assert pass_thru_options.items() <= client.auth.token.create.call_args.kwargs.items() + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + def test_vault_token_create_exception_handling_standard(self, vault_client, capfd): + client = vault_client + client.auth.token.create.side_effect = Exception('side_effect') + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'side_effect' + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) + def test_vault_token_create_exception_handling_orphan(self, vault_client, capfd): + client = vault_client + client.auth.token.create_orphan.side_effect = Exception('side_effect') + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'side_effect' + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options(orphan=True)], indirect=True) + def test_vault_token_create_exception_handling_orphan_fallback(self, vault_client, capfd): + client = vault_client + client.create_token.side_effect = Exception('side_effect') + client.auth.token.create_orphan.side_effect = AttributeError + + with pytest.raises(SystemExit) as e: + vault_token_create.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'side_effect' From 2346e8565ebf297371f2d1c1ca2c75235e67c96e Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Mon, 19 Sep 2022 19:35:21 -0400 Subject: [PATCH 07/45] Release/3.3.0 (#305) * add release summary * add release summary * add release summary * fix changelogs --- CHANGELOG.rst | 14 ++++++++++++++ changelogs/changelog.yaml | 13 +++++++++++++ changelogs/fragments/301-orphan-token-handling.yml | 3 --- 3 files changed, 27 insertions(+), 3 deletions(-) delete mode 100644 changelogs/fragments/301-orphan-token-handling.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 16bfc8439..bb18d6e8d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,20 @@ community.hashi_vault Release Notes .. contents:: Topics +v3.3.0 +====== + +Release Summary +--------------- + +With the release of ``hvac`` version ``1.0.0``, we needed to update ``vault_token_create``'s support for orphan tokens. +The collection's changelog is now viewable in the Ansible documentation site. + +Minor Changes +------------- + +- vault_token_create - creation or orphan tokens uses ``hvac``'s new v1 method for creating orphans, or falls back to the v0 method if needed (https://github.com/ansible-collections/community.hashi_vault/issues/301). + v3.2.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 4e7ca01e8..9a58b0f1f 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -526,3 +526,16 @@ releases: - 296-use-before-assignment.yml - 3.2.0.yml release_date: '2022-08-21' + 3.3.0: + changes: + minor_changes: + - vault_token_create - creation or orphan tokens uses ``hvac``'s new v1 method + for creating orphans, or falls back to the v0 method if needed (https://github.com/ansible-collections/community.hashi_vault/issues/301). + release_summary: 'With the release of ``hvac`` version ``1.0.0``, we needed + to update ``vault_token_create``''s support for orphan tokens. + + The collection''s changelog is now viewable in the Ansible documentation site.' + fragments: + - 3.3.0.yml + - 301-orphan-token-handling.yml + release_date: '2022-09-19' diff --git a/changelogs/fragments/301-orphan-token-handling.yml b/changelogs/fragments/301-orphan-token-handling.yml deleted file mode 100644 index dd99838f7..000000000 --- a/changelogs/fragments/301-orphan-token-handling.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - vault_token_create - creation or orphan tokens uses ``hvac``'s new v1 method for creating orphans, or falls back to the v0 method if needed (https://github.com/ansible-collections/community.hashi_vault/issues/301). From ba839980aafd53420238f0e0afb4ff543a41d793 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Mon, 19 Sep 2022 20:22:07 -0400 Subject: [PATCH 08/45] 3.4.0 next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index f3f9a527c..9086bd738 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.3.0 +version: 3.4.0 readme: README.md authors: - Julie Davila (@juliedavila) From 65f63ca08d1ae4c4ee5d66c5e1324db51a5e8f6c Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 25 Sep 2022 12:00:20 -0400 Subject: [PATCH 09/45] Docs/build update stuff (#306) * update docs build push job dependency * update preview files * exclude .post0 --- .github/workflows/docs-push.yml | 4 ++-- docs/preview/.gitignore | 4 ++++ docs/preview/antsibull-docs.cfg | 22 ++++++++++++++++++++++ docs/preview/build.sh | 12 +++++++++--- docs/preview/conf.py | 4 ++++ docs/preview/requirements.txt | 10 +++++++--- docs/preview/rst/index.rst | 6 +++++- 7 files changed, 53 insertions(+), 9 deletions(-) create mode 100644 docs/preview/antsibull-docs.cfg diff --git a/.github/workflows/docs-push.yml b/.github/workflows/docs-push.yml index 658030e68..82a989507 100644 --- a/.github/workflows/docs-push.yml +++ b/.github/workflows/docs-push.yml @@ -38,7 +38,7 @@ jobs: if: github.repository == 'ansible-collections/community.hashi_vault' permissions: contents: read - needs: [build-docs] + needs: [validate-docs, build-docs] name: Publish Ansible Docs uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-surge.yml@main with: @@ -52,7 +52,7 @@ jobs: if: github.repository == 'ansible-collections/community.hashi_vault' permissions: contents: write - needs: [build-docs] + needs: [validate-docs, build-docs] name: Publish Ansible Docs uses: ansible-community/github-docs-build/.github/workflows/_shared-docs-build-publish-gh-pages.yml@main with: diff --git a/docs/preview/.gitignore b/docs/preview/.gitignore index 66db1004b..2def98f08 100644 --- a/docs/preview/.gitignore +++ b/docs/preview/.gitignore @@ -1,3 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + /temp-rst /build /rst/collections diff --git a/docs/preview/antsibull-docs.cfg b/docs/preview/antsibull-docs.cfg new file mode 100644 index 000000000..9714411eb --- /dev/null +++ b/docs/preview/antsibull-docs.cfg @@ -0,0 +1,22 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +breadcrumbs = true +indexes = true +use_html_blobs = false + +# You can specify ways to convert a collection name (.) to an URL here. +# You can replace either of or by "*" to match all values in that place, +# or use "*" for the collection name to match all collections. In the URL, you can use +# {namespace} and {name} for the two components of the collection name. If you want to use +# "{" or "}" in the URL, write "{{" or "}}" instead. Basically these are Python format +# strings (https://docs.python.org/3.8/library/string.html#formatstrings). +collection_url = { + * = "https://galaxy.ansible.com/{namespace}/{name}" +} + +# The same wildcard rules and formatting rules as for collection_url apply. +collection_install = { + * = "ansible-galaxy collection install {namespace}.{name}" +} diff --git a/docs/preview/build.sh b/docs/preview/build.sh index a19c8c81f..eeb0c4f8e 100755 --- a/docs/preview/build.sh +++ b/docs/preview/build.sh @@ -1,19 +1,25 @@ #!/usr/bin/env bash +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + set -e pushd "${BASH_SOURCE%/*}" # Create collection documentation into temporary directory rm -rf temp-rst mkdir -p temp-rst -antsibull-docs collection \ +antsibull-docs \ + --config-file antsibull-docs.cfg \ + collection \ --use-current \ --dest-dir temp-rst \ community.hashi_vault # Copy collection documentation into source directory -rsync -avc --delete-after temp-rst/collections/ rst/collections/ +rsync -cprv --delete-after temp-rst/collections/ rst/collections/ # Build Sphinx site -sphinx-build -M html rst build -c . +sphinx-build -M html rst build -c . -W --keep-going popd diff --git a/docs/preview/conf.py b/docs/preview/conf.py index 6862c5968..02685b344 100644 --- a/docs/preview/conf.py +++ b/docs/preview/conf.py @@ -1,3 +1,7 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + # This file only contains a selection of the most common options. For a full list see the # documentation: # http://www.sphinx-doc.org/en/master/config diff --git a/docs/preview/requirements.txt b/docs/preview/requirements.txt index 633de27cf..afc7e88e8 100644 --- a/docs/preview/requirements.txt +++ b/docs/preview/requirements.txt @@ -1,4 +1,8 @@ -antsibull-docs +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +antsibull-docs >= 1.0.0, < 2.0.0 ansible-pygments -sphinx -sphinx-ansible-theme +sphinx != 5.2.0.post0 # temporary, see https://github.com/ansible-community/antsibull-docs/issues/39, https://github.com/ansible-community/antsibull-docs/issues/40 +sphinx-ansible-theme >= 0.9.0 diff --git a/docs/preview/rst/index.rst b/docs/preview/rst/index.rst index dc3d17ed8..19db644bc 100644 --- a/docs/preview/rst/index.rst +++ b/docs/preview/rst/index.rst @@ -1,9 +1,13 @@ +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + .. _docsite_root_index: Ansible collection documentation preview ======================================== -This docsite contains documentation from ``community.hashi_vault``. +This docsite contains documentation for ``community.hashi_vault``. .. toctree:: From 85048db34e8f92700e3a983be42fd420e46864c1 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 25 Sep 2022 12:42:07 -0400 Subject: [PATCH 10/45] Add docs for `vault_login_token` filter (#263) * add docs for vault_login_token filter * update filter guide * update plugin references * try a different reference * update reference * update descriptions * add license header * add optional_field example, use direct lookup referencing --- docs/docsite/rst/filter_guide.rst | 7 +- plugins/filter/vault_login_token.yml | 98 +++++++++++++++++++++++++++ plugins/lookup/vault_login.py | 2 +- plugins/lookup/vault_token_create.py | 2 +- plugins/modules/vault_login.py | 2 +- plugins/modules/vault_token_create.py | 2 +- 6 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 plugins/filter/vault_login_token.yml diff --git a/docs/docsite/rst/filter_guide.rst b/docs/docsite/rst/filter_guide.rst index 29ddbab78..8cee2b987 100644 --- a/docs/docsite/rst/filter_guide.rst +++ b/docs/docsite/rst/filter_guide.rst @@ -3,6 +3,11 @@ Filter guide ============ +.. note:: + + Filter Plugins are now included with other :ref:`plugin documentation `. + + .. contents:: Filters .. _ansible_collections.community.hashi_vault.docsite.filter_guide.vault_login_token: @@ -111,7 +116,7 @@ Which produces: "msg": "s.drgLxu6ZtttSVn5Zkoy0huMR" } -This filter is the equivalent of reading into the dictionary directly, but it has the advantage of providing semantic meaning and automatically working against the differing output of both the module and the lookup. +This filter is the equivalent of reading into the dictionary directly, but it has the advantages of providing semantic meaning and automatically working against the differing output of modules and lookups. .. code-block:: yaml+jinja diff --git a/plugins/filter/vault_login_token.yml b/plugins/filter/vault_login_token.yml new file mode 100644 index 000000000..e2946bafe --- /dev/null +++ b/plugins/filter/vault_login_token.yml @@ -0,0 +1,98 @@ +# (c) 2022, Brian Scholer (@briantist) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later +--- +DOCUMENTATION: + name: vault_login_token + short_description: Extracts the Vault token from a login or token creation + version_added: 2.2.0 + description: + - Extracts the token value from the structure returned by a Vault token creation operation. + seealso: + - module: community.hashi_vault.vault_login + - module: community.hashi_vault.vault_token_create + - plugin: community.hashi_vault.vault_login + plugin_type: lookup + - plugin: community.hashi_vault.vault_token_create + plugin_type: lookup + - ref: Filter Guide + description: The C(community.hashi_vault) Filter Guide + notes: + - >- + This filter is the same as reading into the I(_input) dictionary directly, + but it provides semantic meaning and automatically works with the differing output of the modules and lookups. + See the Filter guide for more information. + options: + _input: + description: + - A dictionary matching the structure returned by a login or token creation. + type: dict + required: true + optional_field: + description: + - >- + If this field exists in the input dictionary, then the value of that field is used as the I(_input) value. + - >- + The default value deals with the difference between the output of lookup plugins, + and does not need to be changed in most cases. + - See the examples or the Filter guide for more information. + type: string + default: login + author: + - Brian Scholer (@briantist) + +EXAMPLES: | + - name: Set defaults + vars: + ansible_hashi_vault_url: https://vault:9801/ + ansible_hashi_vault_auth_method: userpass + ansible_hashi_vault_username: user + ansible_hashi_vault_password: "{{ lookup('env', 'MY_SECRET_PASSWORD') }}" + module_defaults: + community.hashi_vault.vault_login: + url: '{{ ansible_hashi_vault_url }}' + auth_method: '{{ ansible_hashi_vault_auth_method }}' + username: '{{ ansible_hashi_vault_username }}' + password: '{{ ansible_hashi_vault_password }}' + block: + - name: Perform a login with a lookup and display the token + vars: + login_response: "{{ lookup('community.hashi_vault.vault_login') }}" + debug: + msg: "The token is {{ login_response | community.hashi_vault.vault_login_token }}" + + - name: Perform a login with a module + community.hashi_vault.vault_login: + register: login_response + + - name: Display the token + debug: + msg: "The token is {{ login_response | community.hashi_vault.vault_login_token }}" + + - name: Use of optional_field + vars: + lookup_login_response: "{{ lookup('community.hashi_vault.vault_login') }}" + my_data: + something: somedata + vault_login: "{{ lookup_login_response }}" + + token_from_param: "{{ my_data | community.hashi_vault.vault_login_token(optional_field='vault_login') }}" + token_from_deref: "{{ my_data['vault_login'] | community.hashi_vault.vault_login_token }}" + # if the optional field doesn't exist, the dictionary itself is still checked + unused_optional: "{{ my_data['vault_login'] | community.hashi_vault.vault_login_token(optional_field='missing') }}" + block: + - name: Display the variables + ansible.builtin.debug: + var: '{{ item }}' + loop: + - my_data + - token_from_param + - token_from_deref + - unused_optional + +RETURN: + _value: + description: The token value. + returned: always + sample: s.nnrpog4i5gjizr6b8g1inwj3 + type: string diff --git a/plugins/lookup/vault_login.py b/plugins/lookup/vault_login.py index 5248fbd01..08980ecaa 100644 --- a/plugins/lookup/vault_login.py +++ b/plugins/lookup/vault_login.py @@ -18,7 +18,7 @@ - Performs a login operation against a given path in HashiCorp Vault, returning the login response, including the token. seealso: - module: community.hashi_vault.vault_login - - ref: community.hashi_vault.vault_login_token filter + - ref: community.hashi_vault.vault_login_token filter description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin. notes: - This lookup does not use the term string and will not work correctly in loops. Only a single response will be returned. diff --git a/plugins/lookup/vault_token_create.py b/plugins/lookup/vault_token_create.py index f876c1cc6..9b19ae290 100644 --- a/plugins/lookup/vault_token_create.py +++ b/plugins/lookup/vault_token_create.py @@ -21,7 +21,7 @@ - ref: community.hashi_vault.vault_login lookup description: The official documentation for the C(community.hashi_vault.vault_login) lookup plugin. - module: community.hashi_vault.vault_login - - ref: community.hashi_vault.vault_login_token filter + - ref: community.hashi_vault.vault_login_token filter description: The official documentation for the C(community.hashi_vault.vault_login_token) filter plugin. notes: - Token creation is a write operation (creating a token persisted to storage), so this module always reports C(changed=True). diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index 46df8e0e6..0cc411cec 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -21,7 +21,7 @@ seealso: - ref: 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 + - ref: 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.connection diff --git a/plugins/modules/vault_token_create.py b/plugins/modules/vault_token_create.py index 052f67f78..4fd757b06 100644 --- a/plugins/modules/vault_token_create.py +++ b/plugins/modules/vault_token_create.py @@ -24,7 +24,7 @@ - module: community.hashi_vault.vault_login - ref: 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 + - ref: 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.connection From f266066c623e3748819592b12dceeb7f03377daa Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 25 Sep 2022 13:46:37 -0400 Subject: [PATCH 11/45] Release/3.3.1 (#307) * set ver * add release summary * prepare 3.3.1 release --- CHANGELOG.rst | 8 ++++++++ changelogs/changelog.yaml | 7 +++++++ galaxy.yml | 2 +- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bb18d6e8d..c25c812e8 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,14 @@ community.hashi_vault Release Notes .. contents:: Topics +v3.3.1 +====== + +Release Summary +--------------- + +No functional changes in this release, this provides updated filter documentation for the public docsite. + v3.3.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 9a58b0f1f..18d6e4e38 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -539,3 +539,10 @@ releases: - 3.3.0.yml - 301-orphan-token-handling.yml release_date: '2022-09-19' + 3.3.1: + changes: + release_summary: No functional changes in this release, this provides updated + filter documentation for the public docsite. + fragments: + - 3.3.1.yml + release_date: '2022-09-25' diff --git a/galaxy.yml b/galaxy.yml index 9086bd738..f1ad908b6 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.4.0 +version: 3.3.1 readme: README.md authors: - Julie Davila (@juliedavila) From 838bef49cf86ff381f29b396dab940df1ed08298 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 25 Sep 2022 13:55:42 -0400 Subject: [PATCH 12/45] 3.4.0 next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index f1ad908b6..9086bd738 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.3.1 +version: 3.4.0 readme: README.md authors: - Julie Davila (@juliedavila) From 8c9f41df9bd5f2f052a91b0a47c13da8827dba39 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Mon, 26 Sep 2022 22:25:17 -0400 Subject: [PATCH 13/45] update CI (#308) * update CI * macos still stuck on 2.12 --- .github/workflows/ansible-test.yml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 7ec5c5632..211e2ad59 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -40,6 +40,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel steps: @@ -67,7 +68,7 @@ jobs: # will run on all python versions it supports. python-version: 3.9 - # Install the head of the given branch (devel, stable-2.13) + # Install the head of the given branch (devel, stable-2.14) - name: Install ansible-base (${{ matrix.ansible }}) run: pip install https://github.com/ansible/ansible/archive/${{ matrix.ansible }}.tar.gz --disable-pip-version-check @@ -116,6 +117,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel steps: @@ -190,6 +192,7 @@ jobs: - stable-2.11 - stable-2.12 - stable-2.13 + - stable-2.14 - devel python: - '3.6' @@ -301,7 +304,7 @@ jobs: matrix: ansible: - stable-2.12 - - stable-2.13 + - stable-2.14 python: - 3.9 runner: @@ -311,11 +314,11 @@ jobs: - default exclude: # To add to the fragility of testing docker stuff on MacOS, - # stable-2.13 test containers crash; unsure of exact cause + # stable-2.13+ test containers crash; unsure of exact cause # but likely due to old versions of the runtimes. # We'll just stick to 2.12 for now, better than nothing. - runner: macos-12 - ansible: stable-2.13 + ansible: stable-2.14 - runner: ubuntu-latest ansible: stable-2.12 From 0bac5dfee06feff362e3273dbd6bb60ece190020 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Mon, 26 Sep 2022 22:26:35 -0400 Subject: [PATCH 14/45] update readme for 2.14 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f1b4db97d..f4f76a23e 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ If you use the Ansible package and don't update collections independently, use * * 2.11 * 2.12 * 2.13 +* 2.14 * devel (latest development commit) See [the CI configuration](https://github.com/ansible-collections/community.hashi_vault/blob/main/.github/workflows/ansible-test.yml) for the most accurate testing information. From f1180f673ef8082c9a23a12be2f70922c74b92f8 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 23 Oct 2022 14:54:50 -0400 Subject: [PATCH 15/45] replace set-output in workflows (#311) * replace set-output in python versions file * fix * reversi * pythons --- .github/actions/docker-image-versions/versions.py | 5 ++++- .github/workflows/ansible-builder.yml | 4 +++- .github/workflows/ansible-test.yml | 8 ++++---- .github/workflows/github-release.yml | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/actions/docker-image-versions/versions.py b/.github/actions/docker-image-versions/versions.py index 6dc69ffb7..f21efe71b 100755 --- a/.github/actions/docker-image-versions/versions.py +++ b/.github/actions/docker-image-versions/versions.py @@ -8,6 +8,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type +import os import sys import getopt @@ -104,7 +105,9 @@ def main(argv): keep.append(str(ver)) - print('::set-output name=versions::%s' % json.dumps(keep)) + with open(os.environ['GITHUB_OUTPUT'], 'a') as f: + f.write('versions=') + json.dump(keep, f) if __name__ == '__main__': diff --git a/.github/workflows/ansible-builder.yml b/.github/workflows/ansible-builder.yml index 5b594f419..10aeb5c2d 100644 --- a/.github/workflows/ansible-builder.yml +++ b/.github/workflows/ansible-builder.yml @@ -3,10 +3,12 @@ name: ansible-builder on: push: paths: + - '.github/workflows/ansible-builder.yml' - 'meta/execution-environment.yml' - 'meta/ee-requirements.txt' pull_request: paths: + - '.github/workflows/ansible-builder.yml' - 'meta/execution-environment.yml' - 'meta/ee-requirements.txt' schedule: @@ -27,7 +29,7 @@ jobs: path: ansible_collections/${{ env.NAMESPACE }}/${{ env.COLLECTION_NAME }} - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.9 diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index 211e2ad59..c17e80403 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -62,7 +62,7 @@ jobs: run: ln -s "${COLLECTION_PATH}/.github" .github - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: # it is just required to run that once as "ansible-test sanity" in the docker image # will run on all python versions it supports. @@ -137,7 +137,7 @@ jobs: run: ln -s "${COLLECTION_PATH}/.github" .github - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: # it is just required to run that once as "ansible-test units" in the docker image # will run on all python versions it supports. @@ -229,7 +229,7 @@ jobs: run: ln -s "${COLLECTION_PATH}/.github" .github - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.9 @@ -341,7 +341,7 @@ jobs: run: ln -s "${COLLECTION_PATH}/.github" .github - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index 547d16e34..6fce7d2e6 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -18,9 +18,9 @@ jobs: uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: - python-version: 3.8 + python-version: 3.9 - name: Install PyYaml run: pip install pyyaml From 56d4fe166af53771a6e110101362154423bfe848 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Mon, 24 Oct 2022 12:52:56 -0400 Subject: [PATCH 16/45] Fix `namespace` value with newer `requests` (#310) * ensure that namespace is stringified * add changelog fragment * remove unused import --- changelogs/fragments/309-stringify-namespace.yml | 3 +++ plugins/module_utils/_hashi_vault_common.py | 5 +++-- .../plugins/plugin_utils/authentication/test_auth_token.py | 7 +++---- tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py | 7 ++++--- 4 files changed, 13 insertions(+), 9 deletions(-) create mode 100644 changelogs/fragments/309-stringify-namespace.yml diff --git a/changelogs/fragments/309-stringify-namespace.yml b/changelogs/fragments/309-stringify-namespace.yml new file mode 100644 index 000000000..608e62f46 --- /dev/null +++ b/changelogs/fragments/309-stringify-namespace.yml @@ -0,0 +1,3 @@ +--- +bugfixes: + - connection options - the ``namespace`` connection option will be forced into a string to ensure cmpatibility with recent ``requests`` versions (https://github.com/ansible-collections/community.hashi_vault/issues/309). diff --git a/plugins/module_utils/_hashi_vault_common.py b/plugins/module_utils/_hashi_vault_common.py index 11aa9c46e..b39431c05 100644 --- a/plugins/module_utils/_hashi_vault_common.py +++ b/plugins/module_utils/_hashi_vault_common.py @@ -53,13 +53,14 @@ class HashiVaultValueError(ValueError): class HashiVaultHelper(): - STRINGIFY_CANDIDATES = set( + STRINGIFY_CANDIDATES = set([ 'token', # Token will end up in a header, requests requires headers to be str or bytes, # and newer versions of requests stopped converting automatically. Because our # token could have been passed in from a previous lookup call, it could be one # of the AnsibleUnsafe types instead, causing a failure. Tokens should always # be strings, so we will convert them. - ) + 'namespace', # namespace is also set in a header + ]) def __init__(self): # TODO move hvac checking here? diff --git a/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py b/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py index f80dfd9fd..3dbc4c7fc 100644 --- a/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py +++ b/tests/unit/plugins/plugin_utils/authentication/test_auth_token.py @@ -29,7 +29,7 @@ def option_dict(): @pytest.fixture(params=[AnsibleUnsafeBytes(b'ub_opaque'), AnsibleUnsafeText(u'ut_opaque'), b'b_opaque', u't_opaque']) -def token(request): +def stringy(request): return request.param @@ -39,9 +39,8 @@ def auth_token(adapter, warner, deprecator): class TestAuthToken(object): - - def test_auth_token_unsafes(self, auth_token, client, adapter, token): - adapter.set_option('token', token) + def test_auth_token_unsafes(self, auth_token, client, adapter, stringy): + adapter.set_option('token', stringy) adapter.set_option('token_validate', False) wrapper = mock.Mock(wraps=auth_token._stringify) diff --git a/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py b/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py index 1f4132ecb..e71e625c0 100644 --- a/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py +++ b/tests/unit/plugins/plugin_utils/test_hashi_vault_helper.py @@ -8,7 +8,7 @@ import pytest -from ansible.utils.unsafe_proxy import AnsibleUnsafe, AnsibleUnsafeBytes, AnsibleUnsafeText +from ansible.utils.unsafe_proxy import AnsibleUnsafeBytes, AnsibleUnsafeText from ansible_collections.community.hashi_vault.tests.unit.compat import mock from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultHelper @@ -21,9 +21,10 @@ def hashi_vault_helper(): @pytest.fixture def expected_stringify_candidates(): - return set( + return set([ 'token', - ) + 'namespace', + ]) class TestHashiVaultHelper(object): From e6c9a18569f916a9afa15c27ca6bd17f80b06f65 Mon Sep 17 00:00:00 2001 From: Isaac Wagner <11237064+idwagner@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:18:02 -0400 Subject: [PATCH 17/45] Add vault_kv2_delete module (#304) * WIP on hashi_vault.vault_kv2_delete module Adds vault_kv2_delete module Adds vault_kv2_delete unit tests * Tweaks to vault_kv2_delete module - Set default engine path to secret - Add initial documentation * Use vault_write response handling for 200/204s * Additional vault_kv2_delete docs * Update Author * Update unit tests for change in response * Add optional versions parameter for kv2_delete - Adds versions as list[int]. Hvac call will vary based on wether versions is defined or not - Updated unit tests to check correct function call. - Fixes some docs * Fix lint errors * Add latest/specific version matrix to tests * Add integration tests for module_vault_kv2_delete - Adds integration tests for module_vault_kv2_delete module. - Adds vault_ci_kv2_metadata_read for reading kv2 metadata in ci tests. * Move setup to test specific setup Moves the setup for vault_kv2_delete into a test specific setup instead of the general setup. * Apply suggestions from code review Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> * Update to add check mode Updates vault_kv2_delete module and tests for check mode. Vault_kv2_delete will skip the hvac call on check mode and return an empty dictionary. * Fix docs and linting * Remove self-referential link * Apply suggestions from code review Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> * Add integration test for check mode * Style fixes * Fully qualify the module name * Update tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> * Fix tests and add meta runtime Updated integration test to fix invalid yaml anchors Co-authored-by: Isaac Wagner Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> --- meta/runtime.yml | 1 + plugins/modules/vault_kv2_delete.py | 172 +++++++++++++ .../targets/module_vault_kv2_delete/aliases | 1 + .../module_vault_kv2_delete/meta/main.yml | 4 + .../module_vault_kv2_delete/tasks/main.yml | 3 + .../tasks/module_vault_kv2_delete_setup.yml | 28 +++ .../tasks/module_vault_kv2_delete_test.yml | 231 ++++++++++++++++++ .../setup_vault_configure/vars/main.yml | 17 ++ .../library/vault_ci_kv2_metadata_read.py | 42 ++++ .../setup_vault_test_plugins/vars/main.yml | 1 + .../plugins/modules/test_vault_kv2_delete.py | 221 +++++++++++++++++ 11 files changed, 721 insertions(+) create mode 100644 plugins/modules/vault_kv2_delete.py create mode 100644 tests/integration/targets/module_vault_kv2_delete/aliases create mode 100644 tests/integration/targets/module_vault_kv2_delete/meta/main.yml create mode 100644 tests/integration/targets/module_vault_kv2_delete/tasks/main.yml create mode 100644 tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_setup.yml create mode 100644 tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml create mode 100644 tests/integration/targets/setup_vault_test_plugins/library/vault_ci_kv2_metadata_read.py create mode 100644 tests/unit/plugins/modules/test_vault_kv2_delete.py diff --git a/meta/runtime.yml b/meta/runtime.yml index 94440a929..fb5b0b8fb 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -4,6 +4,7 @@ action_groups: # let's keep this in alphabetical order vault: - vault_kv1_get + - vault_kv2_delete - vault_kv2_get - vault_login - vault_pki_generate_certificate diff --git a/plugins/modules/vault_kv2_delete.py b/plugins/modules/vault_kv2_delete.py new file mode 100644 index 000000000..0408e72dd --- /dev/null +++ b/plugins/modules/vault_kv2_delete.py @@ -0,0 +1,172 @@ +#!/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 +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. + - In check mode, the module returns C(changed) status without contacting Vault. + Consider using M(community.hashi_vault.vault_kv2_get) to verify the existence of the secret first. +seealso: + - module: community.hashi_vault.vault_kv2_get + - name: KV2 Secrets Engine + description: Documentation for the Vault KV secrets engine, version 2. + link: https://www.vaultproject.io/docs/secrets/kv/kv-v2 +extends_documentation_fragment: + - community.hashi_vault.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/tests/integration/targets/module_vault_kv2_delete/aliases b/tests/integration/targets/module_vault_kv2_delete/aliases new file mode 100644 index 000000000..7636a9a65 --- /dev/null +++ b/tests/integration/targets/module_vault_kv2_delete/aliases @@ -0,0 +1 @@ +context/target diff --git a/tests/integration/targets/module_vault_kv2_delete/meta/main.yml b/tests/integration/targets/module_vault_kv2_delete/meta/main.yml new file mode 100644 index 000000000..d3acb69e9 --- /dev/null +++ b/tests/integration/targets/module_vault_kv2_delete/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - setup_vault_test_plugins + - setup_vault_configure diff --git a/tests/integration/targets/module_vault_kv2_delete/tasks/main.yml b/tests/integration/targets/module_vault_kv2_delete/tasks/main.yml new file mode 100644 index 000000000..e222b14e6 --- /dev/null +++ b/tests/integration/targets/module_vault_kv2_delete/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: module_vault_kv2_delete_setup.yml +- import_tasks: module_vault_kv2_delete_test.yml diff --git a/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_setup.yml b/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_setup.yml new file mode 100644 index 000000000..4b058c0ce --- /dev/null +++ b/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_setup.yml @@ -0,0 +1,28 @@ +--- +- name: Configuration tasks + module_defaults: + vault_ci_token_create: '{{ vault_plugins_module_defaults_common }}' + block: + - name: Create a test non-root token + vault_ci_token_create: + policies: [test-policy] + register: user_token_cmd + +- name: Configuration tasks + module_defaults: + vault_ci_kv2_destroy_all: '{{ vault_plugins_module_defaults_common }}' + vault_ci_kv_put: '{{ vault_plugins_module_defaults_common }}' + block: + - name: Remove existing multi-version secret + vault_ci_kv2_destroy_all: + mount_point: '{{ vault_kv2_mount_point }}' + path: '{{ vault_kv2_versioned_path }}/secret6' + + - name: Set up a multi versioned secret for delete (v2) + vault_ci_kv_put: + version: 2 + mount_point: '{{ vault_kv2_mount_point }}' + path: '{{ vault_kv2_versioned_path }}/secret6' + secret: + v: value{{ item }} + loop: ["1", "2", "3", "4", "5"] diff --git a/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml b/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml new file mode 100644 index 000000000..62a7ccc4a --- /dev/null +++ b/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml @@ -0,0 +1,231 @@ +--- +- name: Var block + vars: + user_token: '{{ user_token_cmd.result.auth.client_token }}' + regex_secret_version_is_deleted: "^[0-9]{4}-[0-9]{2}-[0-9]{2}T.*" + regex_secret_version_not_deleted: "^$" + + module_defaults: + community.hashi_vault.vault_kv2_delete: &defaults + url: '{{ vault_test_server_http }}' + auth_method: token + token: '{{ user_token }}' + timeout: 5 + vault_ci_kv2_metadata_read: '{{ vault_plugins_module_defaults_common }}' + + block: + - name: Test default path value + register: default_path + community.hashi_vault.vault_kv2_delete: + path: '{{ vault_kv2_path }}/secret2' + ignore_errors: true + + - assert: + that: + - default_path is failed + - default_path.msg is search('Permission Denied to path') + + - module_defaults: + community.hashi_vault.vault_kv2_delete: + <<: *defaults + engine_mount_point: '{{ vault_kv2_mount_point }}' + block: + + - name: Check kv2 existing versions + register: kv2_result + vault_ci_kv2_metadata_read: + path: "{{ vault_kv2_versioned_path }}/secret6" + mount_point: '{{ vault_kv2_mount_point }}' + + - assert: + that: + - "'result' in kv2_result" + - "'data' in kv2_result['result']" + - "'versions' in kv2_result['result']['data']" + - "kv2_result['result']['data']['versions']['1']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['2']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['3']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['4']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['5']['deletion_time'] is search(regex_secret_version_not_deleted)" + fail_msg: 'Test Seed value did not contain expected data.' + + + - name: Try kv2 delete latest version in check mode + register: kv2_result + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/secret6" + check_mode: true + + - assert: + that: + - kv2_result is changed + - kv2_result.data == {} + + - name: Read resultant secret versions + register: kv2_result + vault_ci_kv2_metadata_read: + path: "{{ vault_kv2_versioned_path }}/secret6" + mount_point: '{{ vault_kv2_mount_point }}' + + - assert: + that: + - "'result' in kv2_result" + - "'data' in kv2_result['result']" + - "'versions' in kv2_result['result']['data']" + - "kv2_result['result']['data']['versions']['1']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['2']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['3']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['4']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['5']['deletion_time'] is search(regex_secret_version_not_deleted)" + fail_msg: 'Secret version was deleted while in check mode.' + + + - name: Try kv2 delete specific version in check mode + register: kv2_result + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/secret6" + versions: [1, 3] + check_mode: true + + - name: Read resultant secret versions + register: kv2_result + vault_ci_kv2_metadata_read: + path: "{{ vault_kv2_versioned_path }}/secret6" + mount_point: '{{ vault_kv2_mount_point }}' + + - assert: + that: + - "'result' in kv2_result" + - "'data' in kv2_result['result']" + - "'versions' in kv2_result['result']['data']" + - "kv2_result['result']['data']['versions']['1']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['2']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['3']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['4']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['5']['deletion_time'] is search(regex_secret_version_not_deleted)" + fail_msg: 'Secret version was deleted while in check mode.' + + + - name: Try kv2 delete version 1 and 3 + register: kv2_result + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/secret6" + versions: + - 1 + - 3 + + - name: Read resultant secret versions + register: kv2_result + vault_ci_kv2_metadata_read: + path: "{{ vault_kv2_versioned_path }}/secret6" + mount_point: '{{ vault_kv2_mount_point }}' + + - assert: + that: + - "'result' in kv2_result" + - "'data' in kv2_result['result']" + - "'versions' in kv2_result['result']['data']" + - "kv2_result['result']['data']['versions']['1']['deletion_time'] is search(regex_secret_version_is_deleted)" + - "kv2_result['result']['data']['versions']['2']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['3']['deletion_time'] is search(regex_secret_version_is_deleted)" + - "kv2_result['result']['data']['versions']['4']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['5']['deletion_time'] is search(regex_secret_version_not_deleted)" + fail_msg: 'Result value did not contain expected data.' + + + - name: Try kv2 delete latest version + register: kv2_result + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/secret6" + + - name: Read resultant secret versions + register: kv2_result + vault_ci_kv2_metadata_read: + path: "{{ vault_kv2_versioned_path }}/secret6" + mount_point: '{{ vault_kv2_mount_point }}' + + - assert: + that: + - "'result' in kv2_result" + - "'data' in kv2_result['result']" + - "'versions' in kv2_result['result']['data']" + - "kv2_result['result']['data']['versions']['1']['deletion_time'] is search(regex_secret_version_is_deleted)" + - "kv2_result['result']['data']['versions']['2']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['3']['deletion_time'] is search(regex_secret_version_is_deleted)" + - "kv2_result['result']['data']['versions']['4']['deletion_time'] is search(regex_secret_version_not_deleted)" + - "kv2_result['result']['data']['versions']['5']['deletion_time'] is search(regex_secret_version_is_deleted)" + fail_msg: 'Result value did not contain expected data.' + + + - name: Success expected when authorized delete on non-existent path (latest version) + register: test_nonexistant + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/non_existent_secret" + + + - name: Success expected when authorized delete on non-existent path (specific version) + register: test_nonexistant + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/non_existent_secret" + versions: + - 1 + + + ### failure tests + + - name: Failure expected when erroneous credentials are used (latest version) + register: test_wrong_cred + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/secret6" + token: wrong_token + ignore_errors: true + + - assert: + that: + - test_wrong_cred is failed + - test_wrong_cred.msg is search('Invalid Vault Token') + fail_msg: "Expected failure but got success or wrong failure message." + + + - name: Failure expected when erroneous credentials are used (specific version) + register: test_wrong_cred + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_versioned_path }}/secret6" + token: wrong_token + versions: + - 1 + ignore_errors: true + + - assert: + that: + - test_wrong_cred is failed + - test_wrong_cred.msg is search('Invalid Vault Token') + fail_msg: "Expected failure but got success or wrong failure message." + + + - name: Failure expected when unauthorized secret is deleted (latest version) + register: test_unauthorized + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_path }}/secret3" + ignore_errors: true + + - assert: + that: + - test_unauthorized is failed + - test_unauthorized.msg is search('Permission Denied') + fail_msg: "Expected failure but got success or wrong failure message." + + + - name: Failure expected when unauthorized secret is deleted (specific version) + register: test_unauthorized + community.hashi_vault.vault_kv2_delete: + path: "{{ vault_kv2_path }}/secret3" + versions: + - 1 + ignore_errors: true + + - assert: + that: + - test_unauthorized is failed + - test_unauthorized.msg is search('Permission Denied') + fail_msg: "Expected failure but got success or wrong failure message." diff --git a/tests/integration/targets/setup_vault_configure/vars/main.yml b/tests/integration/targets/setup_vault_configure/vars/main.yml index ee09e3704..2d76ea951 100644 --- a/tests/integration/targets/setup_vault_configure/vars/main.yml +++ b/tests/integration/targets/setup_vault_configure/vars/main.yml @@ -16,6 +16,8 @@ vault_kv2_versioned_path: versioned vault_kv2_api_path: '{{ vault_kv2_mount_point }}/data/{{ vault_kv2_path }}' vault_kv2_multi_api_path: '{{ vault_kv2_mount_point }}/data/{{ vault_kv2_multi_path }}' vault_kv2_versioned_api_path: '{{ vault_kv2_mount_point }}/data/{{ vault_kv2_versioned_path }}' +vault_kv2_delete_api_path: '{{ vault_kv2_mount_point }}/delete/{{ vault_kv2_versioned_path }}' +vault_kv2_metadata_api_path: '{{ vault_kv2_mount_point }}/metadata/{{ vault_kv2_versioned_path }}' vault_base_policy: | path "{{ vault_kv1_api_path }}/secret1" { @@ -52,6 +54,21 @@ vault_base_policy: | path "{{ vault_kv2_versioned_api_path }}/*" { capabilities = ["read"] } + path "{{ vault_kv2_versioned_api_path }}/secret6" { + capabilities = ["delete"] + } + path "{{ vault_kv2_versioned_api_path }}/non_existent_secret" { + capabilities = ["delete"] + } + path "{{ vault_kv2_delete_api_path }}/secret6" { + capabilities = ["create", "update"] + } + path "{{ vault_kv2_delete_api_path }}/non_existent_secret" { + capabilities = ["create", "update"] + } + path "{{ vault_kv2_metadata_api_path }}/secret6" { + capabilities = ["read"] + } vault_token_creator_policy: | path "auth/token/create" { diff --git a/tests/integration/targets/setup_vault_test_plugins/library/vault_ci_kv2_metadata_read.py b/tests/integration/targets/setup_vault_test_plugins/library/vault_ci_kv2_metadata_read.py new file mode 100644 index 000000000..0532124ed --- /dev/null +++ b/tests/integration/targets/setup_vault_test_plugins/library/vault_ci_kv2_metadata_read.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022 Isaac Wagner (@idwagner) +# Simplified BSD License (see LICENSES/BSD-2-Clause.txt or https://opensource.org/licenses/BSD-2-Clause) +# SPDX-License-Identifier: BSD-2-Clause + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +import traceback + +from ansible.module_utils.basic import AnsibleModule +import hvac + + +def main(): + module = AnsibleModule( + argument_spec=dict( + url=dict(type='str', required=True), + token=dict(type='str', required=True), + path=dict(type='str'), + mount_point=dict(type='str'), + ), + ) + + p = module.params + + client = hvac.Client(url=p['url'], token=p['token']) + + extra = {} + if p['mount_point'] is not None: + extra['mount_point'] = p['mount_point'] + + try: + result = client.secrets.kv.v2.read_secret_metadata(path=p['path'], **extra) + except Exception as e: + module.fail_json(msg=str(e), exception=traceback.format_exc()) + + module.exit_json(changed=True, result=result) + + +if __name__ == '__main__': + main() diff --git a/tests/integration/targets/setup_vault_test_plugins/vars/main.yml b/tests/integration/targets/setup_vault_test_plugins/vars/main.yml index 010e89505..3f891566e 100644 --- a/tests/integration/targets/setup_vault_test_plugins/vars/main.yml +++ b/tests/integration/targets/setup_vault_test_plugins/vars/main.yml @@ -11,6 +11,7 @@ vault_plugins_module_defaults: vault_ci_enable_engine: '{{ vault_plugins_module_defaults_common }}' vault_ci_kv_put: '{{ vault_plugins_module_defaults_common }}' vault_ci_kv2_destroy_all: '{{ vault_plugins_module_defaults_common }}' + vault_ci_kv2_metadata_read: '{{ vault_plugins_module_defaults_common }}' vault_ci_policy_put: '{{ vault_plugins_module_defaults_common }}' vault_ci_read: '{{ vault_plugins_module_defaults_common }}' vault_ci_token_create: '{{ vault_plugins_module_defaults_common }}' diff --git a/tests/unit/plugins/modules/test_vault_kv2_delete.py b/tests/unit/plugins/modules/test_vault_kv2_delete.py new file mode 100644 index 000000000..faca37d61 --- /dev/null +++ b/tests/unit/plugins/modules/test_vault_kv2_delete.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +# Copyright (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 + +import pytest +import re +import json + +from ansible.module_utils.basic import missing_required_lib + +from ...compat import mock +from .....plugins.modules import vault_kv2_delete +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError + + +hvac = pytest.importorskip('hvac') + + +pytestmark = pytest.mark.usefixtures( + 'patch_ansible_module', + 'patch_authenticator', + 'patch_get_vault_client', +) + + +def _connection_options(): + return { + 'auth_method': 'token', + 'url': 'http://myvault', + 'token': 'beep-boop', + } + + +def _sample_options(): + return { + 'engine_mount_point': 'secret', + 'path': 'endpoint', + } + + +def _combined_options(**kwargs): + opt = _connection_options() + opt.update(_sample_options()) + opt.update(kwargs) + return opt + + +class TestModuleVaultKv2Delete(): + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_kv2_delete_authentication_error(self, authenticator, exc, capfd): + authenticator.authenticate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg', "result: %r" % result + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_kv2_delete_auth_validation_error(self, authenticator, exc, capfd): + authenticator.validate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg' + + @pytest.mark.parametrize('opt_versions', [None, [1, 3]]) + @pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'versions']], indirect=True) + def test_vault_kv2_delete_empty_response(self, patch_ansible_module, opt_versions, requests_unparseable_response, vault_client, capfd): + client = vault_client + + requests_unparseable_response.status_code = 204 + + if opt_versions: + client.secrets.kv.v2.delete_secret_versions.return_value = requests_unparseable_response + else: + client.secrets.kv.v2.delete_latest_version_of_secret.return_value = requests_unparseable_response + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code == 0, "result: %r" % (result,) + + assert result['data'] == {} + + @pytest.mark.parametrize('opt_versions', [None, [1, 3]]) + @pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'versions']], indirect=True) + def test_vault_kv2_delete_unparseable_response(self, vault_client, opt_versions, requests_unparseable_response, module_warn, capfd): + client = vault_client + + requests_unparseable_response.status_code = 200 + requests_unparseable_response.content = '(☞゚ヮ゚)☞ ┻━┻' + + if opt_versions: + client.secrets.kv.v2.delete_secret_versions.return_value = requests_unparseable_response + else: + client.secrets.kv.v2.delete_latest_version_of_secret.return_value = requests_unparseable_response + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code == 0, "result: %r" % (result,) + assert result['data'] == '(☞゚ヮ゚)☞ ┻━┻' + + module_warn.assert_called_once_with( + 'Vault returned status code 200 and an unparsable body.') + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + def test_vault_kv2_delete_no_hvac(self, capfd): + with mock.patch.multiple(vault_kv2_delete, HAS_HVAC=False, HVAC_IMPORT_ERROR=None, create=True): + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == missing_required_lib('hvac') + + @pytest.mark.parametrize( + 'exc', + [ + (hvac.exceptions.Forbidden, "", + r"^Forbidden: Permission Denied to path \['([^']+)'\]"), + ] + ) + @pytest.mark.parametrize('opt_versions', [None, [1, 3]]) + @pytest.mark.parametrize('opt_path', ['path/1', 'second/path']) + @pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'path', 'versions']], indirect=True) + def test_vault_kv2_delete_vault_exception(self, vault_client, exc, opt_versions, opt_path, capfd): + + client = vault_client + + if opt_versions: + client.secrets.kv.v2.delete_secret_versions.side_effect = exc[0]( + exc[1]) + else: + client.secrets.kv.v2.delete_latest_version_of_secret.side_effect = exc[0]( + exc[1]) + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + match = re.search(exc[2], result['msg']) + assert match is not None, "result: %r\ndid not match: %s" % ( + result, exc[2]) + + assert opt_path == match.group(1) + + @pytest.mark.parametrize('opt__ansible_check_mode', [False, True]) + @pytest.mark.parametrize('opt_versions', [None]) + @pytest.mark.parametrize('patch_ansible_module', [[ + _combined_options(), + '_ansible_check_mode', + 'versions' + ]], indirect=True) + def test_vault_kv2_delete_latest_version_call(self, vault_client, opt__ansible_check_mode, opt_versions, capfd): + + client = vault_client + client.secrets.kv.v2.delete_latest_version_of_secret.return_value = {} + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + if opt__ansible_check_mode: + client.secrets.kv.v2.delete_latest_version_of_secret.assert_not_called() + else: + client.secrets.kv.v2.delete_latest_version_of_secret.assert_called_once_with( + path='endpoint', mount_point='secret') + + @pytest.mark.parametrize('opt__ansible_check_mode', [False, True]) + @pytest.mark.parametrize('opt_versions', [[1, 3]]) + @pytest.mark.parametrize('patch_ansible_module', [[ + _combined_options(), + '_ansible_check_mode', + 'versions' + ]], indirect=True) + def test_vault_kv2_delete_specific_versions_call(self, vault_client, opt__ansible_check_mode, opt_versions, capfd): + + client = vault_client + client.secrets.kv.v2.delete_secret_versions.return_value = {} + + with pytest.raises(SystemExit) as e: + vault_kv2_delete.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + if opt__ansible_check_mode: + client.secrets.kv.v2.delete_secret_versions.assert_not_called() + else: + client.secrets.kv.v2.delete_secret_versions.assert_called_once_with( + path='endpoint', mount_point='secret', versions=[1, 3]) From 00f92add9e75dd1f20b8f9ff7806ccb40c054988 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Tue, 1 Nov 2022 19:11:57 -0400 Subject: [PATCH 18/45] `vault_pki_generate_certificate` - fix argspec doc mismatch (#318) * update docs to match argspec * add changelog fragment --- changelogs/fragments/318-pki-argspec-doc-mismatch.yml | 3 +++ plugins/modules/vault_pki_generate_certificate.py | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 changelogs/fragments/318-pki-argspec-doc-mismatch.yml diff --git a/changelogs/fragments/318-pki-argspec-doc-mismatch.yml b/changelogs/fragments/318-pki-argspec-doc-mismatch.yml new file mode 100644 index 000000000..e79fa5c68 --- /dev/null +++ b/changelogs/fragments/318-pki-argspec-doc-mismatch.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - vault_pki_generate_certificate - the documentation has been updated to match the argspec for the default values of options ``alt_names``, ``ip_sans``, ``other_sans``, and ``uri_sans`` (https://github.com/ansible-collections/community.hashi_vault/pull/318). diff --git a/plugins/modules/vault_pki_generate_certificate.py b/plugins/modules/vault_pki_generate_certificate.py index b67d1231e..ffa3d9038 100644 --- a/plugins/modules/vault_pki_generate_certificate.py +++ b/plugins/modules/vault_pki_generate_certificate.py @@ -37,6 +37,7 @@ - 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. @@ -66,6 +67,7 @@ - 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. @@ -78,6 +80,7 @@ - "The format is the same as OpenSSL: C(;:) 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. @@ -102,6 +105,7 @@ - Specifies the requested URI Subject Alternative Names. type: list elements: str + default: [] """ EXAMPLES = """ From b00468310581802d19deedf621e00679310034c9 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Wed, 2 Nov 2022 08:33:58 -0400 Subject: [PATCH 19/45] update mock.py (#316) --- tests/unit/compat/mock.py | 98 ++------------------------------------- 1 file changed, 3 insertions(+), 95 deletions(-) diff --git a/tests/unit/compat/mock.py b/tests/unit/compat/mock.py index 0972cd2e8..515b94a3c 100644 --- a/tests/unit/compat/mock.py +++ b/tests/unit/compat/mock.py @@ -1,19 +1,6 @@ -# (c) 2014, Toshio Kuratomi -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . +# Copyright (c) 2014, Toshio Kuratomi +# 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 # Make coding more python3-ish from __future__ import (absolute_import, division, print_function) @@ -41,82 +28,3 @@ from mock import * except ImportError: print('You need the mock library installed on python2.x to run tests') - - -# Prior to 3.4.4, mock_open cannot handle binary read_data -if sys.version_info >= (3,) and sys.version_info < (3, 4, 4): - file_spec = None - - def _iterate_read_data(read_data): - # Helper for mock_open: - # Retrieve lines from read_data via a generator so that separate calls to - # readline, read, and readlines are properly interleaved - sep = b'\n' if isinstance(read_data, bytes) else '\n' - data_as_list = [l + sep for l in read_data.split(sep)] - - if data_as_list[-1] == sep: - # If the last line ended in a newline, the list comprehension will have an - # extra entry that's just a newline. Remove this. - data_as_list = data_as_list[:-1] - else: - # If there wasn't an extra newline by itself, then the file being - # emulated doesn't have a newline to end the last line remove the - # newline that our naive format() added - data_as_list[-1] = data_as_list[-1][:-1] - - for line in data_as_list: - yield line - - def mock_open(mock=None, read_data=''): - """ - A helper function to create a mock to replace the use of `open`. It works - for `open` called directly or used as a context manager. - - The `mock` argument is the mock object to configure. If `None` (the - default) then a `MagicMock` will be created for you, with the API limited - to methods or attributes available on standard file handles. - - `read_data` is a string for the `read` methoddline`, and `readlines` of the - file handle to return. This is an empty string by default. - """ - def _readlines_side_effect(*args, **kwargs): - if handle.readlines.return_value is not None: - return handle.readlines.return_value - return list(_data) - - def _read_side_effect(*args, **kwargs): - if handle.read.return_value is not None: - return handle.read.return_value - return type(read_data)().join(_data) - - def _readline_side_effect(): - if handle.readline.return_value is not None: - while True: - yield handle.readline.return_value - for line in _data: - yield line - - global file_spec - if file_spec is None: - import _io - file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO)))) - - if mock is None: - mock = MagicMock(name='open', spec=open) - - handle = MagicMock(spec=file_spec) - handle.__enter__.return_value = handle - - _data = _iterate_read_data(read_data) - - handle.write.return_value = None - handle.read.return_value = None - handle.readline.return_value = None - handle.readlines.return_value = None - - handle.read.side_effect = _read_side_effect - handle.readline.side_effect = _readline_side_effect() - handle.readlines.side_effect = _readlines_side_effect - - mock.return_value = handle - return mock From 0a62288ea492411ab7afdb16487deda9aa3db75f Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:23:47 -0400 Subject: [PATCH 20/45] Release/v3.4.0 (#321) * add release summary * add version_added to vault_kv2_delete * release v3.4.0 --- CHANGELOG.rst | 24 +++++++++++++++++++ changelogs/changelog.yaml | 23 ++++++++++++++++++ .../fragments/309-stringify-namespace.yml | 3 --- .../318-pki-argspec-doc-mismatch.yml | 3 --- plugins/modules/vault_kv2_delete.py | 1 + 5 files changed, 48 insertions(+), 6 deletions(-) delete mode 100644 changelogs/fragments/309-stringify-namespace.yml delete mode 100644 changelogs/fragments/318-pki-argspec-doc-mismatch.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index c25c812e8..236157e2b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,30 @@ community.hashi_vault Release Notes .. contents:: Topics +v3.4.0 +====== + +Release Summary +--------------- + +This release includes a new module, fixes (another) ``requests`` header issue, and updates some inaccurate documentation. +This is the last planned release before v4.0.0. + +Minor Changes +------------- + +- vault_pki_generate_certificate - the documentation has been updated to match the argspec for the default values of options ``alt_names``, ``ip_sans``, ``other_sans``, and ``uri_sans`` (https://github.com/ansible-collections/community.hashi_vault/pull/318). + +Bugfixes +-------- + +- connection options - the ``namespace`` connection option will be forced into a string to ensure cmpatibility with recent ``requests`` versions (https://github.com/ansible-collections/community.hashi_vault/issues/309). + +New Modules +----------- + +- vault_kv2_delete - Delete one or more versions of a secret from HashiCorp Vault's KV version 2 secret store + v3.3.1 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 18d6e4e38..294735447 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -546,3 +546,26 @@ releases: fragments: - 3.3.1.yml release_date: '2022-09-25' + 3.4.0: + changes: + bugfixes: + - connection options - the ``namespace`` connection option will be forced into + a string to ensure cmpatibility with recent ``requests`` versions (https://github.com/ansible-collections/community.hashi_vault/issues/309). + minor_changes: + - vault_pki_generate_certificate - the documentation has been updated to match + the argspec for the default values of options ``alt_names``, ``ip_sans``, + ``other_sans``, and ``uri_sans`` (https://github.com/ansible-collections/community.hashi_vault/pull/318). + release_summary: 'This release includes a new module, fixes (another) ``requests`` + header issue, and updates some inaccurate documentation. + + This is the last planned release before v4.0.0.' + fragments: + - 3.4.0.yml + - 309-stringify-namespace.yml + - 318-pki-argspec-doc-mismatch.yml + modules: + - description: Delete one or more versions of a secret from HashiCorp Vault's + KV version 2 secret store + name: vault_kv2_delete + namespace: '' + release_date: '2022-11-03' diff --git a/changelogs/fragments/309-stringify-namespace.yml b/changelogs/fragments/309-stringify-namespace.yml deleted file mode 100644 index 608e62f46..000000000 --- a/changelogs/fragments/309-stringify-namespace.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -bugfixes: - - connection options - the ``namespace`` connection option will be forced into a string to ensure cmpatibility with recent ``requests`` versions (https://github.com/ansible-collections/community.hashi_vault/issues/309). diff --git a/changelogs/fragments/318-pki-argspec-doc-mismatch.yml b/changelogs/fragments/318-pki-argspec-doc-mismatch.yml deleted file mode 100644 index e79fa5c68..000000000 --- a/changelogs/fragments/318-pki-argspec-doc-mismatch.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - vault_pki_generate_certificate - the documentation has been updated to match the argspec for the default values of options ``alt_names``, ``ip_sans``, ``other_sans``, and ``uri_sans`` (https://github.com/ansible-collections/community.hashi_vault/pull/318). diff --git a/plugins/modules/vault_kv2_delete.py b/plugins/modules/vault_kv2_delete.py index 0408e72dd..e215751f4 100644 --- a/plugins/modules/vault_kv2_delete.py +++ b/plugins/modules/vault_kv2_delete.py @@ -9,6 +9,7 @@ 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 From 67d3a2815f16b38879da47f399dbd26cf71228ac Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Thu, 3 Nov 2022 16:25:20 -0400 Subject: [PATCH 21/45] v4.0.0 next expected release --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 9086bd738..09de925e4 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 3.4.0 +version: 4.0.0 readme: README.md authors: - Julie Davila (@juliedavila) From 75e4c59714e90f840bd94d5afe0b6547d7c6ac9a Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:55:17 -0400 Subject: [PATCH 22/45] set pre-release version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 09de925e4..fdd09b322 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 4.0.0 +version: 4.0.0-devel readme: README.md authors: - Julie Davila (@juliedavila) From cf8788c1669f2ea8e0dd45c1c89db0ca8c16f639 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Fri, 4 Nov 2022 19:24:58 -0400 Subject: [PATCH 23/45] `vault_kv2_get` lookup - change `engine_mount_point` default to `secret` (#322) * change vault_kv2_get engine_mount_point default value * add changelog fragment --- .../279-vault_kv2_get-lookup-mount-default.yml | 3 +++ plugins/lookup/vault_kv2_get.py | 16 +++------------- 2 files changed, 6 insertions(+), 13 deletions(-) create mode 100644 changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml diff --git a/changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml b/changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml new file mode 100644 index 000000000..9f748217a --- /dev/null +++ b/changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml @@ -0,0 +1,3 @@ +--- +breaking_changes: + - vault_kv2_get lookup - as previously announced, the default value for ``engine_mount_point`` in the ``vault_kv2_get`` lookup has changed from ``kv`` to ``secret`` (https://github.com/ansible-collections/community.hashi_vault/issues/279). diff --git a/plugins/lookup/vault_kv2_get.py b/plugins/lookup/vault_kv2_get.py index b159eb1ca..bbd54ef8d 100644 --- a/plugins/lookup/vault_kv2_get.py +++ b/plugins/lookup/vault_kv2_get.py @@ -41,10 +41,7 @@ type: str required: True engine_mount_point: - description: - - The path where the secret backend is mounted. - - The default value is C(kv). - - The default value will change to C(secret) in version 4.0.0 to match the module's default. + default: secret version: description: Specifies the version to return. If not set the latest version is returned. type: int @@ -55,7 +52,7 @@ ansible.builtin.set_fact: response: "{{ lookup('community.hashi_vault.vault_kv2_get', 'hello', url='https://vault:8201') }}" # equivalent API path in 3.x.x is kv/data/hello - # equivalent API path in 4.0.0+ will be secret/data/hello + # equivalent API path in 4.0.0+ is secret/data/hello - name: Display the results ansible.builtin.debug: @@ -208,14 +205,7 @@ def run(self, terms, variables=None, **kwargs): client = self.helper.get_vault_client(**client_args) version = self._options_adapter.get_option_default('version') - engine_mount_point = self._options_adapter.get_option_default('engine_mount_point') - if engine_mount_point is None: - engine_mount_point = 'kv' - display.deprecated( - "The default value for 'engine_mount_point' will change from 'kv' to 'secret'.", - version='4.0.0', - collection_name='community.hashi_vault' - ) + engine_mount_point = self._options_adapter.get_option('engine_mount_point') try: self.authenticator.validate() From a09494961d69f807d7521a249c8bec5677f844aa Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Fri, 4 Nov 2022 19:26:40 -0400 Subject: [PATCH 24/45] update `token_validate` default value from `true` to `false` (#317) * change token_validate default * add changelog fragment * update tests to use validation * remove deprecation code * add coverage --- .../fragments/248-token_validate-default.yml | 3 +++ plugins/doc_fragments/auth.py | 3 +-- plugins/lookup/vault_login.py | 4 ---- plugins/module_utils/_auth_method_token.py | 8 -------- plugins/module_utils/_authenticator.py | 3 +-- plugins/modules/vault_login.py | 8 ++------ .../auth_token/tasks/token_test_controller.yml | 15 +++++---------- .../auth_token/tasks/token_test_target.yml | 13 +++++-------- .../lookup_hashi_vault/tasks/lookup_test.yml | 1 + .../tasks/lookup_vault_kv1_get_test.yml | 1 + .../tasks/lookup_vault_kv2_get_test.yml | 1 + .../tasks/lookup_vault_read_test.yml | 1 + .../tasks/lookup_vault_write_test.yml | 1 + .../tasks/module_vault_kv1_get_test.yml | 1 + .../tasks/module_vault_kv2_delete_test.yml | 1 + .../tasks/module_vault_kv2_get_test.yml | 1 + .../tasks/module_vault_read_test.yml | 1 + .../tasks/module_vault_token_create_test.yml | 8 +++++++- .../tasks/module_vault_write_test.yml | 1 + .../authentication/test_auth_token.py | 11 +++++++++++ 20 files changed, 45 insertions(+), 41 deletions(-) create mode 100644 changelogs/fragments/248-token_validate-default.yml diff --git a/changelogs/fragments/248-token_validate-default.yml b/changelogs/fragments/248-token_validate-default.yml new file mode 100644 index 000000000..6f476cdcf --- /dev/null +++ b/changelogs/fragments/248-token_validate-default.yml @@ -0,0 +1,3 @@ +--- +breaking_changes: + - auth - the default value for ``token_validate`` has changed from ``true`` to ``false``, as previously announced (https://github.com/ansible-collections/community.hashi_vault/issues/248). diff --git a/plugins/doc_fragments/auth.py b/plugins/doc_fragments/auth.py index a13dc71c4..8c6bd8760 100644 --- a/plugins/doc_fragments/auth.py +++ b/plugins/doc_fragments/auth.py @@ -54,9 +54,8 @@ class ModuleDocFragment(object): description: - For token auth, will perform a C(lookup-self) operation to determine the token's validity before using it. - Disable if your token does not have the C(lookup-self) capability. - - The default value is C(true). - - The default value will change to C(false) in version 4.0.0. type: bool + default: false version_added: 0.2.0 username: description: Authentication user name. diff --git a/plugins/lookup/vault_login.py b/plugins/lookup/vault_login.py index 08980ecaa..ebb49e4a3 100644 --- a/plugins/lookup/vault_login.py +++ b/plugins/lookup/vault_login.py @@ -42,12 +42,8 @@ type: str required: false token_validate: - description: - - For token auth, will perform a C(lookup-self) operation to determine the token's validity before using it. - - Disable if your token does not have the C(lookup-self) capability. default: true """ -# TODO: remove token_validate description in 4.0.0 when it will match the doc frag description. EXAMPLES = """ - name: Set a fact with a lookup result diff --git a/plugins/module_utils/_auth_method_token.py b/plugins/module_utils/_auth_method_token.py index aa385be76..3b66b1937 100644 --- a/plugins/module_utils/_auth_method_token.py +++ b/plugins/module_utils/_auth_method_token.py @@ -77,14 +77,6 @@ def validate(self): with open(token_filename) as token_file: self._options.set_option('token', token_file.read().strip()) - if self._options.get_option_default('token_validate') is None: - self._options.set_option('token_validate', True) - self.deprecate( - "The default value for 'token_validate' will change from True to False.", - version='4.0.0', - collection_name='community.hashi_vault' - ) - if self._options.get_option_default('token') is None: raise HashiVaultValueError("No Vault Token specified or discovered.") diff --git a/plugins/module_utils/_authenticator.py b/plugins/module_utils/_authenticator.py index 2292d5b6b..acf574bfe 100644 --- a/plugins/module_utils/_authenticator.py +++ b/plugins/module_utils/_authenticator.py @@ -43,8 +43,7 @@ class HashiVaultAuthenticator(): token=dict(type='str', no_log=True, default=None), token_path=dict(type='str', default=None, no_log=False), token_file=dict(type='str', default='.vault-token'), - # TODO: token_validate default becomes False in 4.0.0 - token_validate=dict(type='bool'), + token_validate=dict(type='bool', default=False), username=dict(type='str'), password=dict(type='str', no_log=True), role_id=dict(type='str'), diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index 0cc411cec..5c7622131 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -41,12 +41,8 @@ It may be better to use this module with C(check_mode=no) in order to have a valid token that can be used." options: token_validate: - description: - - For token auth, will perform a C(lookup-self) operation to determine the token's validity before using it. - - Disable if your token does not have the C(lookup-self) capability. default: true """ -# TODO: remove token_validate description in 4.0.0 when it will match the doc frag description. EXAMPLES = """ - name: Login and use the resulting token @@ -123,8 +119,8 @@ def run_module(): 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 will differ from the rest of the - # collection after 4.0.0. + # this module should be True, which differs from the rest of the + # collection since 4.0.0. token_validate=dict(type='bool', default=True) ) diff --git a/tests/integration/targets/auth_token/tasks/token_test_controller.yml b/tests/integration/targets/auth_token/tasks/token_test_controller.yml index d88114eed..137c747ce 100644 --- a/tests/integration/targets/auth_token/tasks/token_test_controller.yml +++ b/tests/integration/targets/auth_token/tasks/token_test_controller.yml @@ -11,10 +11,8 @@ ansible_hashi_vault_token: '{{ user_token }}' - name: Authenticate with a 'no default policy' token (failure expected) - # vars: - # ansible_hashi_vault_token_validate: true - # This will fail when the default changes in 4.0.0. Uncomment above. - # https://github.com/ansible-collections/community.hashi_vault/issues/248 + vars: + ansible_hashi_vault_token_validate: true set_fact: response: "{{ lookup('vault_test_auth', want_exception=true) }}" @@ -24,10 +22,6 @@ - response.msg is search('Invalid Vault Token') - name: Authenticate with 'no default policy' token - with no validation - vars: - ansible_hashi_vault_token_validate: false - # After 4.0.0, let's let this one use the default value and unset the above. - # https://github.com/ansible-collections/community.hashi_vault/issues/248 set_fact: response: "{{ lookup('vault_test_auth') }}" @@ -73,8 +67,9 @@ vars: user_token: '{{ user_token_cmd.result.auth.client_token }}' expected_policy: test-policy + ansible_hashi_vault_token_validate: true block: - - name: Authenticate with a token + - name: Authenticate with a token (with validation) set_fact: response: "{{ lookup('vault_test_auth', token=user_token) }}" @@ -84,7 +79,7 @@ - expected_policy in response.login.data.policies - expected_policy in response.login.auth.policies - - name: Authenticate with an invalid token + - name: Authenticate with an invalid token (wuth validation) set_fact: response: "{{ lookup('vault_test_auth', token='fake', want_exception=true) }}" diff --git a/tests/integration/targets/auth_token/tasks/token_test_target.yml b/tests/integration/targets/auth_token/tasks/token_test_target.yml index a5b41305e..cbfa30f70 100644 --- a/tests/integration/targets/auth_token/tasks/token_test_target.yml +++ b/tests/integration/targets/auth_token/tasks/token_test_target.yml @@ -11,9 +11,7 @@ - name: Authenticate with a 'no default policy' token (failure expected) register: response vault_test_auth: - # token_validate: true - # This will fail when the default changes in 4.0.0. Uncomment above. - # https://github.com/ansible-collections/community.hashi_vault/issues/248 + token_validate: true want_exception: true - assert: @@ -24,9 +22,6 @@ - name: Authenticate with 'no default policy' token - with no validation register: response vault_test_auth: - token_validate: false - # After 4.0.0, let's let this one use the default value and unset the above. - # https://github.com/ansible-collections/community.hashi_vault/issues/248 - assert: that: response.login.auth.client_token == user_token @@ -74,10 +69,11 @@ vault_test_auth: url: '{{ ansible_hashi_vault_url }}' block: - - name: Authenticate with a token + - name: Authenticate with a token (with validation) register: response vault_test_auth: token: '{{ user_token }}' + token_validate: true - assert: that: @@ -85,10 +81,11 @@ - expected_policy in response.login.data.policies - expected_policy in response.login.auth.policies - - name: Authenticate with an invalid token + - name: Authenticate with an invalid token (with validation) register: response vault_test_auth: token: fake + token_validate: true want_exception: true - assert: diff --git a/tests/integration/targets/lookup_hashi_vault/tasks/lookup_test.yml b/tests/integration/targets/lookup_hashi_vault/tasks/lookup_test.yml index e0c7ba240..adca2d0db 100644 --- a/tests/integration/targets/lookup_hashi_vault/tasks/lookup_test.yml +++ b/tests/integration/targets/lookup_hashi_vault/tasks/lookup_test.yml @@ -1,6 +1,7 @@ --- - name: Var block vars: + ansible_hashi_vault_token_validate: true user_token: '{{ user_token_cmd.result.auth.client_token }}' kwargs: url: '{{ vault_test_server_http }}' diff --git a/tests/integration/targets/lookup_vault_kv1_get/tasks/lookup_vault_kv1_get_test.yml b/tests/integration/targets/lookup_vault_kv1_get/tasks/lookup_vault_kv1_get_test.yml index 95ce7777b..ed317023d 100644 --- a/tests/integration/targets/lookup_vault_kv1_get/tasks/lookup_vault_kv1_get_test.yml +++ b/tests/integration/targets/lookup_vault_kv1_get/tasks/lookup_vault_kv1_get_test.yml @@ -1,6 +1,7 @@ --- - name: Var block vars: + ansible_hashi_vault_token_validate: true user_token: '{{ user_token_cmd.result.auth.client_token }}' kwargs: url: '{{ vault_test_server_http }}' diff --git a/tests/integration/targets/lookup_vault_kv2_get/tasks/lookup_vault_kv2_get_test.yml b/tests/integration/targets/lookup_vault_kv2_get/tasks/lookup_vault_kv2_get_test.yml index 41ed54a42..b335290ec 100644 --- a/tests/integration/targets/lookup_vault_kv2_get/tasks/lookup_vault_kv2_get_test.yml +++ b/tests/integration/targets/lookup_vault_kv2_get/tasks/lookup_vault_kv2_get_test.yml @@ -1,6 +1,7 @@ --- - name: Var block vars: + ansible_hashi_vault_token_validate: true user_token: '{{ user_token_cmd.result.auth.client_token }}' kwargs: url: '{{ vault_test_server_http }}' diff --git a/tests/integration/targets/lookup_vault_read/tasks/lookup_vault_read_test.yml b/tests/integration/targets/lookup_vault_read/tasks/lookup_vault_read_test.yml index b2a63637f..111ccdcf8 100644 --- a/tests/integration/targets/lookup_vault_read/tasks/lookup_vault_read_test.yml +++ b/tests/integration/targets/lookup_vault_read/tasks/lookup_vault_read_test.yml @@ -1,6 +1,7 @@ --- - name: Var block vars: + ansible_hashi_vault_token_validate: true user_token: '{{ user_token_cmd.result.auth.client_token }}' kwargs: url: '{{ vault_test_server_http }}' diff --git a/tests/integration/targets/lookup_vault_write/tasks/lookup_vault_write_test.yml b/tests/integration/targets/lookup_vault_write/tasks/lookup_vault_write_test.yml index 984ae5595..33dc245f9 100644 --- a/tests/integration/targets/lookup_vault_write/tasks/lookup_vault_write_test.yml +++ b/tests/integration/targets/lookup_vault_write/tasks/lookup_vault_write_test.yml @@ -1,6 +1,7 @@ --- - name: Var block vars: + ansible_hashi_vault_token_validate: true user_token: '{{ user_token_cmd.result.auth.client_token }}' kwargs: url: '{{ vault_test_server_http }}' diff --git a/tests/integration/targets/module_vault_kv1_get/tasks/module_vault_kv1_get_test.yml b/tests/integration/targets/module_vault_kv1_get/tasks/module_vault_kv1_get_test.yml index 5f474bc11..e6bfb1c1f 100644 --- a/tests/integration/targets/module_vault_kv1_get/tasks/module_vault_kv1_get_test.yml +++ b/tests/integration/targets/module_vault_kv1_get/tasks/module_vault_kv1_get_test.yml @@ -7,6 +7,7 @@ url: '{{ vault_test_server_http }}' auth_method: token token: '{{ user_token }}' + token_validate: true timeout: 5 block: - name: Test defualt path value diff --git a/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml b/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml index 62a7ccc4a..57e770526 100644 --- a/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml +++ b/tests/integration/targets/module_vault_kv2_delete/tasks/module_vault_kv2_delete_test.yml @@ -10,6 +10,7 @@ url: '{{ vault_test_server_http }}' auth_method: token token: '{{ user_token }}' + token_validate: true timeout: 5 vault_ci_kv2_metadata_read: '{{ vault_plugins_module_defaults_common }}' diff --git a/tests/integration/targets/module_vault_kv2_get/tasks/module_vault_kv2_get_test.yml b/tests/integration/targets/module_vault_kv2_get/tasks/module_vault_kv2_get_test.yml index cdd330847..8cb4edf0f 100644 --- a/tests/integration/targets/module_vault_kv2_get/tasks/module_vault_kv2_get_test.yml +++ b/tests/integration/targets/module_vault_kv2_get/tasks/module_vault_kv2_get_test.yml @@ -7,6 +7,7 @@ url: '{{ vault_test_server_http }}' auth_method: token token: '{{ user_token }}' + token_validate: true timeout: 5 block: - name: Test defualt path value diff --git a/tests/integration/targets/module_vault_read/tasks/module_vault_read_test.yml b/tests/integration/targets/module_vault_read/tasks/module_vault_read_test.yml index 6d6ea30b7..55c27d435 100644 --- a/tests/integration/targets/module_vault_read/tasks/module_vault_read_test.yml +++ b/tests/integration/targets/module_vault_read/tasks/module_vault_read_test.yml @@ -7,6 +7,7 @@ url: '{{ vault_test_server_http }}' auth_method: token token: '{{ user_token }}' + token_validate: true timeout: 5 block: - name: 'Check kv2 secret read' diff --git a/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml b/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml index 947d530b5..67e9981ea 100644 --- a/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml +++ b/tests/integration/targets/module_vault_token_create/tasks/module_vault_token_create_test.yml @@ -169,4 +169,10 @@ - "'client_token' in result.login.auth" - result.login.auth.client_token != 'static_token' - result.login.auth.client_token == None - - lookup('vault_test_auth', token='static_token', url=vault_test_server_http, want_exception=True) is failed + - >- + lookup('vault_test_auth', + token='static_token', + token_validate=true, + url=vault_test_server_http, + want_exception=True + ) is failed diff --git a/tests/integration/targets/module_vault_write/tasks/module_vault_write_test.yml b/tests/integration/targets/module_vault_write/tasks/module_vault_write_test.yml index 7332a412c..244b8e29a 100644 --- a/tests/integration/targets/module_vault_write/tasks/module_vault_write_test.yml +++ b/tests/integration/targets/module_vault_write/tasks/module_vault_write_test.yml @@ -9,6 +9,7 @@ community.hashi_vault.vault_write: <<: *defaults auth_method: token + token_validate: true timeout: 5 block: - name: Write data to the cubbyhole (check mode) diff --git a/tests/unit/plugins/module_utils/authentication/test_auth_token.py b/tests/unit/plugins/module_utils/authentication/test_auth_token.py index 1d8d3a708..7f5d12802 100644 --- a/tests/unit/plugins/module_utils/authentication/test_auth_token.py +++ b/tests/unit/plugins/module_utils/authentication/test_auth_token.py @@ -175,3 +175,14 @@ def test_auth_token_authenticate_failed_validation(self, auth_token, adapter, cl with pytest.raises(HashiVaultValueError, match=r'Invalid Vault Token Specified'): with mock.patch.object(client.auth.token, 'lookup_self', raiser): auth_token.authenticate(client, use_token=True, lookup_self=False) + + @pytest.mark.parametrize('exc', [AttributeError, NotImplementedError]) + def test_auth_token_authenticate_old_lookup_self(self, auth_token, adapter, client, token, exc): + adapter.set_option('token', token) + + with mock.patch.object(client, 'lookup_token') as legacy_lookup: + with mock.patch.object(client.auth.token, 'lookup_self', side_effect=exc) as lookup: + auth_token.authenticate(client, use_token=True, lookup_self=True) + + legacy_lookup.assert_called_once_with() + lookup.assert_called_once_with() From 2afac5626055c6cca8c05749efce1c15ec5a65ad Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 5 Nov 2022 16:21:27 -0400 Subject: [PATCH 25/45] add `action_group` and `check_mode` attributes (#320) * add action_group attribute * add missing fields * Update plugins/doc_fragments/action_group.py Co-authored-by: Felix Fontein * refactor * update modules * add changelog * missing options in fragment Co-authored-by: Felix Fontein --- .../fragments/197-module-attributes.yml | 3 ++ plugins/doc_fragments/attributes.py | 33 +++++++++++++++++++ plugins/modules/vault_kv1_get.py | 2 ++ plugins/modules/vault_kv2_delete.py | 9 +++-- plugins/modules/vault_kv2_get.py | 2 ++ plugins/modules/vault_login.py | 11 +++++-- .../modules/vault_pki_generate_certificate.py | 5 +++ plugins/modules/vault_read.py | 2 ++ plugins/modules/vault_token_create.py | 11 +++++-- plugins/modules/vault_write.py | 5 +++ 10 files changed, 75 insertions(+), 8 deletions(-) create mode 100644 changelogs/fragments/197-module-attributes.yml create mode 100644 plugins/doc_fragments/attributes.py diff --git a/changelogs/fragments/197-module-attributes.yml b/changelogs/fragments/197-module-attributes.yml new file mode 100644 index 000000000..2164cfd9c --- /dev/null +++ b/changelogs/fragments/197-module-attributes.yml @@ -0,0 +1,3 @@ +--- +minor_changes: + - modules - all modules now document their action group and support for check mode in their attributes documentation (https://github.com/ansible-collections/community.hashi_vault/issues/197). diff --git a/plugins/doc_fragments/attributes.py b/plugins/doc_fragments/attributes.py new file mode 100644 index 000000000..5ac6a5a30 --- /dev/null +++ b/plugins/doc_fragments/attributes.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +# Copyright: (c) 2022, Brian Scholer (@briantist) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +class ModuleDocFragment(object): + + DOCUMENTATION = r''' +options: {} +''' + + ACTION_GROUP = r''' +options: {} +attributes: + action_group: + description: Use C(group/community.hashi_vault.vault) in C(module_defaults) to set defaults for this module. + support: full + membership: + - community.hashi_vault.vault +''' + + CHECK_MODE_READ_ONLY = r''' +options: {} +attributes: + check_mode: + support: full + description: This module is "read only" and operates the same regardless of check mode. +''' diff --git a/plugins/modules/vault_kv1_get.py b/plugins/modules/vault_kv1_get.py index 667a11378..0921d6384 100644 --- a/plugins/modules/vault_kv1_get.py +++ b/plugins/modules/vault_kv1_get.py @@ -26,6 +26,8 @@ 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.action_group + - community.hashi_vault.attributes.check_mode_read_only - community.hashi_vault.connection - community.hashi_vault.auth - community.hashi_vault.engine_mount diff --git a/plugins/modules/vault_kv2_delete.py b/plugins/modules/vault_kv2_delete.py index e215751f4..923eeb521 100644 --- a/plugins/modules/vault_kv2_delete.py +++ b/plugins/modules/vault_kv2_delete.py @@ -21,14 +21,19 @@ 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. - - 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. +attributes: + check_mode: + support: partial + description: + - In check mode, the module returns C(changed) status without contacting Vault. + Consider using M(community.hashi_vault.vault_kv2_get) to verify the existence of the secret first. seealso: - module: community.hashi_vault.vault_kv2_get - name: KV2 Secrets Engine description: Documentation for the Vault KV secrets engine, version 2. link: https://www.vaultproject.io/docs/secrets/kv/kv-v2 extends_documentation_fragment: + - community.hashi_vault.attributes.action_group - community.hashi_vault.connection - community.hashi_vault.auth - community.hashi_vault.engine_mount diff --git a/plugins/modules/vault_kv2_get.py b/plugins/modules/vault_kv2_get.py index 343da2658..07e6d7d69 100644 --- a/plugins/modules/vault_kv2_get.py +++ b/plugins/modules/vault_kv2_get.py @@ -26,6 +26,8 @@ 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.action_group + - community.hashi_vault.attributes.check_mode_read_only - community.hashi_vault.connection - community.hashi_vault.auth - community.hashi_vault.engine_mount diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index 5c7622131..e396106db 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -24,6 +24,7 @@ - ref: 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.action_group - community.hashi_vault.connection - community.hashi_vault.auth notes: @@ -36,9 +37,13 @@ - "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." - - "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=no) in order to have a valid token that can be used." + attributes: + check_mode: + support: partial + description: + - "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=no) in order to have a valid token that can be used." options: token_validate: default: true diff --git a/plugins/modules/vault_pki_generate_certificate.py b/plugins/modules/vault_pki_generate_certificate.py index ffa3d9038..030ff2f87 100644 --- a/plugins/modules/vault_pki_generate_certificate.py +++ b/plugins/modules/vault_pki_generate_certificate.py @@ -26,9 +26,14 @@ 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.action_group - community.hashi_vault.connection - community.hashi_vault.auth - community.hashi_vault.engine_mount + attributes: + check_mode: + support: partial + description: 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: diff --git a/plugins/modules/vault_read.py b/plugins/modules/vault_read.py index 5bbda8dec..fbb2f611b 100644 --- a/plugins/modules/vault_read.py +++ b/plugins/modules/vault_read.py @@ -24,6 +24,8 @@ - ref: 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.action_group + - community.hashi_vault.attributes.check_mode_read_only - community.hashi_vault.connection - community.hashi_vault.auth options: diff --git a/plugins/modules/vault_token_create.py b/plugins/modules/vault_token_create.py index 4fd757b06..fe24cfde4 100644 --- a/plugins/modules/vault_token_create.py +++ b/plugins/modules/vault_token_create.py @@ -27,6 +27,7 @@ - ref: 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.action_group - community.hashi_vault.connection - community.hashi_vault.auth - community.hashi_vault.token_create @@ -35,9 +36,13 @@ - Token creation is a write operation (creating a token persisted to storage), so this module always reports C(changed=True). - For the purposes of Ansible playbooks however, it may be more useful to set I(changed_when=false) if you are doing idempotency checks against the target system. - - In check mode, this module will not create a token, and will instead return a basic structure with an empty token. - However, this may not be useful if the token is required for follow on tasks. - It may be better to use this module with I(check_mode=no) in order to have a valid token that can be used. + attributes: + check_mode: + support: partial + description: + - In check mode, this module will not create a token, and will instead return a basic structure with an empty token. + However, this may not be useful if the token is required for follow on tasks. + It may be better to use this module with I(check_mode=no) in order to have a valid token that can be used. options: {} """ diff --git a/plugins/modules/vault_write.py b/plugins/modules/vault_write.py index e0833da94..a28c5e54d 100644 --- a/plugins/modules/vault_write.py +++ b/plugins/modules/vault_write.py @@ -23,6 +23,10 @@ - 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 + description: In check mode, an empty response will be returned and the write will not be performed. seealso: - ref: community.hashi_vault.vault_write lookup description: The official documentation for the C(community.hashi_vault.vault_write) lookup plugin. @@ -30,6 +34,7 @@ - ref: 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.action_group - community.hashi_vault.connection - community.hashi_vault.auth - community.hashi_vault.wrapping From 65f73004ce9d7815a695f00cc1d64f49348b0d57 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 5 Nov 2022 17:23:10 -0400 Subject: [PATCH 26/45] Release/v4.0.0 (#323) * bump version to v4.0.0 * add release summary * release v4.0.0 --- CHANGELOG.rst | 19 ++++++++++++++++++ changelogs/changelog.yaml | 20 +++++++++++++++++++ .../fragments/197-module-attributes.yml | 3 --- .../fragments/248-token_validate-default.yml | 3 --- ...279-vault_kv2_get-lookup-mount-default.yml | 3 --- galaxy.yml | 2 +- 6 files changed, 40 insertions(+), 10 deletions(-) delete mode 100644 changelogs/fragments/197-module-attributes.yml delete mode 100644 changelogs/fragments/248-token_validate-default.yml delete mode 100644 changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 236157e2b..48125bde6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,25 @@ community.hashi_vault Release Notes .. contents:: Topics +v4.0.0 +====== + +Release Summary +--------------- + +The next major version of the collection includes previously announced breaking changes to some default values, and improvements to module documentation with attributes that describe the use of action groups and check mode support. + +Minor Changes +------------- + +- modules - all modules now document their action group and support for check mode in their attributes documentation (https://github.com/ansible-collections/community.hashi_vault/issues/197). + +Breaking Changes / Porting Guide +-------------------------------- + +- auth - the default value for ``token_validate`` has changed from ``true`` to ``false``, as previously announced (https://github.com/ansible-collections/community.hashi_vault/issues/248). +- vault_kv2_get lookup - as previously announced, the default value for ``engine_mount_point`` in the ``vault_kv2_get`` lookup has changed from ``kv`` to ``secret`` (https://github.com/ansible-collections/community.hashi_vault/issues/279). + v3.4.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index 294735447..bbb05de95 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -569,3 +569,23 @@ releases: name: vault_kv2_delete namespace: '' release_date: '2022-11-03' + 4.0.0: + changes: + breaking_changes: + - auth - the default value for ``token_validate`` has changed from ``true`` + to ``false``, as previously announced (https://github.com/ansible-collections/community.hashi_vault/issues/248). + - vault_kv2_get lookup - as previously announced, the default value for ``engine_mount_point`` + in the ``vault_kv2_get`` lookup has changed from ``kv`` to ``secret`` (https://github.com/ansible-collections/community.hashi_vault/issues/279). + minor_changes: + - modules - all modules now document their action group and support for check + mode in their attributes documentation (https://github.com/ansible-collections/community.hashi_vault/issues/197). + release_summary: The next major version of the collection includes previously + announced breaking changes to some default values, and improvements to module + documentation with attributes that describe the use of action groups and check + mode support. + fragments: + - 197-module-attributes.yml + - 248-token_validate-default.yml + - 279-vault_kv2_get-lookup-mount-default.yml + - 4.0.0.yml + release_date: '2022-11-05' diff --git a/changelogs/fragments/197-module-attributes.yml b/changelogs/fragments/197-module-attributes.yml deleted file mode 100644 index 2164cfd9c..000000000 --- a/changelogs/fragments/197-module-attributes.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -minor_changes: - - modules - all modules now document their action group and support for check mode in their attributes documentation (https://github.com/ansible-collections/community.hashi_vault/issues/197). diff --git a/changelogs/fragments/248-token_validate-default.yml b/changelogs/fragments/248-token_validate-default.yml deleted file mode 100644 index 6f476cdcf..000000000 --- a/changelogs/fragments/248-token_validate-default.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -breaking_changes: - - auth - the default value for ``token_validate`` has changed from ``true`` to ``false``, as previously announced (https://github.com/ansible-collections/community.hashi_vault/issues/248). diff --git a/changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml b/changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml deleted file mode 100644 index 9f748217a..000000000 --- a/changelogs/fragments/279-vault_kv2_get-lookup-mount-default.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -breaking_changes: - - vault_kv2_get lookup - as previously announced, the default value for ``engine_mount_point`` in the ``vault_kv2_get`` lookup has changed from ``kv`` to ``secret`` (https://github.com/ansible-collections/community.hashi_vault/issues/279). diff --git a/galaxy.yml b/galaxy.yml index fdd09b322..09de925e4 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 4.0.0-devel +version: 4.0.0 readme: README.md authors: - Julie Davila (@juliedavila) From a5ed14c6ecd69bd3ccca36fef5f1f1698c96fc7a Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 5 Nov 2022 17:56:26 -0400 Subject: [PATCH 27/45] 4.1.0 next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 09de925e4..e913e02eb 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 4.0.0 +version: 4.1.0-devel readme: README.md authors: - Julie Davila (@juliedavila) From 7fc08c056629ccc2225c513157837e4d7cdd5396 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 10 Dec 2022 19:50:52 -0800 Subject: [PATCH 28/45] fix version finder (#336) --- .github/actions/docker-image-versions/versions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/docker-image-versions/versions.py b/.github/actions/docker-image-versions/versions.py index f21efe71b..71ba9ce73 100755 --- a/.github/actions/docker-image-versions/versions.py +++ b/.github/actions/docker-image-versions/versions.py @@ -72,10 +72,10 @@ def main(argv): try: vobj = version.parse(tag['name']) except Exception: - pass - - if vobj is None or isinstance(vobj, version.LegacyVersion): continue + else: + if not isinstance(vobj, version.Version): + continue if vobj.is_prerelease is include_prerelease and vobj.is_postrelease is include_postrelease: versions.append(vobj) From 43ed19e370737e89194ee76d36716ba3f3cd9d8a Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 18 Dec 2022 12:59:20 -0500 Subject: [PATCH 29/45] Remove MacOS Local Integration tests from CI (#337) * macos fix? * temporarily disable integration * weong syntax * move python to controller config * macos-a213 * remove macos LI tests * remove macos-docker action --- .github/actions/macos-docker/action.yml | 23 ----------------------- .github/workflows/ansible-test.yml | 24 ++---------------------- 2 files changed, 2 insertions(+), 45 deletions(-) delete mode 100644 .github/actions/macos-docker/action.yml diff --git a/.github/actions/macos-docker/action.yml b/.github/actions/macos-docker/action.yml deleted file mode 100644 index 70833113a..000000000 --- a/.github/actions/macos-docker/action.yml +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Install Docker on MacOS GitHub Runner -description: Install and configure docker for a MacOS GitHub runner, and export the environment variables. -branding: - icon: command - color: white -runs: - using: composite - steps: - - shell: bash - run: | - mkdir -p ~/.docker/machine/cache - curl -Lo ~/.docker/machine/cache/boot2docker.iso https://github.com/boot2docker/boot2docker/releases/download/v19.03.12/boot2docker.iso - - sudo mkdir -p /etc/vbox - sudo chown -R $(whoami) /etc/vbox - sudo echo '* 0.0.0.0/0 ::/0' > /etc/vbox/networks.conf - - brew install docker-machine docker - docker --version - docker-machine create --driver virtualbox default - docker-machine env default - docker-machine env default | sed 's/^export //;/^#/d;s/^#.*//' | tr -d '"' >> $GITHUB_ENV diff --git a/.github/workflows/ansible-test.yml b/.github/workflows/ansible-test.yml index c17e80403..8eac2e64b 100644 --- a/.github/workflows/ansible-test.yml +++ b/.github/workflows/ansible-test.yml @@ -296,31 +296,18 @@ jobs: local_test_invocation: runs-on: ${{ matrix.runner }} name: LI - ${{ matrix.runner }} (Ⓐ${{ matrix.ansible }}+py${{ matrix.python }}) - env: - # needed to prevent Ansible crashing on MacOS - OBJC_DISABLE_INITIALIZE_FORK_SAFETY: 'YES' strategy: fail-fast: false matrix: ansible: - - stable-2.12 - stable-2.14 + - devel python: - 3.9 runner: - ubuntu-latest - - macos-12 test_container: - default - exclude: - # To add to the fragility of testing docker stuff on MacOS, - # stable-2.13+ test containers crash; unsure of exact cause - # but likely due to old versions of the runtimes. - # We'll just stick to 2.12 for now, better than nothing. - - runner: macos-12 - ansible: stable-2.14 - - runner: ubuntu-latest - ansible: stable-2.12 steps: - name: Initialize env vars @@ -330,7 +317,7 @@ jobs: COLLECTION_PATH=ansible_collections/${NAMESPACE}/${COLLECTION_NAME} COLLECTION_INTEGRATION_PATH=${COLLECTION_PATH}/tests/integration COLLECTION_INTEGRATION_TARGETS=${COLLECTION_INTEGRATION_PATH}/targets - DOCKER_TEST_INVOCATION="integration -v --color --retry-on-error --continue-on-error --python ${{ matrix.python }} --docker ${{ matrix.test_container }} ${{ github.event_name != 'schedule' && '--coverage' || '' }}" + DOCKER_TEST_INVOCATION="integration -v --color --retry-on-error --continue-on-error --controller docker:${{ matrix.test_container }},python=${{ matrix.python }} ${{ github.event_name != 'schedule' && '--coverage' || '' }}" - name: Check out code uses: actions/checkout@v3 @@ -358,12 +345,6 @@ jobs: with: collection: community.docker - - name: Install Docker on MacOS - if: ${{ startsWith(matrix.runner, 'macos') }} - timeout-minutes: 6 - # sometimes this hangs forever waiting for an IP - uses: ./.github/actions/macos-docker - - name: Pull Ansible test images timeout-minutes: 5 continue-on-error: true @@ -391,7 +372,6 @@ jobs: #TODO add capability in the Ansible side once vault_list and vault_delete exist - name: Run a third time, but delete Vault's cubbyhole contents first - if: startsWith(matrix.runner, 'ubuntu') working-directory: ${{ env.COLLECTION_PATH }} env: VAULT_TOKEN: 47542cbc-6bf8-4fba-8eda-02e0a0d29a0a From 6c93da9a5066a86bf8208a2fc8911ceb589ee173 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 8 Jan 2023 18:13:46 -0500 Subject: [PATCH 30/45] Docs/fix attributes (#342) * fix attributes * add changelog fragment * fix changelog secton * Apply suggestions from code review Co-authored-by: Felix Fontein Co-authored-by: Felix Fontein --- changelogs/fragments/325-fix attributes.yml | 3 +++ plugins/doc_fragments/attributes.py | 7 ++++++- plugins/modules/vault_kv1_get.py | 1 + plugins/modules/vault_kv2_delete.py | 5 +++-- plugins/modules/vault_kv2_get.py | 1 + plugins/modules/vault_login.py | 7 ++++--- plugins/modules/vault_pki_generate_certificate.py | 4 +++- plugins/modules/vault_read.py | 1 + plugins/modules/vault_token_create.py | 5 +++-- plugins/modules/vault_write.py | 4 +++- 10 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 changelogs/fragments/325-fix attributes.yml diff --git a/changelogs/fragments/325-fix attributes.yml b/changelogs/fragments/325-fix attributes.yml new file mode 100644 index 000000000..743034408 --- /dev/null +++ b/changelogs/fragments/325-fix attributes.yml @@ -0,0 +1,3 @@ +--- +trivial: + - the attribute documentation has been updated to correct the ``description`` and ``details`` fields (https://github.com/ansible-collections/community.hashi_vault/issues/325). diff --git a/plugins/doc_fragments/attributes.py b/plugins/doc_fragments/attributes.py index 5ac6a5a30..7536fa794 100644 --- a/plugins/doc_fragments/attributes.py +++ b/plugins/doc_fragments/attributes.py @@ -12,6 +12,9 @@ class ModuleDocFragment(object): DOCUMENTATION = r''' options: {} +attributes: + check_mode: + description: Can run in C(check_mode) and return changed status prediction without modifying target. ''' ACTION_GROUP = r''' @@ -24,10 +27,12 @@ class ModuleDocFragment(object): - community.hashi_vault.vault ''' + # Should be used together with the standard fragment CHECK_MODE_READ_ONLY = r''' options: {} attributes: check_mode: support: full - description: This module is "read only" and operates the same regardless of check mode. + details: + - This module is "read only" and operates the same regardless of check mode. ''' diff --git a/plugins/modules/vault_kv1_get.py b/plugins/modules/vault_kv1_get.py index 0921d6384..e21f4a813 100644 --- a/plugins/modules/vault_kv1_get.py +++ b/plugins/modules/vault_kv1_get.py @@ -26,6 +26,7 @@ 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 diff --git a/plugins/modules/vault_kv2_delete.py b/plugins/modules/vault_kv2_delete.py index 923eeb521..ac4d59257 100644 --- a/plugins/modules/vault_kv2_delete.py +++ b/plugins/modules/vault_kv2_delete.py @@ -24,15 +24,16 @@ attributes: check_mode: support: partial - description: + 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. + - Consider using M(community.hashi_vault.vault_kv2_get) to verify the existence of the secret first. seealso: - module: community.hashi_vault.vault_kv2_get - name: KV2 Secrets Engine description: Documentation for the Vault KV secrets engine, version 2. link: https://www.vaultproject.io/docs/secrets/kv/kv-v2 extends_documentation_fragment: + - community.hashi_vault.attributes - community.hashi_vault.attributes.action_group - community.hashi_vault.connection - community.hashi_vault.auth diff --git a/plugins/modules/vault_kv2_get.py b/plugins/modules/vault_kv2_get.py index 07e6d7d69..5ff5903fb 100644 --- a/plugins/modules/vault_kv2_get.py +++ b/plugins/modules/vault_kv2_get.py @@ -26,6 +26,7 @@ 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 diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index e396106db..c52e969e0 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -24,6 +24,7 @@ - ref: 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 @@ -40,10 +41,10 @@ attributes: check_mode: support: partial - description: - - "In check mode, this module will not perform a login, and will instead return a basic structure with an empty token. + 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=no) in order to have a valid token that can be used." + - 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 diff --git a/plugins/modules/vault_pki_generate_certificate.py b/plugins/modules/vault_pki_generate_certificate.py index 030ff2f87..66b9190b4 100644 --- a/plugins/modules/vault_pki_generate_certificate.py +++ b/plugins/modules/vault_pki_generate_certificate.py @@ -26,6 +26,7 @@ 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 @@ -33,7 +34,8 @@ attributes: check_mode: support: partial - description: In check mode, this module will not contact Vault and will return an empty C(data) field and C(changed) status. + 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: diff --git a/plugins/modules/vault_read.py b/plugins/modules/vault_read.py index fbb2f611b..6b6b209d5 100644 --- a/plugins/modules/vault_read.py +++ b/plugins/modules/vault_read.py @@ -24,6 +24,7 @@ - ref: 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 diff --git a/plugins/modules/vault_token_create.py b/plugins/modules/vault_token_create.py index fe24cfde4..c2d19422f 100644 --- a/plugins/modules/vault_token_create.py +++ b/plugins/modules/vault_token_create.py @@ -27,6 +27,7 @@ - ref: 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 @@ -39,10 +40,10 @@ attributes: check_mode: support: partial - description: + 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=no) in order to have a valid token that can be used. + - 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: {} """ diff --git a/plugins/modules/vault_write.py b/plugins/modules/vault_write.py index a28c5e54d..35c7fcb60 100644 --- a/plugins/modules/vault_write.py +++ b/plugins/modules/vault_write.py @@ -26,7 +26,8 @@ attributes: check_mode: support: partial - description: In check mode, an empty response will be returned and the write will not be performed. + 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 description: The official documentation for the C(community.hashi_vault.vault_write) lookup plugin. @@ -34,6 +35,7 @@ - ref: 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 From 1f680531cc22106e52350379fc12cb28e373bfea Mon Sep 17 00:00:00 2001 From: Tom Kivlin <52716470+tomkivlin@users.noreply.github.com> Date: Wed, 18 Jan 2023 22:10:24 +0000 Subject: [PATCH 31/45] Add plugins to list objects in Vault (#343) * read -> list * lookup and module for vault_list - initial tests * unit tests for list lookup/module * copyright - not sure if done correctly * add new plugins to codecov.yml * update documentation block for both plugins * Apply suggestions from @briantist code review Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> * add vault_list to meta/runtime.yml * add extra examples as per suggestion * new fixtures for unit tests * dedup unit test for fixtures * update module units with new fixtures * more list lookup examples and formatting * update secret path in list module examples * fix policies to test inexistant path response * fix inexistant path integration tests * missed variable substitution * update paths and add comments explaining * correct the path for lookup plugin * Update tests/integration/targets/setup_vault_configure/vars/main.yml * add further tests and comments * Apply suggestions from code review Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> --- codecov.yml | 8 + meta/runtime.yml | 1 + plugins/lookup/vault_list.py | 183 ++++++++++++++++++ plugins/modules/vault_list.py | 134 +++++++++++++ .../targets/lookup_vault_list/aliases | 1 + .../targets/lookup_vault_list/meta/main.yml | 4 + .../tasks/lookup_vault_list_setup.yml | 9 + .../tasks/lookup_vault_list_test.yml | 137 +++++++++++++ .../targets/lookup_vault_list/tasks/main.yml | 3 + .../targets/module_vault_list/aliases | 1 + .../targets/module_vault_list/meta/main.yml | 4 + .../targets/module_vault_list/tasks/main.yml | 3 + .../tasks/module_vault_list_setup.yml | 9 + .../tasks/module_vault_list_test.yml | 100 ++++++++++ .../setup_vault_configure/tasks/configure.yml | 16 ++ .../setup_vault_configure/vars/main.yml | 25 +++ tests/unit/fixtures/kv2_list_response.json | 15 ++ tests/unit/fixtures/policy_list_response.json | 15 ++ .../unit/fixtures/userpass_list_response.json | 15 ++ tests/unit/plugins/lookup/test_vault_list.py | 94 +++++++++ tests/unit/plugins/modules/test_vault_list.py | 164 ++++++++++++++++ 21 files changed, 941 insertions(+) create mode 100644 plugins/lookup/vault_list.py create mode 100644 plugins/modules/vault_list.py create mode 100644 tests/integration/targets/lookup_vault_list/aliases create mode 100644 tests/integration/targets/lookup_vault_list/meta/main.yml create mode 100644 tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_setup.yml create mode 100644 tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_test.yml create mode 100644 tests/integration/targets/lookup_vault_list/tasks/main.yml create mode 100644 tests/integration/targets/module_vault_list/aliases create mode 100644 tests/integration/targets/module_vault_list/meta/main.yml create mode 100644 tests/integration/targets/module_vault_list/tasks/main.yml create mode 100644 tests/integration/targets/module_vault_list/tasks/module_vault_list_setup.yml create mode 100644 tests/integration/targets/module_vault_list/tasks/module_vault_list_test.yml create mode 100644 tests/unit/fixtures/kv2_list_response.json create mode 100644 tests/unit/fixtures/policy_list_response.json create mode 100644 tests/unit/fixtures/userpass_list_response.json create mode 100644 tests/unit/plugins/lookup/test_vault_list.py create mode 100644 tests/unit/plugins/modules/test_vault_list.py diff --git a/codecov.yml b/codecov.yml index 8dafd8ba1..03b0eec8b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -20,6 +20,10 @@ flags: paths: - plugins/modules/vault_kv2_get.py + target_module_vault_list: + paths: + - plugins/modules/vault_list.py + target_module_vault_login: paths: - plugins/modules/vault_login.py @@ -44,6 +48,10 @@ flags: paths: - plugins/lookup/vault_kv2_get.py + target_lookup_vault_list: + paths: + - plugins/lookup/vault_list.py + target_lookup_vault_login: paths: - plugins/lookup/vault_login.py diff --git a/meta/runtime.yml b/meta/runtime.yml index fb5b0b8fb..4a4663dda 100644 --- a/meta/runtime.yml +++ b/meta/runtime.yml @@ -6,6 +6,7 @@ action_groups: - vault_kv1_get - vault_kv2_delete - vault_kv2_get + - vault_list - vault_login - vault_pki_generate_certificate - vault_read diff --git a/plugins/lookup/vault_list.py b/plugins/lookup/vault_list.py new file mode 100644 index 000000000..56521c792 --- /dev/null +++ b/plugins/lookup/vault_list.py @@ -0,0 +1,183 @@ +# (c) 2023, Tom Kivlin (@tomkivlin) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + +DOCUMENTATION = """ + name: vault_list + version_added: 4.1.0 + author: + - Tom Kivlin (@tomkivlin) + short_description: Perform a list operation against HashiCorp Vault + requirements: + - C(hvac) (L(Python library,https://hvac.readthedocs.io/en/stable/overview.html)) + - For detailed requirements, see R(the collection requirements page,ansible_collections.community.hashi_vault.docsite.user_guide.requirements). + description: + - Performs a generic list operation against a given path in HashiCorp Vault. + seealso: + - module: community.hashi_vault.vault_list + extends_documentation_fragment: + - community.hashi_vault.connection + - community.hashi_vault.connection.plugins + - community.hashi_vault.auth + - community.hashi_vault.auth.plugins + options: + _terms: + description: Vault path(s) to be listed. + type: str + required: true +""" + +EXAMPLES = """ +- name: List all secrets at a path + ansible.builtin.debug: + msg: "{{ lookup('community.hashi_vault.vault_list', 'secret/metadata', url='https://vault:8201') }}" + # For kv2, the path needs to follow the pattern 'mount_point/metadata' or 'mount_point/metadata/path' to list all secrets in that path + +- name: List access policies + ansible.builtin.debug: + msg: "{{ lookup('community.hashi_vault.vault_list', 'sys/policies/acl', url='https://vault:8201') }}" + +- name: Perform multiple list operations with a single Vault login + vars: + paths: + - secret/metadata + - sys/policies/acl + ansible.builtin.debug: + msg: "{{ lookup('community.hashi_vault.vault_list', *paths, auth_method='userpass', username=user, password=pwd) }}" + +- name: Perform multiple list operations with a single Vault login in a loop + vars: + paths: + - secret/metadata + - sys/policies/acl + ansible.builtin.debug: + msg: '{{ item }}' + loop: "{{ query('community.hashi_vault.vault_list', *paths, auth_method='userpass', username=user, password=pwd) }}" + +- name: Perform list operations with a single Vault login in a loop (via with_) + vars: + ansible_hashi_vault_auth_method: userpass + ansible_hashi_vault_username: '{{ user }}' + ansible_hashi_vault_password: '{{ pwd }}' + ansible.builtin.debug: + msg: '{{ item }}' + with_community.hashi_vault.vault_list: + - secret/metadata + - sys/policies/acl + +- name: Create fact consisting of list of dictionaries each with secret name (e.g. username) and value of a key (e.g. 'password') within that secret + ansible.builtin.set_fact: + credentials: >- + {{ + credentials + | default([]) + [ + { + 'username': item, + 'password': lookup('community.hashi_vault.vault_kv2_get', item, engine_mount_point='vpn-users').secret.password + } + ] + }} + loop: "{{ query('community.hashi_vault.vault_list', 'vpn-users/metadata')[0].data['keys'] }}" + no_log: true + +- ansible.builtin.debug: + msg: "{{ credentials }}" + +- name: Create the same as above without looping, and only 2 logins + vars: + secret_names: >- + {{ + query('community.hashi_vault.vault_list', 'vpn-users/metadata') + | map(attribute='data') + | map(attribute='keys') + | flatten + }} + secret_values: >- + {{ + lookup('community.hashi_vault.vault_kv2_get', *secret_names, engine_mount_point='vpn-users') + | map(attribute='secret') + | map(attribute='password') + | flatten + }} + credentials_dict: "{{ dict(secret_names | zip(secret_values)) }}" + ansible.builtin.set_fact: + credentials_dict: "{{ credentials_dict }}" + credentials_list: "{{ credentials_dict | dict2items(key_name='username', value_name='password') }}" + no_log: true + +- ansible.builtin.debug: + msg: + - "Dictionary: {{ credentials_dict }}" + - "List: {{ credentials_list }}" + +- name: List all userpass users and output the token policies for each user + ansible.builtin.debug: + msg: "{{ lookup('community.hashi_vault.vault_read', 'auth/userpass/users/' + item).data.token_policies }}" + loop: "{{ query('community.hashi_vault.vault_list', 'auth/userpass/users')[0].data['keys'] }}" +""" + +RETURN = """ +_raw: + description: + - The raw result of the read against the given path. + type: list + elements: dict +""" + +from ansible.errors import AnsibleError +from ansible.utils.display import Display + +from ansible.module_utils.six import raise_from + +from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase +from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError + +display = Display() + +try: + import hvac +except ImportError as imp_exc: + HVAC_IMPORT_ERROR = imp_exc +else: + HVAC_IMPORT_ERROR = None + + +class LookupModule(HashiVaultLookupBase): + def run(self, terms, variables=None, **kwargs): + if HVAC_IMPORT_ERROR: + raise_from( + AnsibleError("This plugin requires the 'hvac' Python library"), + HVAC_IMPORT_ERROR + ) + + ret = [] + + self.set_options(direct=kwargs, var_options=variables) + # TODO: remove process_deprecations() if backported fix is available (see method definition) + self.process_deprecations() + + self.connection_options.process_connection_options() + client_args = self.connection_options.get_hvac_connection_options() + client = self.helper.get_vault_client(**client_args) + + try: + self.authenticator.validate() + self.authenticator.authenticate(client) + except (NotImplementedError, HashiVaultValueError) as e: + raise AnsibleError(e) + + for term in terms: + try: + data = client.list(term) + except hvac.exceptions.Forbidden: + raise AnsibleError("Forbidden: Permission Denied to path '%s'." % term) + + if data is None: + raise AnsibleError("The path '%s' doesn't seem to exist." % term) + + ret.append(data) + + return ret diff --git a/plugins/modules/vault_list.py b/plugins/modules/vault_list.py new file mode 100644 index 000000000..a0823dc2d --- /dev/null +++ b/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 + 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/tests/integration/targets/lookup_vault_list/aliases b/tests/integration/targets/lookup_vault_list/aliases new file mode 100644 index 000000000..1bb8bf6d7 --- /dev/null +++ b/tests/integration/targets/lookup_vault_list/aliases @@ -0,0 +1 @@ +# empty diff --git a/tests/integration/targets/lookup_vault_list/meta/main.yml b/tests/integration/targets/lookup_vault_list/meta/main.yml new file mode 100644 index 000000000..d3acb69e9 --- /dev/null +++ b/tests/integration/targets/lookup_vault_list/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - setup_vault_test_plugins + - setup_vault_configure diff --git a/tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_setup.yml b/tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_setup.yml new file mode 100644 index 000000000..193d6fa5e --- /dev/null +++ b/tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_setup.yml @@ -0,0 +1,9 @@ +--- +- name: Configuration tasks + module_defaults: + vault_ci_token_create: '{{ vault_plugins_module_defaults_common }}' + block: + - name: 'Create a test non-root token' + vault_ci_token_create: + policies: test-policy + register: user_token_cmd diff --git a/tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_test.yml b/tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_test.yml new file mode 100644 index 000000000..9c2190208 --- /dev/null +++ b/tests/integration/targets/lookup_vault_list/tasks/lookup_vault_list_test.yml @@ -0,0 +1,137 @@ +--- +- name: Var block + vars: + ansible_hashi_vault_token_validate: true + user_token: '{{ user_token_cmd.result.auth.client_token }}' + kwargs: + url: '{{ vault_test_server_http }}' + auth_method: token + token: '{{ user_token }}' + block: + - name: 'Check kv2 secret list' + vars: + kv2_secret2: "{{ lookup('community.hashi_vault.vault_list', vault_kv2_api_list_path, **kwargs) }}" + assert: + that: + - "'data' in kv2_secret2" + - "'keys' in kv2_secret2['data']" + fail_msg: 'Return value did not contain expected fields.' + + - name: 'Check kv2 mount point list' + vars: + kv2_mount_point: "{{ lookup('community.hashi_vault.vault_list', vault_kv2_api_list_mount_point, **kwargs) }}" + assert: + that: + - "'data' in kv2_mount_point" + - "'keys' in kv2_mount_point['data']" + fail_msg: 'Return value did not contain expected fields.' + + - name: "Check multiple path list as array" + vars: + paths: + - '{{ vault_kv2_api_list_path }}' + - '{{ vault_policy_api_list_path }}' + list_results: "{{ lookup('community.hashi_vault.vault_list', *paths, **kwargs) }}" + assert: + that: + - list_results | type_debug == 'list' + - item | type_debug == 'dict' + - "'data' in item" + - "'keys' in item['data']" + - item['data']['keys'] | type_debug == 'list' + fail_msg: 'Return value was not correct type or items do not match.' + loop: '{{ list_results }}' + + + ### failure tests + + - name: 'Failure expected when erroneous credentials are used' + vars: + secret_wrong_cred: "{{ lookup('community.hashi_vault.vault_list', vault_kv2_api_list_path, token='wrong_token', url=kwargs.url) }}" + debug: + msg: 'Failure is expected ({{ secret_wrong_cred }})' + register: test_wrong_cred + ignore_errors: true + + - assert: + that: + - test_wrong_cred is failed + - test_wrong_cred.msg is search('Invalid Vault Token') + fail_msg: "Expected failure but got success or wrong failure message." + + - name: 'Failure expected when unauthorized path is provided' + vars: + secret_unauthorized: "{{ lookup('community.hashi_vault.vault_list', unauthorized_vault_kv2_mount_point, **kwargs) }}" + debug: + msg: 'Failure is expected ({{ secret_unauthorized }})' + register: test_unauthorized + ignore_errors: true + + - assert: + that: + - test_unauthorized is failed + - test_unauthorized.msg is search('Permission Denied') + fail_msg: "Expected failure but got success or wrong failure message." + + # When an inexistent mount point is listed, the API returns a 403 error, not 404. + - name: 'Failure expected when inexistent mount point is listed' + vars: + mount_point_inexistent: "{{ lookup('community.hashi_vault.vault_list', vault_kv2_api_list_inexistent_mount_point, **kwargs) }}" + debug: + msg: 'Failure is expected ({{ mount_point_inexistent }})' + register: test_inexistent_mount_point + ignore_errors: true + + - assert: + that: + - test_inexistent_mount_point is failed + - test_inexistent_mount_point.msg is search("Permission Denied") + fail_msg: "Expected failure but got success or wrong failure message." + + - name: 'Failure expected when inexistent path is listed' + vars: + path_inexistent: "{{ lookup('community.hashi_vault.vault_list', vault_kv2_api_list_inexistent_path, **kwargs) }}" + debug: + msg: 'Failure is expected ({{ path_inexistent }})' + register: test_inexistent + ignore_errors: true + + - assert: + that: + - test_inexistent is failed + - test_inexistent.msg is search("doesn't seem to exist") + fail_msg: "Expected failure but got success or wrong failure message." + + # If an inexistent path is included in a policy statement that denies access, the list API returns a 403 error. + - name: 'Failure expected when inexistent path is listed but is explicitly mentioned in a policy statement' + vars: + path_inexistent_unauthorized: "{{ lookup('community.hashi_vault.vault_list', vault_kv2_api_list_inexistent_unauthorized_path, **kwargs) }}" + debug: + msg: 'Failure is expected ({{ path_inexistent_unauthorized }})' + register: test_inexistent_unauthorized + ignore_errors: true + + - assert: + that: + - test_inexistent_unauthorized is failed + - test_inexistent_unauthorized.msg is search("Permission Denied") + fail_msg: "Expected failure but got success or wrong failure message." + + # do this last so our set_fact doesn't affect any other tests + - name: Set the vars that will configure the lookup settings we can't set via with_ + set_fact: + ansible_hashi_vault_url: '{{ kwargs.url }}' + ansible_hashi_vault_token: '{{ kwargs.token }}' + ansible_hashi_vault_auth_method: '{{ kwargs.auth_method }}' + + - name: Check multiple path list via with_ + assert: + that: + - item | type_debug == 'dict' + - "'data' in item" + - "'keys' in item['data']" + - item['data']['keys'] | type_debug == 'list' + fail_msg: 'Return value was not correct type or items do not match.' + with_community.hashi_vault.vault_list: + - '{{ vault_kv2_api_list_path }}' + - '{{ vault_policy_api_list_path }}' diff --git a/tests/integration/targets/lookup_vault_list/tasks/main.yml b/tests/integration/targets/lookup_vault_list/tasks/main.yml new file mode 100644 index 000000000..e0caae6bd --- /dev/null +++ b/tests/integration/targets/lookup_vault_list/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: lookup_vault_list_setup.yml +- import_tasks: lookup_vault_list_test.yml diff --git a/tests/integration/targets/module_vault_list/aliases b/tests/integration/targets/module_vault_list/aliases new file mode 100644 index 000000000..7636a9a65 --- /dev/null +++ b/tests/integration/targets/module_vault_list/aliases @@ -0,0 +1 @@ +context/target diff --git a/tests/integration/targets/module_vault_list/meta/main.yml b/tests/integration/targets/module_vault_list/meta/main.yml new file mode 100644 index 000000000..d3acb69e9 --- /dev/null +++ b/tests/integration/targets/module_vault_list/meta/main.yml @@ -0,0 +1,4 @@ +--- +dependencies: + - setup_vault_test_plugins + - setup_vault_configure diff --git a/tests/integration/targets/module_vault_list/tasks/main.yml b/tests/integration/targets/module_vault_list/tasks/main.yml new file mode 100644 index 000000000..cd7bd5d5d --- /dev/null +++ b/tests/integration/targets/module_vault_list/tasks/main.yml @@ -0,0 +1,3 @@ +--- +- import_tasks: module_vault_list_setup.yml +- import_tasks: module_vault_list_test.yml diff --git a/tests/integration/targets/module_vault_list/tasks/module_vault_list_setup.yml b/tests/integration/targets/module_vault_list/tasks/module_vault_list_setup.yml new file mode 100644 index 000000000..193d6fa5e --- /dev/null +++ b/tests/integration/targets/module_vault_list/tasks/module_vault_list_setup.yml @@ -0,0 +1,9 @@ +--- +- name: Configuration tasks + module_defaults: + vault_ci_token_create: '{{ vault_plugins_module_defaults_common }}' + block: + - name: 'Create a test non-root token' + vault_ci_token_create: + policies: test-policy + register: user_token_cmd diff --git a/tests/integration/targets/module_vault_list/tasks/module_vault_list_test.yml b/tests/integration/targets/module_vault_list/tasks/module_vault_list_test.yml new file mode 100644 index 000000000..64f40d845 --- /dev/null +++ b/tests/integration/targets/module_vault_list/tasks/module_vault_list_test.yml @@ -0,0 +1,100 @@ +--- +- name: Var block + vars: + user_token: '{{ user_token_cmd.result.auth.client_token }}' + module_defaults: + community.hashi_vault.vault_list: + url: '{{ vault_test_server_http }}' + auth_method: token + token: '{{ user_token }}' + token_validate: true + timeout: 5 + block: + - name: 'Check kv2 secret list' + register: kv2_path + community.hashi_vault.vault_list: + path: "{{ vault_kv2_api_list_path }}" + + - assert: + that: + - "'data' in kv2_path" + - "'data' in kv2_path['data']" + - "'keys' in kv2_path['data']['data']" + fail_msg: 'Return value did not contain expected fields.' + + - name: 'Check kv2 mount point list' + register: kv2_mount_point + community.hashi_vault.vault_list: + path: "{{ vault_kv2_api_list_mount_point }}" + + - assert: + that: + - "'data' in kv2_mount_point" + - "'data' in kv2_mount_point['data']" + - "'keys' in kv2_mount_point['data']['data']" + fail_msg: 'Return value did not contain expected fields.' + + ### failure tests + + - name: 'Failure expected when erroneous credentials are used' + register: test_wrong_cred + community.hashi_vault.vault_list: + path: "{{ vault_kv2_api_list_path }}" + token: wrong_token + ignore_errors: true + + - assert: + that: + - test_wrong_cred is failed + - test_wrong_cred.msg is search('Invalid Vault Token') + fail_msg: "Expected failure but got success or wrong failure message." + + - name: 'Failure expected when unauthorized path is listed' + register: test_unauthorized + community.hashi_vault.vault_list: + path: "{{ unauthorized_vault_kv2_mount_point }}" + ignore_errors: true + + - assert: + that: + - test_unauthorized is failed + - test_unauthorized.msg is search('Permission Denied') + fail_msg: "Expected failure but got success or wrong failure message." + + # When an inexistent mount point is listed, the API returns a 403 error, not 404. + - name: 'Failure expected when inexistent mount point is listed' + register: test_inexistent_mount_point + community.hashi_vault.vault_list: + path: "{{ vault_kv2_api_list_inexistent_mount_point }}" + ignore_errors: true + + - assert: + that: + - test_inexistent_mount_point is failed + - test_inexistent_mount_point.msg is search("Permission Denied") + fail_msg: "Expected failure but got success or wrong failure message." + + - name: 'Failure expected when inexistent path is listed' + register: test_inexistent + community.hashi_vault.vault_list: + path: "{{ vault_kv2_api_list_inexistent_path }}" + ignore_errors: true + + - assert: + that: + - test_inexistent is failed + - test_inexistent.msg is search("doesn't seem to exist") + fail_msg: "Expected failure but got success or wrong failure message." + + # If an inexistent path is included in a policy statement that denies access, the list API returns a 403 error. + - name: 'Failure expected when inexistent path is listed but is explicitly mentioned in a policy statement' + register: test_inexistent_unauthorized + community.hashi_vault.vault_list: + path: "{{ vault_kv2_api_list_inexistent_unauthorized_path }}" + ignore_errors: true + + - assert: + that: + - test_inexistent_unauthorized is failed + - test_inexistent_unauthorized.msg is search("Permission Denied") + fail_msg: "Expected failure but got success or wrong failure message." diff --git a/tests/integration/targets/setup_vault_configure/tasks/configure.yml b/tests/integration/targets/setup_vault_configure/tasks/configure.yml index 3d7bc5f61..5fdafe937 100644 --- a/tests/integration/targets/setup_vault_configure/tasks/configure.yml +++ b/tests/integration/targets/setup_vault_configure/tasks/configure.yml @@ -13,6 +13,13 @@ options: version: 2 +- name: 'Create KV v2 secrets engine to test unauthorized access' + vault_ci_enable_engine: + backend_type: kv + path: '{{ unauthorized_vault_kv2_mount_point }}' + options: + version: 2 + - name: Create a test policy vault_ci_policy_put: name: test-policy @@ -56,6 +63,15 @@ secret: value: 'foo{{ item }}' +- name: 'Create KV v2 secrets in unauthorized path' + loop: [1, 2, 3, 4, 5] + vault_ci_kv_put: + path: "{{ vault_kv2_path }}/secret{{ item }}" + version: 2 + mount_point: '{{ unauthorized_vault_kv2_mount_point }}' + secret: + value: 'foo{{ item }}' + - name: 'Update KV v2 secret4 with new value to create version' vault_ci_kv_put: path: "{{ vault_kv2_path }}/secret4" diff --git a/tests/integration/targets/setup_vault_configure/vars/main.yml b/tests/integration/targets/setup_vault_configure/vars/main.yml index 2d76ea951..900185ed8 100644 --- a/tests/integration/targets/setup_vault_configure/vars/main.yml +++ b/tests/integration/targets/setup_vault_configure/vars/main.yml @@ -8,6 +8,7 @@ vault_kv1_path: testproject vault_kv1_api_path: '{{ vault_kv1_mount_point }}/{{ vault_kv1_path }}' vault_kv2_mount_point: kv2 +unauthorized_vault_kv2_mount_point: kv2_noauth vault_kv2_path: testproject vault_kv2_multi_path: testmulti @@ -18,6 +19,15 @@ vault_kv2_multi_api_path: '{{ vault_kv2_mount_point }}/data/{{ vault_kv2_multi_p vault_kv2_versioned_api_path: '{{ vault_kv2_mount_point }}/data/{{ vault_kv2_versioned_path }}' vault_kv2_delete_api_path: '{{ vault_kv2_mount_point }}/delete/{{ vault_kv2_versioned_path }}' vault_kv2_metadata_api_path: '{{ vault_kv2_mount_point }}/metadata/{{ vault_kv2_versioned_path }}' +vault_kv2_api_list_mount_point: '{{ vault_kv2_mount_point }}/metadata' +vault_kv2_api_list_path: '{{ vault_kv2_mount_point }}/metadata/{{ vault_kv2_path }}' + +vault_policy_api_list_path: 'sys/policies/acl' + +vault_kv2_api_list_inexistent_path: '{{ vault_kv2_mount_point }}/metadata/__inexistent' +vault_kv2_api_list_inexistent_mount_point: '{{ vault_kv2_mount_point }}__inexistent/metadata' +vault_kv2_api_list_inexistent_unauthorized_path: '{{ vault_kv2_mount_point }}/metadata/__inexistent_no_auth' +vault_kv2_api_list_unauthorized_path: '{{ unauthorized_vault_kv2_mount_point }}/metadata' vault_base_policy: | path "{{ vault_kv1_api_path }}/secret1" { @@ -69,6 +79,21 @@ vault_base_policy: | path "{{ vault_kv2_metadata_api_path }}/secret6" { capabilities = ["read"] } + path "{{ vault_kv2_api_list_mount_point }}/*" { + capabilities = ["list"] + } + path "{{ vault_kv2_api_list_path }}" { + capabilities = ["list"] + } + path "{{ vault_policy_api_list_path }}" { + capabilities = ["list"] + } + path "{{ vault_kv2_api_list_inexistent_unauthorized_path }}" { + capabilities = ["deny"] + } + path "{{ vault_kv2_api_list_unauthorized_path }}" { + capabilities = ["deny"] + } vault_token_creator_policy: | path "auth/token/create" { diff --git a/tests/unit/fixtures/kv2_list_response.json b/tests/unit/fixtures/kv2_list_response.json new file mode 100644 index 000000000..2fe833b51 --- /dev/null +++ b/tests/unit/fixtures/kv2_list_response.json @@ -0,0 +1,15 @@ +{ + "auth": null, + "data": { + "keys": [ + "Secret1", + "Secret2" + ] + }, + "lease_duration": 0, + "lease_id": "", + "renewable": false, + "request_id": "02e4b52a-23b1-9a1c-cf2b-3799edb17fed", + "warnings": null, + "wrap_info": null +} diff --git a/tests/unit/fixtures/policy_list_response.json b/tests/unit/fixtures/policy_list_response.json new file mode 100644 index 000000000..5a7dfdb8d --- /dev/null +++ b/tests/unit/fixtures/policy_list_response.json @@ -0,0 +1,15 @@ +{ + "auth": null, + "data": { + "keys": [ + "Policy1", + "Policy2" + ] + }, + "lease_duration": 0, + "lease_id": "", + "renewable": false, + "request_id": "96f2857e-5e33-1957-ea7e-be58f483faa3", + "warnings": null, + "wrap_info": null +} diff --git a/tests/unit/fixtures/userpass_list_response.json b/tests/unit/fixtures/userpass_list_response.json new file mode 100644 index 000000000..84cabf3bb --- /dev/null +++ b/tests/unit/fixtures/userpass_list_response.json @@ -0,0 +1,15 @@ +{ + "auth": null, + "data": { + "keys": [ + "User1", + "User2" + ] + }, + "lease_duration": 0, + "lease_id": "", + "renewable": false, + "request_id": "8b18a5ca-9baf-eb7c-18a6-11be81ed95a6", + "warnings": null, + "wrap_info": null +} diff --git a/tests/unit/plugins/lookup/test_vault_list.py b/tests/unit/plugins/lookup/test_vault_list.py new file mode 100644 index 000000000..dddb4a381 --- /dev/null +++ b/tests/unit/plugins/lookup/test_vault_list.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +# Copyright (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 + +import pytest + +from ansible.plugins.loader import lookup_loader +from ansible.errors import AnsibleError + +from ...compat import mock + +from .....plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError + +from .....plugins.lookup import vault_list + + +hvac = pytest.importorskip('hvac') + + +pytestmark = pytest.mark.usefixtures( + 'patch_authenticator', + 'patch_get_vault_client', +) + + +@pytest.fixture +def vault_list_lookup(): + return lookup_loader.get('community.hashi_vault.vault_list') + + +LIST_FIXTURES = [ + 'kv2_list_response.json', + 'policy_list_response.json', + 'userpass_list_response.json', +] + + +@pytest.fixture(params=LIST_FIXTURES) +def list_response(request, fixture_loader): + return fixture_loader(request.param) + + +class TestVaultListLookup(object): + + def test_vault_list_is_lookup_base(self, vault_list_lookup): + assert issubclass(type(vault_list_lookup), HashiVaultLookupBase) + + def test_vault_list_no_hvac(self, vault_list_lookup, minimal_vars): + with mock.patch.object(vault_list, 'HVAC_IMPORT_ERROR', new=ImportError()): + with pytest.raises(AnsibleError, match=r"This plugin requires the 'hvac' Python library"): + vault_list_lookup.run(terms='fake', variables=minimal_vars) + + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_list_authentication_error(self, vault_list_lookup, minimal_vars, authenticator, exc): + authenticator.authenticate.side_effect = exc + + with pytest.raises(AnsibleError, match=r'throwaway msg'): + vault_list_lookup.run(terms='fake', variables=minimal_vars) + + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_list_auth_validation_error(self, vault_list_lookup, minimal_vars, authenticator, exc): + authenticator.validate.side_effect = exc + + with pytest.raises(AnsibleError, match=r'throwaway msg'): + vault_list_lookup.run(terms='fake', variables=minimal_vars) + + @pytest.mark.parametrize('paths', [['fake1'], ['fake2', 'fake3']]) + def test_vault_list_return_data(self, vault_list_lookup, minimal_vars, list_response, vault_client, paths): + client = vault_client + + expected_calls = [mock.call(p) for p in paths] + + def _fake_list_operation(path): + r = list_response.copy() + r.update({'_path': path}) + return r + + client.list = mock.Mock(wraps=_fake_list_operation) + + response = vault_list_lookup.run(terms=paths, variables=minimal_vars) + + client.list.assert_has_calls(expected_calls) + + assert len(response) == len(paths), "%i paths processed but got %i responses" % (len(paths), len(response)) + + for p in paths: + r = response.pop(0) + ins_p = r.pop('_path') + assert p == ins_p, "expected '_path=%s' field was not found in response, got %r" % (p, ins_p) diff --git a/tests/unit/plugins/modules/test_vault_list.py b/tests/unit/plugins/modules/test_vault_list.py new file mode 100644 index 000000000..6891547be --- /dev/null +++ b/tests/unit/plugins/modules/test_vault_list.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- +# Copyright (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 + +import pytest +import re +import json + +from ansible.module_utils.basic import missing_required_lib + +from ...compat import mock +from .....plugins.modules import vault_list +from .....plugins.module_utils._hashi_vault_common import HashiVaultValueError + + +hvac = pytest.importorskip('hvac') + + +pytestmark = pytest.mark.usefixtures( + 'patch_ansible_module', + 'patch_authenticator', + 'patch_get_vault_client', +) + + +def _connection_options(): + return { + 'auth_method': 'token', + 'url': 'http://myvault', + 'token': 'beep-boop', + } + + +def _sample_options(): + return { + 'path': 'endpoint', + } + + +def _combined_options(**kwargs): + opt = _connection_options() + opt.update(_sample_options()) + opt.update(kwargs) + return opt + + +LIST_FIXTURES = [ + 'kv2_list_response.json', + 'policy_list_response.json', + 'userpass_list_response.json', +] + + +@pytest.fixture(params=LIST_FIXTURES) +def list_response(request, fixture_loader): + return fixture_loader(request.param) + + +class TestModuleVaultList(): + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_list_authentication_error(self, authenticator, exc, capfd): + authenticator.authenticate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_list.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg', "result: %r" % result + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + @pytest.mark.parametrize('exc', [HashiVaultValueError('throwaway msg'), NotImplementedError('throwaway msg')]) + def test_vault_list_auth_validation_error(self, authenticator, exc, capfd): + authenticator.validate.side_effect = exc + + with pytest.raises(SystemExit) as e: + vault_list.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == 'throwaway msg' + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + def test_vault_list_return_data(self, patch_ansible_module, list_response, vault_client, capfd): + client = vault_client + client.list.return_value = list_response.copy() + + with pytest.raises(SystemExit) as e: + vault_list.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code == 0, "result: %r" % (result,) + + client.list.assert_called_once_with(patch_ansible_module['path']) + + assert result['data'] == list_response, "module result did not match expected result:\nexpected: %r\ngot: %r" % (list_response, result) + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + def test_vault_list_no_data(self, patch_ansible_module, vault_client, capfd): + client = vault_client + client.list.return_value = None + + with pytest.raises(SystemExit) as e: + vault_list.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + + client.list.assert_called_once_with(patch_ansible_module['path']) + + match = re.search(r"The path '[^']+' doesn't seem to exist", result['msg']) + + assert match is not None, "Unexpected msg: %s" % result['msg'] + + @pytest.mark.parametrize('patch_ansible_module', [_combined_options()], indirect=True) + def test_vault_list_no_hvac(self, capfd): + with mock.patch.multiple(vault_list, HAS_HVAC=False, HVAC_IMPORT_ERROR=None, create=True): + with pytest.raises(SystemExit) as e: + vault_list.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + assert result['msg'] == missing_required_lib('hvac') + + @pytest.mark.parametrize( + 'exc', + [ + (hvac.exceptions.Forbidden, "", r"^Forbidden: Permission Denied to path '([^']+)'"), + ] + ) + @pytest.mark.parametrize('patch_ansible_module', [[_combined_options(), 'path']], indirect=True) + @pytest.mark.parametrize('opt_path', ['path/1', 'second/path']) + def test_vault_list_vault_exception(self, vault_client, exc, opt_path, capfd): + + client = vault_client + client.list.side_effect = exc[0](exc[1]) + + with pytest.raises(SystemExit) as e: + vault_list.main() + + out, err = capfd.readouterr() + result = json.loads(out) + + assert e.value.code != 0, "result: %r" % (result,) + match = re.search(exc[2], result['msg']) + assert match is not None, "result: %r\ndid not match: %s" % (result, exc[2]) + + assert opt_path == match.group(1) From fce3ae8862a3716765c3dee4c79afd54af1bfb77 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Wed, 18 Jan 2023 17:15:42 -0500 Subject: [PATCH 32/45] add deprecation notices (#341) * add deprecation notices * fix formatting * update wording --- changelogs/fragments/324-deprecate-hvac.yml | 3 +++ changelogs/fragments/340-deprecate-core-211-212.yml | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 changelogs/fragments/324-deprecate-hvac.yml create mode 100644 changelogs/fragments/340-deprecate-core-211-212.yml diff --git a/changelogs/fragments/324-deprecate-hvac.yml b/changelogs/fragments/324-deprecate-hvac.yml new file mode 100644 index 000000000..52242aac9 --- /dev/null +++ b/changelogs/fragments/324-deprecate-hvac.yml @@ -0,0 +1,3 @@ +--- +deprecated_features: + - hvac - the minimum version of ``hvac`` to be supported in collection version ``5.0.0`` will be at least ``1.0.2``; this minimum may be raised before ``5.0.0`` is released, so please subscribe to the linked issue and look out for new notices in the changelog (https://github.com/ansible-collections/community.hashi_vault/issues/324). diff --git a/changelogs/fragments/340-deprecate-core-211-212.yml b/changelogs/fragments/340-deprecate-core-211-212.yml new file mode 100644 index 000000000..b85f7cded --- /dev/null +++ b/changelogs/fragments/340-deprecate-core-211-212.yml @@ -0,0 +1,3 @@ +--- +deprecated_features: + - ansible-core - support for ``ansible-core`` versions ``2.11`` and ``2.12`` will be dropped in collection version ``5.0.0``, making ``2.13`` the minimum supported version of ``ansible-core`` (https://github.com/ansible-collections/community.hashi_vault/issues/340). From c2167860347307dee54fa33ae085a34861ec5bc3 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Wed, 18 Jan 2023 21:35:45 -0500 Subject: [PATCH 33/45] Release/v4.1.0 (#345) * add release summary * set version * release v4.1.0 --- CHANGELOG.rst | 28 +++++++++++++++++ changelogs/changelog.yaml | 30 +++++++++++++++++++ changelogs/fragments/324-deprecate-hvac.yml | 3 -- changelogs/fragments/325-fix attributes.yml | 3 -- .../fragments/340-deprecate-core-211-212.yml | 3 -- galaxy.yml | 2 +- 6 files changed, 59 insertions(+), 10 deletions(-) delete mode 100644 changelogs/fragments/324-deprecate-hvac.yml delete mode 100644 changelogs/fragments/325-fix attributes.yml delete mode 100644 changelogs/fragments/340-deprecate-core-211-212.yml diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 48125bde6..f9ff0e57c 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -5,6 +5,34 @@ community.hashi_vault Release Notes .. contents:: Topics +v4.1.0 +====== + +Release Summary +--------------- + +This release brings new generic ``vault_list`` plugins from a new contributor! +There are also some deprecation notices for the next major version, and some updates to documentation attributes. + +Deprecated Features +------------------- + +- ansible-core - support for ``ansible-core`` versions ``2.11`` and ``2.12`` will be dropped in collection version ``5.0.0``, making ``2.13`` the minimum supported version of ``ansible-core`` (https://github.com/ansible-collections/community.hashi_vault/issues/340). +- hvac - the minimum version of ``hvac`` to be supported in collection version ``5.0.0`` will be at least ``1.0.2``; this minimum may be raised before ``5.0.0`` is released, so please subscribe to the linked issue and look out for new notices in the changelog (https://github.com/ansible-collections/community.hashi_vault/issues/324). + +New Plugins +----------- + +Lookup +~~~~~~ + +- vault_list - Perform a list operation against HashiCorp Vault + +New Modules +----------- + +- vault_list - Perform a list operation against HashiCorp Vault + v4.0.0 ====== diff --git a/changelogs/changelog.yaml b/changelogs/changelog.yaml index bbb05de95..2e44bb77c 100644 --- a/changelogs/changelog.yaml +++ b/changelogs/changelog.yaml @@ -589,3 +589,33 @@ releases: - 279-vault_kv2_get-lookup-mount-default.yml - 4.0.0.yml release_date: '2022-11-05' + 4.1.0: + changes: + deprecated_features: + - ansible-core - support for ``ansible-core`` versions ``2.11`` and ``2.12`` + will be dropped in collection version ``5.0.0``, making ``2.13`` the minimum + supported version of ``ansible-core`` (https://github.com/ansible-collections/community.hashi_vault/issues/340). + - hvac - the minimum version of ``hvac`` to be supported in collection version + ``5.0.0`` will be at least ``1.0.2``; this minimum may be raised before ``5.0.0`` + is released, so please subscribe to the linked issue and look out for new + notices in the changelog (https://github.com/ansible-collections/community.hashi_vault/issues/324). + release_summary: 'This release brings new generic ``vault_list`` plugins from + a new contributor! + + There are also some deprecation notices for the next major version, and some + updates to documentation attributes.' + fragments: + - 324-deprecate-hvac.yml + - 325-fix attributes.yml + - 340-deprecate-core-211-212.yml + - 4.1.0.yml + modules: + - description: Perform a list operation against HashiCorp Vault + name: vault_list + namespace: '' + plugins: + lookup: + - description: Perform a list operation against HashiCorp Vault + name: vault_list + namespace: null + release_date: '2023-01-18' diff --git a/changelogs/fragments/324-deprecate-hvac.yml b/changelogs/fragments/324-deprecate-hvac.yml deleted file mode 100644 index 52242aac9..000000000 --- a/changelogs/fragments/324-deprecate-hvac.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -deprecated_features: - - hvac - the minimum version of ``hvac`` to be supported in collection version ``5.0.0`` will be at least ``1.0.2``; this minimum may be raised before ``5.0.0`` is released, so please subscribe to the linked issue and look out for new notices in the changelog (https://github.com/ansible-collections/community.hashi_vault/issues/324). diff --git a/changelogs/fragments/325-fix attributes.yml b/changelogs/fragments/325-fix attributes.yml deleted file mode 100644 index 743034408..000000000 --- a/changelogs/fragments/325-fix attributes.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -trivial: - - the attribute documentation has been updated to correct the ``description`` and ``details`` fields (https://github.com/ansible-collections/community.hashi_vault/issues/325). diff --git a/changelogs/fragments/340-deprecate-core-211-212.yml b/changelogs/fragments/340-deprecate-core-211-212.yml deleted file mode 100644 index b85f7cded..000000000 --- a/changelogs/fragments/340-deprecate-core-211-212.yml +++ /dev/null @@ -1,3 +0,0 @@ ---- -deprecated_features: - - ansible-core - support for ``ansible-core`` versions ``2.11`` and ``2.12`` will be dropped in collection version ``5.0.0``, making ``2.13`` the minimum supported version of ``ansible-core`` (https://github.com/ansible-collections/community.hashi_vault/issues/340). diff --git a/galaxy.yml b/galaxy.yml index e913e02eb..7a274ad06 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 4.1.0-devel +version: 4.1.0 readme: README.md authors: - Julie Davila (@juliedavila) From 5d86075e3648ea458d7b1d492895de61858b0fa6 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Wed, 18 Jan 2023 21:39:23 -0500 Subject: [PATCH 34/45] v4.2.0 next expected version --- galaxy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/galaxy.yml b/galaxy.yml index 7a274ad06..e96d480ef 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -2,7 +2,7 @@ namespace: community name: hashi_vault -version: 4.1.0 +version: 4.2.0-devel readme: README.md authors: - Julie Davila (@juliedavila) From fa44d9cf300b2a5980a93f9cb93bbfd570efbd13 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Wed, 18 Jan 2023 21:49:31 -0500 Subject: [PATCH 35/45] set 100% patch coverage target --- codecov.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/codecov.yml b/codecov.yml index 03b0eec8b..1f0977656 100644 --- a/codecov.yml +++ b/codecov.yml @@ -7,6 +7,12 @@ ignore: fixes: - "ansible_collections/community/hashi_vault/::" +coverage: + status: + patch: + default: + target: 100% + flags: target_filter_vault_login_token: paths: From cd881ac3e9e52b260b09bb369548591c52522576 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sat, 18 Feb 2023 17:20:49 -0500 Subject: [PATCH 36/45] fix new sanity test requirements and CI docker version hang (#347) * remove unused import * disrespect retry-after header * show warning on retry * more import shenanigans * wheres my pep pep * sigh 2.11 --- .github/actions/docker-image-versions/versions.py | 11 +++++++++-- plugins/lookup/vault_login.py | 6 +++--- plugins/lookup/vault_token_create.py | 6 +++--- plugins/modules/vault_login.py | 6 +++--- tests/unit/compat/builtins.py | 2 +- tests/unit/compat/mock.py | 2 -- tests/unit/plugins/lookup/conftest.py | 2 -- .../module_utils/authentication/test_auth_none.py | 7 ++----- .../module_utils/authentication/test_auth_token.py | 8 +++----- .../authentication/test_hashi_vault_authenticator.py | 4 +--- .../plugins/module_utils/test_hashi_vault_helper.py | 5 ++--- 11 files changed, 27 insertions(+), 32 deletions(-) diff --git a/.github/actions/docker-image-versions/versions.py b/.github/actions/docker-image-versions/versions.py index 71ba9ce73..9d7fcea2d 100755 --- a/.github/actions/docker-image-versions/versions.py +++ b/.github/actions/docker-image-versions/versions.py @@ -17,13 +17,20 @@ import requests from urllib3.util.retry import Retry from requests.adapters import HTTPAdapter - +from warnings import warn from packaging import version TAG_URI = 'https://registry.hub.docker.com/v2/repositories/library/%s/tags?page_size=1024' +class WarningRetry(Retry): + def new(self, **kwargs): + if self.total > 0: + warn('Error on request. Retries remaining: %i' % (self.total,)) + return super().new(**kwargs) + + def main(argv): image = None include_prerelease = include_postrelease = False @@ -60,7 +67,7 @@ def main(argv): tag_url = TAG_URI % image sess = requests.Session() - retry = Retry(total=5, backoff_factor=0.2) + retry = WarningRetry(total=5, backoff_factor=0.2, respect_retry_after_header=False) adapter = HTTPAdapter(max_retries=retry) sess.mount('https://', adapter) diff --git a/plugins/lookup/vault_login.py b/plugins/lookup/vault_login.py index ebb49e4a3..27d497965 100644 --- a/plugins/lookup/vault_login.py +++ b/plugins/lookup/vault_login.py @@ -94,13 +94,13 @@ from ansible.module_utils.six import raise_from -from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase -from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError +from ...plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase +from ...plugins.module_utils._hashi_vault_common import HashiVaultValueError display = Display() try: - import hvac + import hvac # pylint: disable=unused-import except ImportError as imp_exc: HVAC_IMPORT_ERROR = imp_exc else: diff --git a/plugins/lookup/vault_token_create.py b/plugins/lookup/vault_token_create.py index 9b19ae290..520288897 100644 --- a/plugins/lookup/vault_token_create.py +++ b/plugins/lookup/vault_token_create.py @@ -102,13 +102,13 @@ from ansible.module_utils.six import raise_from -from ansible_collections.community.hashi_vault.plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase -from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultValueError +from ...plugins.plugin_utils._hashi_vault_lookup_base import HashiVaultLookupBase +from ...plugins.module_utils._hashi_vault_common import HashiVaultValueError display = Display() try: - import hvac + import hvac # pylint: disable=unused-import except ImportError as imp_exc: HVAC_IMPORT_ERROR = imp_exc else: diff --git a/plugins/modules/vault_login.py b/plugins/modules/vault_login.py index c52e969e0..fe0408da2 100644 --- a/plugins/modules/vault_login.py +++ b/plugins/modules/vault_login.py @@ -102,14 +102,14 @@ 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 ...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 + import hvac # pylint: disable=unused-import except ImportError: HAS_HVAC = False HVAC_IMPORT_ERROR = traceback.format_exc() diff --git a/tests/unit/compat/builtins.py b/tests/unit/compat/builtins.py index f60ee6782..349d310e8 100644 --- a/tests/unit/compat/builtins.py +++ b/tests/unit/compat/builtins.py @@ -26,7 +26,7 @@ # One unittest needs to import builtins via __import__() so we need to have # the string that represents it try: - import __builtin__ + import __builtin__ # pylint: disable=unused-import except ImportError: BUILTINS = 'builtins' else: diff --git a/tests/unit/compat/mock.py b/tests/unit/compat/mock.py index 515b94a3c..c98b26312 100644 --- a/tests/unit/compat/mock.py +++ b/tests/unit/compat/mock.py @@ -9,8 +9,6 @@ ''' Compat module for Python3.x's unittest.mock module ''' -import sys - # Python 2.7 # Note: Could use the pypi mock library on python3.x as well as python2.x. It diff --git a/tests/unit/plugins/lookup/conftest.py b/tests/unit/plugins/lookup/conftest.py index 244cb4c5e..ba4da9291 100644 --- a/tests/unit/plugins/lookup/conftest.py +++ b/tests/unit/plugins/lookup/conftest.py @@ -7,8 +7,6 @@ import pytest -from ...compat import mock - @pytest.fixture def minimal_vars(): diff --git a/tests/unit/plugins/module_utils/authentication/test_auth_none.py b/tests/unit/plugins/module_utils/authentication/test_auth_none.py index 87cfe91fb..1e3024f11 100644 --- a/tests/unit/plugins/module_utils/authentication/test_auth_none.py +++ b/tests/unit/plugins/module_utils/authentication/test_auth_none.py @@ -8,11 +8,8 @@ import pytest -from ansible_collections.community.hashi_vault.tests.unit.compat import mock - -from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_none import HashiVaultAuthMethodNone - -from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase +from ......plugins.module_utils._auth_method_none import HashiVaultAuthMethodNone +from ......plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase @pytest.fixture diff --git a/tests/unit/plugins/module_utils/authentication/test_auth_token.py b/tests/unit/plugins/module_utils/authentication/test_auth_token.py index 7f5d12802..d8a13435b 100644 --- a/tests/unit/plugins/module_utils/authentication/test_auth_token.py +++ b/tests/unit/plugins/module_utils/authentication/test_auth_token.py @@ -4,14 +4,12 @@ # SPDX-License-Identifier: GPL-3.0-or-later from __future__ import (absolute_import, division, print_function) -from _pytest.fixtures import fixture -from _pytest.python_api import raises __metaclass__ = type import os import pytest -from ansible_collections.community.hashi_vault.tests.unit.compat import mock +from ......tests.unit.compat import mock try: import hvac @@ -19,11 +17,11 @@ # python 2.6, which isn't supported anyway hvac = mock.MagicMock() -from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_token import ( +from ......plugins.module_utils._auth_method_token import ( HashiVaultAuthMethodToken, ) -from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import ( +from ......plugins.module_utils._hashi_vault_common import ( HashiVaultAuthMethodBase, HashiVaultValueError, ) diff --git a/tests/unit/plugins/module_utils/authentication/test_hashi_vault_authenticator.py b/tests/unit/plugins/module_utils/authentication/test_hashi_vault_authenticator.py index 439dd7f15..4853d2c76 100644 --- a/tests/unit/plugins/module_utils/authentication/test_hashi_vault_authenticator.py +++ b/tests/unit/plugins/module_utils/authentication/test_hashi_vault_authenticator.py @@ -8,9 +8,7 @@ import pytest -from ansible_collections.community.hashi_vault.tests.unit.compat import mock - -from ansible_collections.community.hashi_vault.plugins.module_utils._authenticator import HashiVaultAuthenticator +from ......plugins.module_utils._authenticator import HashiVaultAuthenticator @pytest.fixture diff --git a/tests/unit/plugins/module_utils/test_hashi_vault_helper.py b/tests/unit/plugins/module_utils/test_hashi_vault_helper.py index 1993a8598..6a5c6002b 100644 --- a/tests/unit/plugins/module_utils/test_hashi_vault_helper.py +++ b/tests/unit/plugins/module_utils/test_hashi_vault_helper.py @@ -6,12 +6,11 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -import sys import os import pytest -from ansible_collections.community.hashi_vault.tests.unit.compat import mock -from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import ( +from .....tests.unit.compat import mock +from .....plugins.module_utils._hashi_vault_common import ( HashiVaultHelper, _stringify, ) From ed5be2c42aaa6c64fcff499d2627f4c761533ffb Mon Sep 17 00:00:00 2001 From: chris93111 Date: Sun, 13 Feb 2022 15:22:20 +0100 Subject: [PATCH 37/45] Create _auth_method_k8s.py, add k8s auth, add role params for k8s auth --- plugins/doc_fragments/auth.py | 26 ++++ plugins/lookup/hashi_vault.py | 184 +++++++++++++++++++++++ plugins/module_utils/_auth_method_k8s.py | 53 +++++++ plugins/module_utils/_authenticator.py | 5 + 4 files changed, 268 insertions(+) create mode 100644 plugins/module_utils/_auth_method_k8s.py diff --git a/plugins/doc_fragments/auth.py b/plugins/doc_fragments/auth.py index 8c6bd8760..c3415c88b 100644 --- a/plugins/doc_fragments/auth.py +++ b/plugins/doc_fragments/auth.py @@ -29,6 +29,7 @@ class ModuleDocFragment(object): - jwt - cert - none + - kubernetes default: token type: str mount_point: @@ -75,6 +76,13 @@ class ModuleDocFragment(object): jwt: description: The JSON Web Token (JWT) to use for JWT authentication to Vault. type: str + kubernetes_token: + description: The Kubernetes Token (JWT) to use for Kubernetes authentication to Vault. + type: str + kubernetes_token_path: + description: If no kubernetes_token is specified, will try to read the token from this path. + default: '/var/run/secrets/kubernetes.io/serviceaccount/token' + type: str aws_profile: description: The AWS profile type: str @@ -305,4 +313,22 @@ class ModuleDocFragment(object): ini: - section: hashi_vault_collection key: cert_auth_private_key + kubernetes_token: + env: + - name: ANSIBLE_HASHI_VAULT_KUBERNETES_TOKEN + version_added: 2.3.0 + vars: + - name: ansible_hashi_vault_kubernetes_token + version_added: 2.3.0 + kubernetes_token_path: + env: + - name: ANSIBLE_HASHI_VAULT_KUBERNETES_TOKEN_PATH + version_added: 2.3.0 + ini: + - section: hashi_vault_collection + key: kubernetes_token_path + version_added: 2.3.0 + vars: + - name: ansible_hashi_vault_kubernetes_token_path + version_added: 2.3.0 ''' diff --git a/plugins/lookup/hashi_vault.py b/plugins/lookup/hashi_vault.py index 1ea9b2c90..5ac791125 100644 --- a/plugins/lookup/hashi_vault.py +++ b/plugins/lookup/hashi_vault.py @@ -61,6 +61,190 @@ - raw default: dict aliases: [ as ] +<<<<<<< HEAD +======= + url: + ini: + - section: lookup_hashi_vault + key: url + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: url + version_added: 1.4.0 + proxies: + ini: + - section: lookup_hashi_vault + key: proxies + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: proxies + version_added: 1.4.0 + ca_cert: + ini: + - section: lookup_hashi_vault + key: ca_cert + version_added: 1.2.0 + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: ca_cert + version_added: 1.4.0 + namespace: + ini: + - section: lookup_hashi_vault + key: namespace + version_added: 0.2.0 + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: namespace + version_added: 1.4.0 + timeout: + ini: + - section: lookup_hashi_vault + key: timeout + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: timeout + version_added: 1.4.0 + retries: + ini: + - section: lookup_hashi_vault + key: retries + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: retries + version_added: 1.4.0 + retry_action: + ini: + - section: lookup_hashi_vault + key: retry_action + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: retry_action + version_added: 1.4.0 + auth_method: + ini: + - section: lookup_hashi_vault + key: auth_method + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: auth_method + version_added: 1.4.0 + token_path: + ini: + - section: lookup_hashi_vault + key: token_path + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: token_path + version_added: 1.4.0 + token_file: + ini: + - section: lookup_hashi_vault + key: token_file + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: token_file + version_added: 1.4.0 + token_validate: + ini: + - section: lookup_hashi_vault + key: token_validate + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: token_validate + version_added: 1.4.0 + role_id: + ini: + - section: lookup_hashi_vault + key: role_id + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: role_id + version_added: 1.4.0 + aws_iam_server_id: + ini: + - section: lookup_hashi_vault + key: aws_iam_server_id + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: aws_iam_server_id + version_added: 1.4.0 + kubernetes_token: + ini: + - section: lookup_hashi_vault + key: kubernetes_token + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: kubernetes_token + version_added: 2.3.0 + kubernetes_token_path: + ini: + - section: lookup_hashi_vault + key: kubernetes_token_path + deprecated: + why: collection-wide config section + version: 3.0.0 + collection_name: community.hashi_vault + alternatives: use section [hashi_vault_collection] + - section: hashi_vault_collection + key: token_path + version_added: 2.3.0 """ EXAMPLES = """ diff --git a/plugins/module_utils/_auth_method_k8s.py b/plugins/module_utils/_auth_method_k8s.py new file mode 100644 index 000000000..41174157c --- /dev/null +++ b/plugins/module_utils/_auth_method_k8s.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021 FERREIRA Christophe (@chris93111) +# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause) + +'''Python versions supported: >=3.6''' + +# FOR INTERNAL COLLECTION USE ONLY +# The interfaces in this file are meant for use within the community.hashi_vault collection +# and may not remain stable to outside uses. Changes may be made in ANY release, even a bugfix release. +# See also: https://github.com/ansible/community/issues/539#issuecomment-780839686 +# Please open an issue if you have questions about this. + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase, HashiVaultValueError +import os + +class HashiVaultAuthMethodKubernetes(HashiVaultAuthMethodBase): + '''HashiVault option group class for auth: k8s''' + + NAME = 'kubernetes' + OPTIONS = ['kubernetes_token', 'kubernetes_token_path', 'role_id', 'mount_point'] + + def __init__(self, option_adapter, warning_callback): + super(HashiVaultAuthMethodKubernetes, self).__init__(option_adapter, warning_callback) + + def validate(self): + self.validate_by_required_fields('role_id') + + if self._options.get_option('kubernetes_token') is None and self._options.get_option('kubernetes_token_path') is not None: + token_filename = self._options.get_option('kubernetes_token_path') + if os.path.exists(token_filename): + if not os.path.isfile(token_filename): + raise HashiVaultValueError("The Kubernetes token file '%s' was found but is not a file." % token_filename) + with open(token_filename) as token_file: + self._options.set_option('kubernetes_token', token_file.read().strip()) + + if self._options.get_option('kubernetes_token') is None: + raise HashiVaultValueError(self._options.get_option('kubernetes_token')+self._options.get_option_default('kubernetes_token_path')+"No Kubernetes Token specified or discovered.") + + def authenticate(self, client, use_token=True): + origin_params = self._options.get_filled_options(*self.OPTIONS) + params = {"role": origin_params.get('role_id'), + "jwt": origin_params.get('kubernetes_token'), + "mount_point": origin_params.get('mount_point')} + + try: + response = client.auth_kubernetes(**params) + except (NotImplementedError, AttributeError): + raise NotImplementedError("Kubernetes authentication requires HVAC version 0.8.0 or higher.") + + return response diff --git a/plugins/module_utils/_authenticator.py b/plugins/module_utils/_authenticator.py index acf574bfe..58f79dbde 100644 --- a/plugins/module_utils/_authenticator.py +++ b/plugins/module_utils/_authenticator.py @@ -20,6 +20,7 @@ from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_azure import HashiVaultAuthMethodAzure from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_cert import HashiVaultAuthMethodCert from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_jwt import HashiVaultAuthMethodJwt +from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_k8s import HashiVaultAuthMethodKubernetes from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_ldap import HashiVaultAuthMethodLdap from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_none import HashiVaultAuthMethodNone from ansible_collections.community.hashi_vault.plugins.module_utils._auth_method_token import HashiVaultAuthMethodToken @@ -38,6 +39,7 @@ class HashiVaultAuthenticator(): 'jwt', 'cert', 'none', + 'kubernetes', ]), mount_point=dict(type='str'), token=dict(type='str', no_log=True, default=None), @@ -49,6 +51,8 @@ class HashiVaultAuthenticator(): role_id=dict(type='str'), secret_id=dict(type='str', no_log=True), jwt=dict(type='str', no_log=True), + kubernetes_token=dict(type='str', no_log=True), + kubernetes_token_path=dict(type='str', default='/var/run/secrets/kubernetes.io/serviceaccount/token', no_log=False), aws_profile=dict(type='str', aliases=['boto_profile']), aws_access_key=dict(type='str', aliases=['aws_access_key_id'], no_log=False), aws_secret_key=dict(type='str', aliases=['aws_secret_access_key'], no_log=True), @@ -73,6 +77,7 @@ def __init__(self, option_adapter, warning_callback, deprecate_callback): 'azure': HashiVaultAuthMethodAzure(option_adapter, warning_callback, deprecate_callback), 'cert': HashiVaultAuthMethodCert(option_adapter, warning_callback, deprecate_callback), 'jwt': HashiVaultAuthMethodJwt(option_adapter, warning_callback, deprecate_callback), + 'kubernetes': HashiVaultAuthMethodKubernetes(option_adapter, warning_callback, deprecate_callback), 'ldap': HashiVaultAuthMethodLdap(option_adapter, warning_callback, deprecate_callback), 'none': HashiVaultAuthMethodNone(option_adapter, warning_callback, deprecate_callback), 'token': HashiVaultAuthMethodToken(option_adapter, warning_callback, deprecate_callback), From 795fa08f709f3491bba6e7ac9a1c742342f0c020 Mon Sep 17 00:00:00 2001 From: chris93111 Date: Wed, 16 Feb 2022 13:39:09 +0100 Subject: [PATCH 38/45] Update plugins/lookup/hashi_vault.py Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> --- plugins/doc_fragments/auth.py | 7 ++----- plugins/lookup/hashi_vault.py | 24 ------------------------ 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/plugins/doc_fragments/auth.py b/plugins/doc_fragments/auth.py index c3415c88b..a36a7fddb 100644 --- a/plugins/doc_fragments/auth.py +++ b/plugins/doc_fragments/auth.py @@ -79,10 +79,12 @@ class ModuleDocFragment(object): kubernetes_token: description: The Kubernetes Token (JWT) to use for Kubernetes authentication to Vault. type: str + version_added: 2.4.0 kubernetes_token_path: description: If no kubernetes_token is specified, will try to read the token from this path. default: '/var/run/secrets/kubernetes.io/serviceaccount/token' type: str + version_added: 2.4.0 aws_profile: description: The AWS profile type: str @@ -316,19 +318,14 @@ class ModuleDocFragment(object): kubernetes_token: env: - name: ANSIBLE_HASHI_VAULT_KUBERNETES_TOKEN - version_added: 2.3.0 vars: - name: ansible_hashi_vault_kubernetes_token - version_added: 2.3.0 kubernetes_token_path: env: - name: ANSIBLE_HASHI_VAULT_KUBERNETES_TOKEN_PATH - version_added: 2.3.0 ini: - section: hashi_vault_collection key: kubernetes_token_path - version_added: 2.3.0 vars: - name: ansible_hashi_vault_kubernetes_token_path - version_added: 2.3.0 ''' diff --git a/plugins/lookup/hashi_vault.py b/plugins/lookup/hashi_vault.py index 5ac791125..369ceecc6 100644 --- a/plugins/lookup/hashi_vault.py +++ b/plugins/lookup/hashi_vault.py @@ -221,30 +221,6 @@ - section: hashi_vault_collection key: aws_iam_server_id version_added: 1.4.0 - kubernetes_token: - ini: - - section: lookup_hashi_vault - key: kubernetes_token - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: kubernetes_token - version_added: 2.3.0 - kubernetes_token_path: - ini: - - section: lookup_hashi_vault - key: kubernetes_token_path - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: token_path - version_added: 2.3.0 """ EXAMPLES = """ From a035ef2e78b602750e225105fff401b8ed93a341 Mon Sep 17 00:00:00 2001 From: chris93111 Date: Wed, 16 Feb 2022 13:48:05 +0100 Subject: [PATCH 39/45] change to auth.kubernetes + switch depracated --- plugins/doc_fragments/auth.py | 2 +- plugins/module_utils/_auth_method_k8s.py | 12 ++++++++---- plugins/module_utils/_authenticator.py | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/doc_fragments/auth.py b/plugins/doc_fragments/auth.py index a36a7fddb..e34f64d1b 100644 --- a/plugins/doc_fragments/auth.py +++ b/plugins/doc_fragments/auth.py @@ -28,8 +28,8 @@ class ModuleDocFragment(object): - azure - jwt - cert - - none - kubernetes + - none default: token type: str mount_point: diff --git a/plugins/module_utils/_auth_method_k8s.py b/plugins/module_utils/_auth_method_k8s.py index 41174157c..cf8d146b2 100644 --- a/plugins/module_utils/_auth_method_k8s.py +++ b/plugins/module_utils/_auth_method_k8s.py @@ -16,6 +16,7 @@ from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase, HashiVaultValueError import os + class HashiVaultAuthMethodKubernetes(HashiVaultAuthMethodBase): '''HashiVault option group class for auth: k8s''' @@ -37,17 +38,20 @@ def validate(self): self._options.set_option('kubernetes_token', token_file.read().strip()) if self._options.get_option('kubernetes_token') is None: - raise HashiVaultValueError(self._options.get_option('kubernetes_token')+self._options.get_option_default('kubernetes_token_path')+"No Kubernetes Token specified or discovered.") + raise HashiVaultValueError(self._options.get_option_default('kubernetes_token_path') + + " No Kubernetes Token specified or discovered.") def authenticate(self, client, use_token=True): origin_params = self._options.get_filled_options(*self.OPTIONS) params = {"role": origin_params.get('role_id'), "jwt": origin_params.get('kubernetes_token'), - "mount_point": origin_params.get('mount_point')} + "mount_point": origin_params.get('mount_point'), + "use_token": use_token} try: - response = client.auth_kubernetes(**params) + response = client.auth.kubernetes.login(**params) except (NotImplementedError, AttributeError): - raise NotImplementedError("Kubernetes authentication requires HVAC version 0.8.0 or higher.") + self.warn("Kubernetes authentication requires HVAC version 1.0.0 or higher. Deprecated method 'auth_kubernetes' will be used.") + response = client.auth_kubernetes(**params) return response diff --git a/plugins/module_utils/_authenticator.py b/plugins/module_utils/_authenticator.py index 58f79dbde..836fc24a3 100644 --- a/plugins/module_utils/_authenticator.py +++ b/plugins/module_utils/_authenticator.py @@ -38,8 +38,8 @@ class HashiVaultAuthenticator(): 'azure', 'jwt', 'cert', - 'none', 'kubernetes', + 'none', ]), mount_point=dict(type='str'), token=dict(type='str', no_log=True, default=None), From 16fb27f7d8eb6f7fa842cbc698cc3c48ffbc2209 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Fri, 1 Apr 2022 14:07:22 -0400 Subject: [PATCH 40/45] Bump version_added --- plugins/doc_fragments/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/doc_fragments/auth.py b/plugins/doc_fragments/auth.py index e34f64d1b..ae07545d4 100644 --- a/plugins/doc_fragments/auth.py +++ b/plugins/doc_fragments/auth.py @@ -79,12 +79,12 @@ class ModuleDocFragment(object): kubernetes_token: description: The Kubernetes Token (JWT) to use for Kubernetes authentication to Vault. type: str - version_added: 2.4.0 + version_added: 2.5.0 kubernetes_token_path: description: If no kubernetes_token is specified, will try to read the token from this path. default: '/var/run/secrets/kubernetes.io/serviceaccount/token' type: str - version_added: 2.4.0 + version_added: 2.5.0 aws_profile: description: The AWS profile type: str From 420cf1e10dcf4859b8885e53739d999a751154cf Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:28:03 -0500 Subject: [PATCH 41/45] remove old conflict artifact --- plugins/lookup/hashi_vault.py | 160 ---------------------------------- 1 file changed, 160 deletions(-) diff --git a/plugins/lookup/hashi_vault.py b/plugins/lookup/hashi_vault.py index 369ceecc6..1ea9b2c90 100644 --- a/plugins/lookup/hashi_vault.py +++ b/plugins/lookup/hashi_vault.py @@ -61,166 +61,6 @@ - raw default: dict aliases: [ as ] -<<<<<<< HEAD -======= - url: - ini: - - section: lookup_hashi_vault - key: url - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: url - version_added: 1.4.0 - proxies: - ini: - - section: lookup_hashi_vault - key: proxies - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: proxies - version_added: 1.4.0 - ca_cert: - ini: - - section: lookup_hashi_vault - key: ca_cert - version_added: 1.2.0 - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: ca_cert - version_added: 1.4.0 - namespace: - ini: - - section: lookup_hashi_vault - key: namespace - version_added: 0.2.0 - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: namespace - version_added: 1.4.0 - timeout: - ini: - - section: lookup_hashi_vault - key: timeout - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: timeout - version_added: 1.4.0 - retries: - ini: - - section: lookup_hashi_vault - key: retries - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: retries - version_added: 1.4.0 - retry_action: - ini: - - section: lookup_hashi_vault - key: retry_action - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: retry_action - version_added: 1.4.0 - auth_method: - ini: - - section: lookup_hashi_vault - key: auth_method - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: auth_method - version_added: 1.4.0 - token_path: - ini: - - section: lookup_hashi_vault - key: token_path - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: token_path - version_added: 1.4.0 - token_file: - ini: - - section: lookup_hashi_vault - key: token_file - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: token_file - version_added: 1.4.0 - token_validate: - ini: - - section: lookup_hashi_vault - key: token_validate - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: token_validate - version_added: 1.4.0 - role_id: - ini: - - section: lookup_hashi_vault - key: role_id - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: role_id - version_added: 1.4.0 - aws_iam_server_id: - ini: - - section: lookup_hashi_vault - key: aws_iam_server_id - deprecated: - why: collection-wide config section - version: 3.0.0 - collection_name: community.hashi_vault - alternatives: use section [hashi_vault_collection] - - section: hashi_vault_collection - key: aws_iam_server_id - version_added: 1.4.0 """ EXAMPLES = """ From 88f30947c178ba09a6ce978527ea2798bf829d57 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:40:49 -0500 Subject: [PATCH 42/45] [chore] use relative imports --- plugins/module_utils/_auth_method_k8s.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/_auth_method_k8s.py b/plugins/module_utils/_auth_method_k8s.py index cf8d146b2..9b492c7c4 100644 --- a/plugins/module_utils/_auth_method_k8s.py +++ b/plugins/module_utils/_auth_method_k8s.py @@ -13,7 +13,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type -from ansible_collections.community.hashi_vault.plugins.module_utils._hashi_vault_common import HashiVaultAuthMethodBase, HashiVaultValueError +from ..module_utils._hashi_vault_common import HashiVaultAuthMethodBase, HashiVaultValueError import os From 8d761468d501e5470b5e2c640b085011fe335512 Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:41:22 -0500 Subject: [PATCH 43/45] update auth method name --- plugins/module_utils/_auth_method_k8s.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/module_utils/_auth_method_k8s.py b/plugins/module_utils/_auth_method_k8s.py index 9b492c7c4..c3f989abe 100644 --- a/plugins/module_utils/_auth_method_k8s.py +++ b/plugins/module_utils/_auth_method_k8s.py @@ -18,7 +18,7 @@ class HashiVaultAuthMethodKubernetes(HashiVaultAuthMethodBase): - '''HashiVault option group class for auth: k8s''' + '''HashiVault option group class for auth: kubernetes''' NAME = 'kubernetes' OPTIONS = ['kubernetes_token', 'kubernetes_token_path', 'role_id', 'mount_point'] From bcbeb9d02857c5afee18af5c4ab9fa8be677b9fd Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:43:29 -0500 Subject: [PATCH 44/45] nit: change to preferred style --- plugins/module_utils/_auth_method_k8s.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/module_utils/_auth_method_k8s.py b/plugins/module_utils/_auth_method_k8s.py index c3f989abe..735ce524a 100644 --- a/plugins/module_utils/_auth_method_k8s.py +++ b/plugins/module_utils/_auth_method_k8s.py @@ -38,15 +38,18 @@ def validate(self): self._options.set_option('kubernetes_token', token_file.read().strip()) if self._options.get_option('kubernetes_token') is None: - raise HashiVaultValueError(self._options.get_option_default('kubernetes_token_path') + - " No Kubernetes Token specified or discovered.") + raise HashiVaultValueError( + self._options.get_option_default('kubernetes_token_path') + " No Kubernetes Token specified or discovered." + ) def authenticate(self, client, use_token=True): origin_params = self._options.get_filled_options(*self.OPTIONS) - params = {"role": origin_params.get('role_id'), - "jwt": origin_params.get('kubernetes_token'), - "mount_point": origin_params.get('mount_point'), - "use_token": use_token} + params = { + "role": origin_params.get('role_id'), + "jwt": origin_params.get('kubernetes_token'), + "mount_point": origin_params.get('mount_point'), + "use_token": use_token, + } try: response = client.auth.kubernetes.login(**params) From b4653aa559d297a9b23b6d265f7dd939e68f298c Mon Sep 17 00:00:00 2001 From: Brian Scholer <1260690+briantist@users.noreply.github.com> Date: Sun, 5 Mar 2023 15:44:10 -0500 Subject: [PATCH 45/45] add required deprecate_callback to init --- plugins/module_utils/_auth_method_k8s.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/module_utils/_auth_method_k8s.py b/plugins/module_utils/_auth_method_k8s.py index 735ce524a..faed8d078 100644 --- a/plugins/module_utils/_auth_method_k8s.py +++ b/plugins/module_utils/_auth_method_k8s.py @@ -23,8 +23,8 @@ class HashiVaultAuthMethodKubernetes(HashiVaultAuthMethodBase): NAME = 'kubernetes' OPTIONS = ['kubernetes_token', 'kubernetes_token_path', 'role_id', 'mount_point'] - def __init__(self, option_adapter, warning_callback): - super(HashiVaultAuthMethodKubernetes, self).__init__(option_adapter, warning_callback) + def __init__(self, option_adapter, warning_callback, deprecate_callback): + super(HashiVaultAuthMethodKubernetes, self).__init__(option_adapter, warning_callback, deprecate_callback) def validate(self): self.validate_by_required_fields('role_id')