From 08b4ceb1acf9166c75a58d47c51a7d431ee29b4b Mon Sep 17 00:00:00 2001 From: Andreas Maier Date: Thu, 28 Nov 2024 19:47:58 +0100 Subject: [PATCH] Fixed return and logging of password-like props in clear text Details: * Increased minimum version of zhmcclient to 1.8.2 to pick up fixes for no longer logging password-like properties in clear-text. * Fixed that all password-like input parameters that were written in clear text to the module entry log are now blanked out. This affected the following modules: zhmc_ldap_server_definition, zhmc_lpar, zhmc_partition, zhmc_user. * Fixed that all password-like input parameters that were added to the module return value in clear text for 'state' values that created or updated the resource are now removed from the return value. This affected the following modules: zhmc_ldap_server_definition, zhmc_lpar, zhmc_partition. * The 'hmc_auth' input parameter is no longer completely removed from the module entry log, but instead its sensitive items 'password' and 'session_id' are now blanked out. * In support of the above, added common functions blanked_params(), blanked_dict() and removed_dict(). Added unit tests for these new functions. * Improved the end2end tests for the affected modules to check that the module output does not contain the password-like properties. Signed-off-by: Andreas Maier --- .../modules/zhmc_ldap_server_definition.rst | 2 +- docs/source/modules/zhmc_lpar.rst | 2 +- docs/source/modules/zhmc_partition.rst | 2 +- docs/source/modules/zhmc_user.rst | 4 +- docs/source/release_notes.rst | 15 +- minimum-constraints-install.txt | 4 +- plugins/module_utils/common.py | 78 ++++- plugins/modules/zhmc_adapter.py | 8 +- plugins/modules/zhmc_adapter_list.py | 8 +- plugins/modules/zhmc_console.py | 8 +- plugins/modules/zhmc_cpc.py | 9 +- plugins/modules/zhmc_cpc_capacity.py | 8 +- plugins/modules/zhmc_cpc_list.py | 8 +- plugins/modules/zhmc_crypto_attachment.py | 8 +- plugins/modules/zhmc_hba.py | 8 +- .../modules/zhmc_ldap_server_definition.py | 25 +- .../zhmc_ldap_server_definition_list.py | 8 +- plugins/modules/zhmc_lpar.py | 21 +- plugins/modules/zhmc_lpar_command.py | 8 +- plugins/modules/zhmc_lpar_list.py | 8 +- plugins/modules/zhmc_lpar_messages.py | 8 +- plugins/modules/zhmc_nic.py | 8 +- plugins/modules/zhmc_nic_list.py | 8 +- plugins/modules/zhmc_partition.py | 19 +- plugins/modules/zhmc_partition_command.py | 8 +- plugins/modules/zhmc_partition_list.py | 8 +- plugins/modules/zhmc_partition_messages.py | 8 +- plugins/modules/zhmc_password_rule.py | 8 +- plugins/modules/zhmc_password_rule_list.py | 8 +- plugins/modules/zhmc_session.py | 8 +- plugins/modules/zhmc_storage_group.py | 8 +- .../modules/zhmc_storage_group_attachment.py | 8 +- plugins/modules/zhmc_storage_volume.py | 8 +- plugins/modules/zhmc_user.py | 42 ++- plugins/modules/zhmc_user_list.py | 8 +- plugins/modules/zhmc_user_pattern.py | 8 +- plugins/modules/zhmc_user_pattern_list.py | 8 +- plugins/modules/zhmc_user_role.py | 8 +- plugins/modules/zhmc_user_role_list.py | 8 +- plugins/modules/zhmc_versions.py | 8 +- plugins/modules/zhmc_virtual_function.py | 8 +- requirements.txt | 6 +- .../test_zhmc_ldap_server_definition.py | 42 ++- tests/end2end/test_zhmc_lpar.py | 4 + tests/end2end/test_zhmc_partition.py | 23 +- tests/end2end/test_zhmc_user.py | 4 + tests/unit/test_common.py | 280 ++++++++++++++++++ 47 files changed, 631 insertions(+), 183 deletions(-) diff --git a/docs/source/modules/zhmc_ldap_server_definition.rst b/docs/source/modules/zhmc_ldap_server_definition.rst index b7a3bb917..473b0b32f 100644 --- a/docs/source/modules/zhmc_ldap_server_definition.rst +++ b/docs/source/modules/zhmc_ldap_server_definition.rst @@ -228,7 +228,7 @@ ldap_server_definition | **type**: str {property} - Additional properties of the LDAP Server Definition, as described in the data model of the 'LDAP Server Definition' object in the :ref:`HMC API ` book. The property names have hyphens (-) as described in that book. + Additional properties of the LDAP Server Definition, as described in the data model of the 'LDAP Server Definition' object in the :ref:`HMC API ` book. Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book. | **type**: raw diff --git a/docs/source/modules/zhmc_lpar.rst b/docs/source/modules/zhmc_lpar.rst index 3275f833c..cada73dd6 100644 --- a/docs/source/modules/zhmc_lpar.rst +++ b/docs/source/modules/zhmc_lpar.rst @@ -560,7 +560,7 @@ lpar | **type**: str {property} - Additional properties of the LPAR, as described in the data model of the 'Logical Partition' object in the :ref:`HMC API ` book. The property names have hyphens (-) as described in that book. + Additional properties of the LPAR, as described in the data model of the 'Logical Partition' object in the :ref:`HMC API ` book. Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book. | **type**: raw diff --git a/docs/source/modules/zhmc_partition.rst b/docs/source/modules/zhmc_partition.rst index 8300c5473..0d3faf26b 100644 --- a/docs/source/modules/zhmc_partition.rst +++ b/docs/source/modules/zhmc_partition.rst @@ -515,7 +515,7 @@ partition | **type**: str {property} - Additional properties of the partition, as described in the data model of the 'Partition' object in the :ref:`HMC API ` book. The property names have hyphens (-) as described in that book. + Additional properties of the partition, as described in the data model of the 'Partition' object in the :ref:`HMC API ` book. Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book. | **type**: raw diff --git a/docs/source/modules/zhmc_user.rst b/docs/source/modules/zhmc_user.rst index 798351ea3..a762718df 100644 --- a/docs/source/modules/zhmc_user.rst +++ b/docs/source/modules/zhmc_user.rst @@ -277,7 +277,7 @@ user | **type**: str {property} - Additional properties of the user, as described in the data model of the 'User' object in the :ref:`HMC API ` book. The property names have hyphens (-) as described in that book. + Additional properties of the user, as described in the data model of the 'User' object in the :ref:`HMC API ` book. Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book. | **type**: raw @@ -348,7 +348,7 @@ user | **type**: dict {property} - Properties of the LDAP server definition, as described in the data model of the 'LDAP Server Definition' object in the :ref:`HMC API ` book. The property names have hyphens (-) as described in that book. + Properties of the LDAP server definition, as described in the data model of the 'LDAP Server Definition' object in the :ref:`HMC API ` book. Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book. | **type**: raw diff --git a/docs/source/release_notes.rst b/docs/source/release_notes.rst index 5db5f9f7f..42e178e40 100644 --- a/docs/source/release_notes.rst +++ b/docs/source/release_notes.rst @@ -37,7 +37,16 @@ Availability: `AutomationHub`_, `Galaxy`_, `GitHub`_ * Fixed safety issues up to 2024-11-21. -* Increased zhmcclient version to 1.18.0 to pick up fixes. (issue #1074) +* Increased zhmcclient version to 1.18.2 to pick up fixes. (issue #1074) + +* Fixed that all password-like input parameters that were written in clear text + to the module entry log are now blanked out. This affected the following + modules: zhmc_ldap_server_definition, zhmc_lpar, zhmc_partition, zhmc_user. + +* Fixed that all password-like input parameters that were added to the + module return value in clear text for 'state' values that created or updated + the resource are now removed from the return value. This affected the + following modules: zhmc_ldap_server_definition, zhmc_lpar, zhmc_partition. * Sanity test: Fixed the sanity test on AutomationHub which failed because the "compile" and "import" tests were run for all target node Python versions, @@ -67,6 +76,10 @@ Availability: `AutomationHub`_, `Galaxy`_, `GitHub`_ * Support for ansible-core 2.18, by adding an ignore file for the sanity tests. +* The 'hmc_auth' input parameter is no longer completely removed from the + module entry log, but instead its sensitive items 'password' and 'session_id' + are now blanked out. + **Cleanup:** * Removed the unnecessary .pylintrc file from the distribution archive of the diff --git a/minimum-constraints-install.txt b/minimum-constraints-install.txt index bd5727cfa..845127ab1 100644 --- a/minimum-constraints-install.txt +++ b/minimum-constraints-install.txt @@ -38,7 +38,7 @@ requests==2.32.2 pytz==2019.1 -zhmcclient==1.18.0 +zhmcclient==1.18.2 # Indirect dependencies for install that are needed for some reason (must be consistent with requirements.txt) @@ -57,7 +57,7 @@ packaging==22.0 PyYAML==6.0.2 python-dateutil==2.8.2 -jsonschema==4.18.1 +jsonschema==4.18.2 urllib3==1.26.19 diff --git a/plugins/module_utils/common.py b/plugins/module_utils/common.py index 901e9e88b..65a3ea700 100644 --- a/plugins/module_utils/common.py +++ b/plugins/module_utils/common.py @@ -1423,7 +1423,7 @@ def params_deepcopy(params): an optional '_faked_session' item with a value that cannot be copied. Parameters: - params (dict): Module input parameters. + params (dict): Module input parameters. Must not be None. Returns: dict: Deep copy of params, where possible. @@ -1435,3 +1435,79 @@ def params_deepcopy(params): except TypeError: copy_params[key] = value return copy_params + + +def blanked_params(params, blanked_properties=None): + """ + Return a copy of the module input parameters, with the following items + blanked out: + + * params['properties'][...] according to the blanked_properties list + * params['hmc_auth']['password'] + * params['hmc_auth']['session_id'] + + Parameters: + params (dict): Module input parameters. Must not be None. + blanked_properties (Sequence): List of property names that will be + blanked out in the 'properties' item of the module input parameters. + Property names that are not in the input properties will be ignored. + + Returns: + dict: Deep copy of the input parameters, with blanked out values. + """ + # The params['properties'] dict and the params['hmc_auth'] dict in the + # return value will be copies of the corresponding input items, and + # therefore it is sufficient to make a shallow copy of params. + copied_params = dict(params) + if 'properties' in copied_params and copied_params['properties'] \ + and blanked_properties: + copied_params['properties'] = \ + blanked_dict(copied_params['properties'], blanked_properties) + if 'hmc_auth' in copied_params: + copied_params['hmc_auth'] = \ + blanked_dict(copied_params['hmc_auth'], ['password', 'session_id']) + return copied_params + + +def blanked_dict(properties, blanked_properties): + """ + Return a shallow copy of the input properties, where the values of the + specified properties have been blanked out. + + Parameters: + properties (Mapping): Input properties. Must not be None. + blanked_properties (Sequence): List of property names that will be + blanked out. Property names that are not in the input properties + will be ignored. Must not be None. + + Returns: + dict: Shallow copy of the input properties, with blanked out values. + """ + copied_properties = dict(properties) + for pname in blanked_properties: + if pname in copied_properties: + copied_properties[pname] = BLANKED_OUT + return copied_properties + + +def removed_dict(properties, removed_properties): + """ + Return a shallow copy of the input properties, where the specified + properties have been removed. + + Parameters: + properties (Mapping): Input properties. Must not be None. + removed_properties (Sequence): List of property names that will be + removed. Property names that are not in the input properties + will be ignored. Must not be None. + + Returns: + dict: Shallow copy of the input properties, with removed properties. + """ + copied_properties = dict(properties) + for pname in removed_properties: + try: + del copied_properties[pname] + except KeyError: + pass + return copied_properties diff --git a/plugins/modules/zhmc_adapter.py b/plugins/modules/zhmc_adapter.py index 032ec4ffd..73ff8fb01 100644 --- a/plugins/modules/zhmc_adapter.py +++ b/plugins/modules/zhmc_adapter.py @@ -350,7 +350,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, eq_hex, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -966,9 +966,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_adapter_list.py b/plugins/modules/zhmc_adapter_list.py index bf1d31855..397d666f6 100644 --- a/plugins/modules/zhmc_adapter_list.py +++ b/plugins/modules/zhmc_adapter_list.py @@ -279,7 +279,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, \ - missing_required_lib, parse_hmc_host # noqa: E402 + missing_required_lib, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -474,9 +474,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_console.py b/plugins/modules/zhmc_console.py index bbf0b7232..af4999d09 100644 --- a/plugins/modules/zhmc_console.py +++ b/plugins/modules/zhmc_console.py @@ -237,7 +237,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -413,9 +413,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_cpc.py b/plugins/modules/zhmc_cpc.py index d2f5d8b18..ac903dfab 100644 --- a/plugins/modules/zhmc_cpc.py +++ b/plugins/modules/zhmc_cpc.py @@ -416,7 +416,8 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, StatusError, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, pull_properties, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, pull_properties, parse_hmc_host, \ + blanked_params # noqa: E402 try: import urllib3 @@ -899,9 +900,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_cpc_capacity.py b/plugins/modules/zhmc_cpc_capacity.py index f86c2f540..70db6b8e7 100644 --- a/plugins/modules/zhmc_cpc_capacity.py +++ b/plugins/modules/zhmc_cpc_capacity.py @@ -451,7 +451,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ common_fail_on_import_errors, parse_hmc_host, \ - underscore_properties # noqa: E402 + underscore_properties, blanked_params # noqa: E402 try: import urllib3 @@ -835,9 +835,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_cpc_list.py b/plugins/modules/zhmc_cpc_list.py index e1d25d771..53df4f777 100644 --- a/plugins/modules/zhmc_cpc_list.py +++ b/plugins/modules/zhmc_cpc_list.py @@ -222,7 +222,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -330,9 +330,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_crypto_attachment.py b/plugins/modules/zhmc_crypto_attachment.py index 810aad953..030022136 100644 --- a/plugins/modules/zhmc_crypto_attachment.py +++ b/plugins/modules/zhmc_crypto_attachment.py @@ -374,7 +374,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: @@ -1091,9 +1091,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_hba.py b/plugins/modules/zhmc_hba.py index 919089892..1250f41d7 100644 --- a/plugins/modules/zhmc_hba.py +++ b/plugins/modules/zhmc_hba.py @@ -246,7 +246,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, wait_for_transition_completion, \ eq_hex, to_unicode, process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -627,9 +627,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_ldap_server_definition.py b/plugins/modules/zhmc_ldap_server_definition.py index 52755dc5d..faf6d0af5 100644 --- a/plugins/modules/zhmc_ldap_server_definition.py +++ b/plugins/modules/zhmc_ldap_server_definition.py @@ -212,6 +212,7 @@ description: "Additional properties of the LDAP Server Definition, as described in the data model of the 'LDAP Server Definition' object in the R(HMC API,HMC API) book. + Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book." type: raw sample: @@ -244,7 +245,8 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params, \ + blanked_dict, removed_dict # noqa: E402 try: import urllib3 @@ -325,6 +327,11 @@ def casefold(txt): 'class': (False, False, False, None, None, None), } +# Write-only properties (blanked out in logs and removed in output) +WRITEONLY_PROPERTIES_USCORE = ['bind_password'] +WRITEONLY_PROPERTIES_HYPHEN = [p.replace('_', '-') + for p in WRITEONLY_PROPERTIES_USCORE] + def process_properties(lsd, params): """ @@ -521,9 +528,11 @@ def ensure_present(params, check_mode): raise AssertionError("Unexpected " "create_props: %r" % create_props) if update_props: - LOGGER.debug( - "Existing LDAP Server Definition %r needs to get " - "properties updated: %r", lsd_name, update_props) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug( + "Existing LDAP Server Definition %r needs to get " + "properties updated: %r", lsd_name, + blanked_dict(update_props, WRITEONLY_PROPERTIES_USCORE)) if not check_mode: lsd.update_properties(update_props) # We refresh the properties after the update, in case an @@ -539,6 +548,8 @@ def ensure_present(params, check_mode): if not lsd: raise AssertionError() + result = removed_dict(result, WRITEONLY_PROPERTIES_HYPHEN) + return changed, result finally: @@ -669,9 +680,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params, WRITEONLY_PROPERTIES_USCORE)) try: diff --git a/plugins/modules/zhmc_ldap_server_definition_list.py b/plugins/modules/zhmc_ldap_server_definition_list.py index 57ec99e68..a866c9ca8 100644 --- a/plugins/modules/zhmc_ldap_server_definition_list.py +++ b/plugins/modules/zhmc_ldap_server_definition_list.py @@ -162,7 +162,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -246,9 +246,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_lpar.py b/plugins/modules/zhmc_lpar.py index 74592a3ce..ff31f9968 100644 --- a/plugins/modules/zhmc_lpar.py +++ b/plugins/modules/zhmc_lpar.py @@ -454,6 +454,7 @@ description: "Additional properties of the LPAR, as described in the data model of the 'Logical Partition' object in the R(HMC API,HMC API) book. + Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book." type: raw sample: @@ -620,7 +621,8 @@ hmc_auth_parameter, Error, ParameterError, StatusError, \ ensure_lpar_inactive, ensure_lpar_active, ensure_lpar_loaded, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, pull_properties, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, pull_properties, parse_hmc_host, \ + blanked_params, removed_dict # noqa: E402 try: import urllib3 @@ -781,6 +783,11 @@ 'request_origin': (False, False, False, None, None, None), } +# Write-only properties (blanked out in logs and removed in output) +WRITEONLY_PROPERTIES_USCORE = ['ssc_master_pw', 'zaware_master_pw'] +WRITEONLY_PROPERTIES_HYPHEN = [p.replace('_', '-') + for p in WRITEONLY_PROPERTIES_USCORE] + def process_properties(lpar, params): """ @@ -1094,6 +1101,9 @@ def ensure_active(params, check_mode): add_artificial_properties(lpar_properties, lpar) + lpar_properties = removed_dict( + lpar_properties, WRITEONLY_PROPERTIES_HYPHEN) + return changed, lpar_properties finally: @@ -1153,6 +1163,9 @@ def ensure_loaded(params, check_mode): add_artificial_properties(lpar_properties, lpar) + lpar_properties = removed_dict( + lpar_properties, WRITEONLY_PROPERTIES_HYPHEN) + return changed, lpar_properties finally: @@ -1323,9 +1336,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params, WRITEONLY_PROPERTIES_USCORE)) try: diff --git a/plugins/modules/zhmc_lpar_command.py b/plugins/modules/zhmc_lpar_command.py index d3b7b7460..94a7ee62b 100644 --- a/plugins/modules/zhmc_lpar_command.py +++ b/plugins/modules/zhmc_lpar_command.py @@ -211,7 +211,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, NotificationThread, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -424,9 +424,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = True try: diff --git a/plugins/modules/zhmc_lpar_list.py b/plugins/modules/zhmc_lpar_list.py index f42ceb40a..3c222026d 100644 --- a/plugins/modules/zhmc_lpar_list.py +++ b/plugins/modules/zhmc_lpar_list.py @@ -237,7 +237,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -422,9 +422,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_lpar_messages.py b/plugins/modules/zhmc_lpar_messages.py index e8f6ac6ec..bf3efb16f 100644 --- a/plugins/modules/zhmc_lpar_messages.py +++ b/plugins/modules/zhmc_lpar_messages.py @@ -286,7 +286,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -448,9 +448,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_nic.py b/plugins/modules/zhmc_nic.py index d998cf9bd..c24dc802c 100644 --- a/plugins/modules/zhmc_nic.py +++ b/plugins/modules/zhmc_nic.py @@ -279,7 +279,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, wait_for_transition_completion, \ eq_hex, eq_mac, to_unicode, process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -753,9 +753,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_nic_list.py b/plugins/modules/zhmc_nic_list.py index 53ca2e06b..5abc20638 100644 --- a/plugins/modules/zhmc_nic_list.py +++ b/plugins/modules/zhmc_nic_list.py @@ -196,7 +196,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -302,9 +302,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_partition.py b/plugins/modules/zhmc_partition.py index 571afc9c1..a8f6bc342 100644 --- a/plugins/modules/zhmc_partition.py +++ b/plugins/modules/zhmc_partition.py @@ -414,6 +414,7 @@ "{property}": description: "Additional properties of the partition, as described in the data model of the 'Partition' object in the R(HMC API,HMC API) book. + Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book." type: raw hbas: @@ -625,7 +626,8 @@ hmc_auth_parameter, Error, ParameterError, StatusError, stop_partition, \ start_partition, wait_for_transition_completion, eq_hex, to_unicode, \ process_normal_property, missing_required_lib, ImageError, \ - common_fail_on_import_errors, pull_properties, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, pull_properties, parse_hmc_host, \ + blanked_params, removed_dict # noqa: E402 try: import urllib3 @@ -1042,6 +1044,11 @@ def required_type_ssc(partition_properties): False, []), } +# Write-only properties (blanked out in logs and removed in output) +WRITEONLY_PROPERTIES_USCORE = ['boot_ftp_password', 'ssc_master_pw'] +WRITEONLY_PROPERTIES_HYPHEN = [p.replace('_', '-') + for p in WRITEONLY_PROPERTIES_USCORE] + def storage_mgmt_enabled(cpc): """ @@ -1876,6 +1883,8 @@ def ensure_active(params, check_mode): add_artificial_properties( result, partition, expand_storage_groups, expand_crypto_adapters) + result = removed_dict(result, WRITEONLY_PROPERTIES_HYPHEN) + return changed, result finally: @@ -1974,6 +1983,8 @@ def ensure_stopped(params, check_mode): add_artificial_properties( result, partition, expand_storage_groups, expand_crypto_adapters) + result = removed_dict(result, WRITEONLY_PROPERTIES_HYPHEN) + return changed, result finally: @@ -2231,9 +2242,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params, WRITEONLY_PROPERTIES_USCORE)) try: diff --git a/plugins/modules/zhmc_partition_command.py b/plugins/modules/zhmc_partition_command.py index a3e6e0bc4..5b80dde09 100644 --- a/plugins/modules/zhmc_partition_command.py +++ b/plugins/modules/zhmc_partition_command.py @@ -211,7 +211,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, NotificationThread, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -425,9 +425,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = True try: diff --git a/plugins/modules/zhmc_partition_list.py b/plugins/modules/zhmc_partition_list.py index 21a270fc5..0ee503a4e 100644 --- a/plugins/modules/zhmc_partition_list.py +++ b/plugins/modules/zhmc_partition_list.py @@ -234,7 +234,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -415,9 +415,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_partition_messages.py b/plugins/modules/zhmc_partition_messages.py index 8b25af424..a4adf675a 100644 --- a/plugins/modules/zhmc_partition_messages.py +++ b/plugins/modules/zhmc_partition_messages.py @@ -261,7 +261,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -413,9 +413,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_password_rule.py b/plugins/modules/zhmc_password_rule.py index 4415bd861..dc09ad187 100644 --- a/plugins/modules/zhmc_password_rule.py +++ b/plugins/modules/zhmc_password_rule.py @@ -263,7 +263,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -671,9 +671,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_password_rule_list.py b/plugins/modules/zhmc_password_rule_list.py index ed4160245..6a93b9019 100644 --- a/plugins/modules/zhmc_password_rule_list.py +++ b/plugins/modules/zhmc_password_rule_list.py @@ -176,7 +176,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -266,9 +266,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_session.py b/plugins/modules/zhmc_session.py index 88f587e4f..69bb4f307 100644 --- a/plugins/modules/zhmc_session.py +++ b/plugins/modules/zhmc_session.py @@ -221,7 +221,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, \ - missing_required_lib, parse_hmc_host # noqa: E402 + missing_required_lib, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -353,9 +353,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) # We do not count session creation or deletion as a change changed = False diff --git a/plugins/modules/zhmc_storage_group.py b/plugins/modules/zhmc_storage_group.py index 50823f4b2..b02624336 100644 --- a/plugins/modules/zhmc_storage_group.py +++ b/plugins/modules/zhmc_storage_group.py @@ -580,7 +580,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -1142,9 +1142,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_storage_group_attachment.py b/plugins/modules/zhmc_storage_group_attachment.py index 3d9e75128..784ba872e 100644 --- a/plugins/modules/zhmc_storage_group_attachment.py +++ b/plugins/modules/zhmc_storage_group_attachment.py @@ -227,7 +227,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -455,9 +455,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_storage_volume.py b/plugins/modules/zhmc_storage_volume.py index 406c0d54b..3bdcbc9ab 100644 --- a/plugins/modules/zhmc_storage_volume.py +++ b/plugins/modules/zhmc_storage_volume.py @@ -278,7 +278,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, eq_hex, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -704,9 +704,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_user.py b/plugins/modules/zhmc_user.py index a66b19d0c..e5a85ad62 100644 --- a/plugins/modules/zhmc_user.py +++ b/plugins/modules/zhmc_user.py @@ -249,6 +249,7 @@ "{property}": description: "Additional properties of the user, as described in the data model of the 'User' object in the R(HMC API,HMC API) book. + Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book." type: raw user-role-names: @@ -324,6 +325,7 @@ description: "Properties of the LDAP server definition, as described in the data model of the 'LDAP Server Definition' object in the R(HMC API,HMC API) book. + Write-only properties in the data model are not included. The property names have hyphens (-) as described in that book." type: raw sample: @@ -380,8 +382,8 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host, BLANKED_OUT, \ - params_deepcopy # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params, \ + blanked_dict, removed_dict # noqa: E402 try: import urllib3 @@ -493,6 +495,11 @@ 'user_role_objects': (False, False, False, None, None, None), } +# Write-only properties (blanked out in logs and removed in output) +WRITEONLY_PROPERTIES_USCORE = ['password'] +WRITEONLY_PROPERTIES_HYPHEN = [p.replace('_', '-') + for p in WRITEONLY_PROPERTIES_USCORE] + def process_properties(console, user, params): """ @@ -986,14 +993,11 @@ def ensure_present(params, check_mode): raise AssertionError("Unexpected " "create_props: %r" % create_props) if update_props: - logged_props = dict(update_props) - if 'password' in logged_props: - # This is not a hard-coded password. Added # nosec to avoid - # generating false positive in bandit - logged_props['password'] = BLANKED_OUT # nosec - LOGGER.debug( - "Existing user %r needs to get properties updated: %r", - user_name, logged_props) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug( + "Existing user %r needs to get properties updated: %r", + user_name, + blanked_dict(update_props, WRITEONLY_PROPERTIES_USCORE)) if not check_mode: user.update_properties(update_props) # We refresh the properties after the update, in case an @@ -1030,10 +1034,7 @@ def ensure_present(params, check_mode): add_artificial_properties(result, console, user, expand) - if 'password' in result: - # This is not a hard-coded password. Added # nosec to avoid - # generating false positive in bandit - result['password'] = BLANKED_OUT # nosec + result = removed_dict(result, WRITEONLY_PROPERTIES_HYPHEN) return changed, result @@ -1168,16 +1169,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - # We need to deepcopy the input parameters, because the dict in which we - # blank out the password is at the second level. With a shallow copy, - # that would blank out the password in the original params. - _params = params_deepcopy(module.params) - del _params['hmc_auth'] - if _params['properties'] and 'password' in _params['properties']: - # This is not a hard-coded password. Added # nosec to avoid - # generating false positive in bandit - _params['properties']['password'] = BLANKED_OUT # nosec - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params, WRITEONLY_PROPERTIES_USCORE)) try: diff --git a/plugins/modules/zhmc_user_list.py b/plugins/modules/zhmc_user_list.py index 14e21f7cd..b4ddabbcd 100644 --- a/plugins/modules/zhmc_user_list.py +++ b/plugins/modules/zhmc_user_list.py @@ -181,7 +181,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -271,9 +271,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_user_pattern.py b/plugins/modules/zhmc_user_pattern.py index b1adb6b68..40e25762f 100644 --- a/plugins/modules/zhmc_user_pattern.py +++ b/plugins/modules/zhmc_user_pattern.py @@ -291,7 +291,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, underscore_properties, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -944,9 +944,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_user_pattern_list.py b/plugins/modules/zhmc_user_pattern_list.py index 1ce4178bc..f07a9cbea 100644 --- a/plugins/modules/zhmc_user_pattern_list.py +++ b/plugins/modules/zhmc_user_pattern_list.py @@ -197,7 +197,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ underscore_properties_list, common_fail_on_import_errors, \ - parse_hmc_host # noqa: E402 + parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -276,9 +276,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_user_role.py b/plugins/modules/zhmc_user_role.py index 1cef3f95a..f8fe049c9 100644 --- a/plugins/modules/zhmc_user_role.py +++ b/plugins/modules/zhmc_user_role.py @@ -452,7 +452,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, to_unicode, \ process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -1321,9 +1321,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_user_role_list.py b/plugins/modules/zhmc_user_role_list.py index 3aa7e32c6..b5d39eeb6 100644 --- a/plugins/modules/zhmc_user_role_list.py +++ b/plugins/modules/zhmc_user_role_list.py @@ -181,7 +181,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -271,9 +271,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) changed = False try: diff --git a/plugins/modules/zhmc_versions.py b/plugins/modules/zhmc_versions.py index 1591ed86d..05f101514 100644 --- a/plugins/modules/zhmc_versions.py +++ b/plugins/modules/zhmc_versions.py @@ -245,7 +245,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -368,9 +368,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/plugins/modules/zhmc_virtual_function.py b/plugins/modules/zhmc_virtual_function.py index dbe0b9667..a0c2ee299 100644 --- a/plugins/modules/zhmc_virtual_function.py +++ b/plugins/modules/zhmc_virtual_function.py @@ -241,7 +241,7 @@ from ..module_utils.common import log_init, open_session, close_session, \ hmc_auth_parameter, Error, ParameterError, wait_for_transition_completion, \ eq_hex, to_unicode, process_normal_property, missing_required_lib, \ - common_fail_on_import_errors, parse_hmc_host # noqa: E402 + common_fail_on_import_errors, parse_hmc_host, blanked_params # noqa: E402 try: import urllib3 @@ -605,9 +605,9 @@ def main(): module.params['hmc_host'] = parse_hmc_host(module.params['hmc_host']) - _params = dict(module.params) - del _params['hmc_auth'] - LOGGER.debug("Module entry: params: %r", _params) + if LOGGER.isEnabledFor(logging.DEBUG): + LOGGER.debug("Module entry: params: %r", + blanked_params(module.params)) try: diff --git a/requirements.txt b/requirements.txt index 21cabe8b4..80c3b15a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,7 +40,7 @@ requests>=2.32.2 pytz>=2019.1 # zhmcclient @ git+https://github.com/zhmcclient/python-zhmcclient.git@master -zhmcclient>=1.18.0 +zhmcclient>=1.18.2 # Indirect dependencies for install that are needed for some reason (must be consistent with minimum-constraints-install.txt) @@ -74,6 +74,8 @@ PyYAML>=6.0.2 python-dateutil>=2.8.2 # jsonschema -jsonschema>=4.18.1 +jsonschema>=4.18.2 +# TODO: Python 3.13 support requires pyo3-ffi fixing its issue with Python 3.13 +# (see https://github.com/PyO3/pyo3/issues/4038#issuecomment-2156363013) urllib3>=1.26.19 diff --git a/tests/end2end/test_zhmc_ldap_server_definition.py b/tests/end2end/test_zhmc_ldap_server_definition.py index 6b883a978..a2a80e035 100644 --- a/tests/end2end/test_zhmc_ldap_server_definition.py +++ b/tests/end2end/test_zhmc_ldap_server_definition.py @@ -57,7 +57,6 @@ 'search_distinguished_name': 'test_user{0}', } - # A standard test LDAP server definition, as the input properties for # LDAPServerDefinitionManager.create() (i.e. using dashes, and limited to valid # input parameters) @@ -67,6 +66,16 @@ 'primary-hostname-ipaddr': '10.11.12.13', 'search-distinguished-name': 'test_user{0}', } +STD_LSD_HMC_EXP_PROPS = dict(STD_LSD_HMC_INPUT_PROPS) + +# LDAP server definition with bind data +STD_LSD_MODULE_INPUT_PROPS_WITH_BIND = dict(STD_LSD_MODULE_INPUT_PROPS) +STD_LSD_MODULE_INPUT_PROPS_WITH_BIND['bind_password'] = "Bumerang9x" +STD_LSD_MODULE_INPUT_PROPS_WITH_BIND['bind_distinguished_name'] = "bind_dn" +STD_LSD_HMC_INPUT_PROPS_WITH_BIND = dict(STD_LSD_HMC_INPUT_PROPS) +STD_LSD_HMC_INPUT_PROPS_WITH_BIND['bind-distinguished-name'] = "bind_dn" +STD_LSD_HMC_EXP_PROPS_WITH_BIND = dict(STD_LSD_HMC_INPUT_PROPS_WITH_BIND) +STD_LSD_HMC_INPUT_PROPS_WITH_BIND['bind-password'] = "Bumerang9x" def new_lsd_name(): @@ -135,6 +144,10 @@ def assert_lsd_props(lsd_props, exp_lsd_props, where): where_prop = where + f", property {prop_name!r}" assert act_value == exp_value, where_prop + # Assert that none of the write-only properties is in the output object + for prop_name in zhmc_ldap_server_definition.WRITEONLY_PROPERTIES_HYPHEN: + assert prop_name not in lsd_props, where + @pytest.mark.parametrize( "diff_case", [ @@ -220,11 +233,19 @@ def test_zhmc_lsd_facts( # - exp_changed (bool): Boolean for expected 'changed' flag. ( - "Present with non-existing LDAP server definition", + "Present with non-existing LDAP server definition, without password", None, 'present', STD_LSD_MODULE_INPUT_PROPS, - STD_LSD_HMC_INPUT_PROPS, + STD_LSD_HMC_EXP_PROPS, + True, + ), + ( + "Present with non-existing LDAP server definition, with password", + None, + 'present', + STD_LSD_MODULE_INPUT_PROPS_WITH_BIND, + STD_LSD_HMC_EXP_PROPS_WITH_BIND, True, ), ( @@ -232,7 +253,7 @@ def test_zhmc_lsd_facts( {}, 'present', None, - STD_LSD_HMC_INPUT_PROPS, + STD_LSD_HMC_EXP_PROPS, False, ), ( @@ -242,7 +263,18 @@ def test_zhmc_lsd_facts( }, 'present', STD_LSD_MODULE_INPUT_PROPS, - STD_LSD_HMC_INPUT_PROPS, + STD_LSD_HMC_EXP_PROPS, + True, + ), + ( + "Present with existing LDAP server definition, some properties " + "changed, with password", + { + 'description': 'bla', + }, + 'present', + STD_LSD_MODULE_INPUT_PROPS_WITH_BIND, + STD_LSD_HMC_EXP_PROPS_WITH_BIND, True, ), ( diff --git a/tests/end2end/test_zhmc_lpar.py b/tests/end2end/test_zhmc_lpar.py index 53b18adb4..9540c0856 100644 --- a/tests/end2end/test_zhmc_lpar.py +++ b/tests/end2end/test_zhmc_lpar.py @@ -172,6 +172,10 @@ def assert_lpar_props(act_props, exp_props, where): f"Actual: {act_value!r}") assert act_value == exp_value, where_prop + # Assert that none of the write-only properties is in the output object + for prop_name in zhmc_lpar.WRITEONLY_PROPERTIES_HYPHEN: + assert prop_name not in act_props, where + def ensure_lpar_status(logger, lpar, iap, status): """ diff --git a/tests/end2end/test_zhmc_partition.py b/tests/end2end/test_zhmc_partition.py index f6568dea1..f4791d6d5 100644 --- a/tests/end2end/test_zhmc_partition.py +++ b/tests/end2end/test_zhmc_partition.py @@ -115,6 +115,7 @@ 'initial-memory': MIN_MEMORY, 'maximum-memory': MIN_MEMORY, } +STD_LINUX_PARTITION_HMC_EXP_PROPS = dict(STD_LINUX_PARTITION_HMC_INPUT_PROPS) STD_SSC_PARTITION_HMC_INPUT_PROPS = { # 'name': updated upon use 'description': "zhmc test partition", @@ -126,6 +127,8 @@ 'ssc-master-userid': 'sscuser', 'ssc-master-pw': 'Need2ChangeSoon!', } +STD_SSC_PARTITION_HMC_EXP_PROPS = dict(STD_SSC_PARTITION_HMC_INPUT_PROPS) +del STD_SSC_PARTITION_HMC_EXP_PROPS['ssc-master-pw'] def storage_mgmt_enabled(cpc): @@ -499,6 +502,10 @@ def assert_partition_props(act_props, exp_props, where): exp_boot_sv_name = exp_props['boot-storage-volume-name'] assert act_props['boot-storage-volume-name'] == exp_boot_sv_name + # Assert that none of the write-only properties is in the output object + for prop_name in zhmc_partition.WRITEONLY_PROPERTIES_HYPHEN: + assert prop_name not in act_props, where + PARTITION_FACTS_TESTCASES = [ # The list items are tuples with the following items: @@ -727,7 +734,7 @@ def test_zhmc_partition_facts( 'stopped', STD_LINUX_PARTITION_MODULE_INPUT_PROPS, None, - STD_LINUX_PARTITION_HMC_INPUT_PROPS, + STD_LINUX_PARTITION_HMC_EXP_PROPS, True, True, ), @@ -739,7 +746,7 @@ def test_zhmc_partition_facts( 'stopped', None, None, - STD_LINUX_PARTITION_HMC_INPUT_PROPS, + STD_LINUX_PARTITION_HMC_EXP_PROPS, False, True, ), @@ -751,7 +758,7 @@ def test_zhmc_partition_facts( 'stopped', None, None, - STD_LINUX_PARTITION_HMC_INPUT_PROPS, + STD_LINUX_PARTITION_HMC_EXP_PROPS, True, True, ), @@ -791,7 +798,7 @@ def test_zhmc_partition_facts( 'stopped', STD_LINUX_PARTITION_MODULE_INPUT_PROPS, None, - STD_LINUX_PARTITION_HMC_INPUT_PROPS, + STD_LINUX_PARTITION_HMC_EXP_PROPS, True, True, ), @@ -802,7 +809,7 @@ def test_zhmc_partition_facts( 'stopped', STD_SSC_PARTITION_MODULE_INPUT_PROPS, None, - STD_SSC_PARTITION_HMC_INPUT_PROPS, + STD_SSC_PARTITION_HMC_EXP_PROPS, True, True, ), @@ -818,7 +825,7 @@ def test_zhmc_partition_facts( 'stopped', None, None, - STD_SSC_PARTITION_HMC_INPUT_PROPS, + STD_SSC_PARTITION_HMC_EXP_PROPS, False, True, ), @@ -830,7 +837,7 @@ def test_zhmc_partition_facts( 'active', None, None, # Code ignores "HTTPError: 409,131" - STD_SSC_PARTITION_HMC_INPUT_PROPS, + STD_SSC_PARTITION_HMC_EXP_PROPS, True, True, ), @@ -844,7 +851,7 @@ def test_zhmc_partition_facts( 'stopped', STD_SSC_PARTITION_MODULE_INPUT_PROPS, None, - STD_SSC_PARTITION_HMC_INPUT_PROPS, + STD_SSC_PARTITION_HMC_EXP_PROPS, True, True, ), diff --git a/tests/end2end/test_zhmc_user.py b/tests/end2end/test_zhmc_user.py index e613b33d2..1cc086089 100644 --- a/tests/end2end/test_zhmc_user.py +++ b/tests/end2end/test_zhmc_user.py @@ -224,6 +224,10 @@ def assert_user_props(user_props, expand, where): if expand: assert 'user-role-objects' in user_props, where + # Assert that none of the write-only properties is in the output object + for prop_name in zhmc_user.WRITEONLY_PROPERTIES_HYPHEN: + assert prop_name not in user_props, where + @pytest.mark.parametrize( "check_mode", [ diff --git a/tests/unit/test_common.py b/tests/unit/test_common.py index 2ac248fce..20cb1af0d 100644 --- a/tests/unit/test_common.py +++ b/tests/unit/test_common.py @@ -21,6 +21,7 @@ __metaclass__ = type import re +from copy import deepcopy from collections.abc import Sequence, Mapping, Set from types import ModuleType import pytest @@ -548,3 +549,282 @@ def test_common_params_deepcopy(desc, in_params): params = common.params_deepcopy(in_params) assert_disparate_equal(params, in_params) + + +COMMON_BLANKED_PARAMS_TESTCASES = [ + # Testcases for test_common_blanked_params() + # The list items are tuples with the following items: + # - desc (string): description of the testcase. + # - in_params (dict): Input params. Must not be None + # - blanked_properties (list): Properties to be blanked out, may be None + + ( + "Params without hmc_auth or properties", + { + 'state': 'bla', + }, + None + ), + ( + "Params with hmc_auth that does not have sensitive items", + { + 'state': 'bla', + 'hmc_auth': {'userid': 'user'}, + }, + None + ), + ( + "Params with hmc_auth that does have all sensitive items", + { + 'state': 'bla', + 'hmc_auth': { + 'userid': 'user', + 'password': 'pass', + 'session_id': 'sid', + }, + }, + None + ), + ( + "Params with properties but without blanked props specified", + { + 'state': 'bla', + 'properties': { + 'name': 'foo', + }, + }, + None + ), + ( + "Params with properties being None", + { + 'state': 'bla', + 'properties': None, + }, + None + ), + ( + "Params with properties with different blanked props specified", + { + 'state': 'bla', + 'properties': { + 'name': 'foo', + }, + }, + ['password'] + ), + ( + "Params with properties with one blanked props specified", + { + 'state': 'bla', + 'properties': { + 'name': 'foo', + 'password': 'pass', + }, + }, + ['password'] + ), + ( + "Params with properties with one blanked prop and excess blanked prop", + { + 'state': 'bla', + 'properties': { + 'name': 'foo', + 'password': 'pass', + }, + }, + ['password', 'extra'] + ), +] + +BLANKED_PROPS_HMC_AUTH = ['password', 'session_id'] + + +@pytest.mark.parametrize( + "desc, in_params, blanked_properties", + COMMON_BLANKED_PARAMS_TESTCASES) +def test_common_blanked_params(desc, in_params, blanked_properties): + # pylint: disable=unused-argument + """ + Test the blanked_params() function. + """ + + saved_params = common.params_deepcopy(in_params) + + # The code to be tested + act_params = common.blanked_params(in_params, blanked_properties) + + # Check that the input parameters have not been changed + assert_disparate_equal(in_params, saved_params) + + # Check that all dict items are still there. + in_keys = sorted(in_params.keys()) + act_keys = sorted(act_params.keys()) + assert act_keys == in_keys + + # Check that the 'hmc_auth' item, if present, has its sensitive items + # blanked out. + if 'hmc_auth' in act_params: + hmc_auth = act_params['hmc_auth'] + for pname in BLANKED_PROPS_HMC_AUTH: + if pname in hmc_auth: + assert hmc_auth[pname] == common.BLANKED_OUT + + # Check that the 'properties' item, if present, has its sensitive items + # blanked out. + if 'properties' in act_params and act_params['properties'] and \ + blanked_properties: + properties = act_params['properties'] + for pname in blanked_properties: + if pname in properties: + assert properties[pname] == common.BLANKED_OUT + + +COMMON_BLANKED_DICT_TESTCASES = [ + # Testcases for test_common_blanked_dict() + # The list items are tuples with the following items: + # - desc (string): description of the testcase. + # - in_dict (dict): Input dict. Must not be None + # - blanked_properties (list): Prop to be blanked out. Must not be None + + ( + "Empty dict and empty blanked props", + {}, + [] + ), + ( + "Empty dict and one excess blanked prop", + {}, + ['foo'] + ), + ( + "Dict and empty blanked props", + { + 'p1': 'v1', + 'p2': None, + }, + [] + ), + ( + "Dict and one matching blanked prop", + { + 'p1': 'v1', + 'p2': None, + }, + ['p1'] + ), + ( + "Dict and one matching and one excess blanked prop", + { + 'p1': 'v1', + 'p2': None, + }, + ['p1', 'foo'] + ), +] + + +@pytest.mark.parametrize( + "desc, in_dict, blanked_properties", + COMMON_BLANKED_DICT_TESTCASES) +def test_common_blanked_dict(desc, in_dict, blanked_properties): + # pylint: disable=unused-argument + """ + Test the blanked_dict() function. + """ + + saved_dict = deepcopy(in_dict) + + # The code to be tested + act_dict = common.blanked_dict(in_dict, blanked_properties) + + # Check that the input dict has not been changed + assert_disparate_equal(in_dict, saved_dict) + + # Check that the returned dict did not get additional keys + for pname in act_dict.keys(): + assert pname in in_dict + + # Check that the specified items have been blanked out, and that all other + # items have unchanged values. + for pname, in_value in in_dict.items(): + assert pname in act_dict + act_value = act_dict[pname] + if pname in blanked_properties: + assert act_value == common.BLANKED_OUT + else: + assert act_value == in_value + + +COMMON_REMOVED_DICT_TESTCASES = [ + # Testcases for test_common_removed_dict() + # The list items are tuples with the following items: + # - desc (string): description of the testcase. + # - in_dict (dict): Input dict. Must not be None + # - removed_properties (list): Properties to be removed. Must not be None + + ( + "Empty dict and empty removed props", + {}, + [] + ), + ( + "Empty dict and one excess removed prop", + {}, + ['foo'] + ), + ( + "Dict and empty removed props", + { + 'p1': 'v1', + 'p2': None, + }, + [] + ), + ( + "Dict and one matching removed prop", + { + 'p1': 'v1', + 'p2': None, + }, + ['p1'] + ), + ( + "Dict and one matching and one excess removed prop", + { + 'p1': 'v1', + 'p2': None, + }, + ['p1', 'foo'] + ), +] + + +@pytest.mark.parametrize( + "desc, in_dict, removed_properties", + COMMON_REMOVED_DICT_TESTCASES) +def test_common_removed_dict(desc, in_dict, removed_properties): + # pylint: disable=unused-argument + """ + Test the removed_dict() function. + """ + + saved_dict = deepcopy(in_dict) + + # The code to be tested + act_dict = common.removed_dict(in_dict, removed_properties) + + # Check that the input dict has not been changed + assert_disparate_equal(in_dict, saved_dict) + + # Check that the returned dict did not get additional keys + for pname in act_dict.keys(): + assert pname in in_dict + + # Check that the specified items have been removed, and that all other + # items have unchanged values. + for pname, in_value in in_dict.items(): + if pname in removed_properties: + assert pname not in act_dict + else: + assert act_dict[pname] == in_value