From c2bf9ea9fb7576d4df6e8dad84f57a1668340df8 Mon Sep 17 00:00:00 2001 From: Andrew Klychkov Date: Tue, 2 Jun 2020 09:17:54 +0300 Subject: [PATCH] Mysql db unsafe passwd (#428) * mysql_db: add new parameter unsafe_login_password * add CI tests * add changelog fragment --- ...sql_db_add_unsafe_login_password_param.yml | 2 ++ plugins/modules/database/mysql/mysql_db.py | 30 +++++++++++++++---- .../mysql_db/tasks/state_dump_import.yml | 18 ++++++++++- 3 files changed, 43 insertions(+), 7 deletions(-) create mode 100644 changelogs/fragments/428-mysql_db_add_unsafe_login_password_param.yml diff --git a/changelogs/fragments/428-mysql_db_add_unsafe_login_password_param.yml b/changelogs/fragments/428-mysql_db_add_unsafe_login_password_param.yml new file mode 100644 index 0000000000..887e061d2f --- /dev/null +++ b/changelogs/fragments/428-mysql_db_add_unsafe_login_password_param.yml @@ -0,0 +1,2 @@ +minor_changes: +- mysql_db - add the ``unsafe_login_password`` parameter (https://github.com/ansible/ansible/issues/63955). diff --git a/plugins/modules/database/mysql/mysql_db.py b/plugins/modules/database/mysql/mysql_db.py index f0a3ecbfe5..8fab6872b4 100644 --- a/plugins/modules/database/mysql/mysql_db.py +++ b/plugins/modules/database/mysql/mysql_db.py @@ -113,6 +113,15 @@ options: required: no type: bool default: no + unsafe_login_password: + description: + - If C(no), the module will safely use a shell-escaped version of the I(login_password) value. + - It makes sense to use C(yes) only if there are special symbols in the value and errors C(Access denied) occur. + - Used only when I(state) is C(import) or C(dump) and I(login_password) is passed, ignored otherwise. + type: bool + default: no + version_added: '2.10' + seealso: - module: mysql_info - module: mysql_variables @@ -299,7 +308,8 @@ def db_delete(cursor, db): def db_dump(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, single_transaction=None, quick=None, ignore_tables=None, hex_blob=None, - encoding=None, force=False, master_data=0, skip_lock_tables=False, dump_extra_args=None): + encoding=None, force=False, master_data=0, skip_lock_tables=False, + dump_extra_args=None, unsafe_password=False): cmd = module.get_bin_path('mysqldump', True) # If defined, mysqldump demands --defaults-extra-file be the first option if config_file: @@ -307,7 +317,10 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, if user is not None: cmd += " --user=%s" % shlex_quote(user) if password is not None: - cmd += " --password=%s" % shlex_quote(password) + if not unsafe_password: + cmd += " --password=%s" % shlex_quote(password) + else: + cmd += " --password=%s" % password if ssl_cert is not None: cmd += " --ssl-cert=%s" % shlex_quote(ssl_cert) if ssl_key is not None: @@ -366,7 +379,7 @@ def db_dump(module, host, user, password, db_name, target, all_databases, port, def db_import(module, host, user, password, db_name, target, all_databases, port, config_file, socket=None, ssl_cert=None, ssl_key=None, ssl_ca=None, encoding=None, force=False, - use_shell=False): + use_shell=False, unsafe_password=False): if not os.path.exists(target): return module.fail_json(msg="target %s does not exist on the host" % target) @@ -377,7 +390,10 @@ def db_import(module, host, user, password, db_name, target, all_databases, port if user: cmd.append("--user=%s" % shlex_quote(user)) if password: - cmd.append("--password=%s" % shlex_quote(password)) + if not unsafe_password: + cmd.append("--password=%s" % shlex_quote(password)) + else: + cmd.append("--password=%s" % password) if ssl_cert is not None: cmd.append("--ssl-cert=%s" % shlex_quote(ssl_cert)) if ssl_key is not None: @@ -492,6 +508,7 @@ def main(): skip_lock_tables=dict(type='bool', default=False), dump_extra_args=dict(type='str'), use_shell=dict(type='bool', default=False), + unsafe_login_password=dict(type='bool', default=False), ), supports_check_mode=True, ) @@ -518,6 +535,7 @@ def main(): connect_timeout = module.params['connect_timeout'] config_file = module.params['config_file'] login_password = module.params["login_password"] + unsafe_login_password = module.params["unsafe_login_password"] login_user = module.params["login_user"] login_host = module.params["login_host"] ignore_tables = module.params["ignore_tables"] @@ -599,7 +617,7 @@ def main(): login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca, single_transaction, quick, ignore_tables, hex_blob, encoding, force, master_data, skip_lock_tables, - dump_extra_args) + dump_extra_args, unsafe_login_password) if rc != 0: module.fail_json(msg="%s" % stderr) module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout, @@ -618,7 +636,7 @@ def main(): all_databases, login_port, config_file, socket, ssl_cert, ssl_key, ssl_ca, - encoding, force, use_shell) + encoding, force, use_shell, unsafe_login_password) if rc != 0: module.fail_json(msg="%s" % stderr) module.exit_json(changed=True, db=db_name, db_list=db, msg=stdout, diff --git a/tests/integration/targets/mysql_db/tasks/state_dump_import.yml b/tests/integration/targets/mysql_db/tasks/state_dump_import.yml index 475aed9ad6..cad6fcc940 100644 --- a/tests/integration/targets/mysql_db/tasks/state_dump_import.yml +++ b/tests/integration/targets/mysql_db/tasks/state_dump_import.yml @@ -22,6 +22,16 @@ wrong_sql_file="{{tmp_dir}}/wrong.sql" dump_file1="{{tmp_dir}}/{{file2}}" dump_file2="{{tmp_dir}}/{{file3}}" + db_user="test" + db_user_unsafe_password="pass!word" + +- name: create user for test unsafe_login_password parameter + mysql_user: + name: '{{ db_user }}' + password: '{{ db_user_unsafe_password }}' + priv: '*.*:ALL' + state: present + login_unix_socket: '{{ mysql_socket }}' - name: state dump/import - create database mysql_db: @@ -58,6 +68,9 @@ - name: state dump without department table. mysql_db: + login_user: '{{ db_user }}' + login_password: '{{ db_user_unsafe_password }}' + unsafe_login_password: yes name: "{{ db_name }}" state: dump target: "{{ db_file_name }}" @@ -74,7 +87,7 @@ assert: that: - result is changed - - result.executed_commands[0] is search("mysqldump --force --socket={{ mysql_socket }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers") + - result.executed_commands[0] is search("mysqldump --user={{ db_user }} --password=\*\*\*\*\*\*\*\* --force --socket={{ mysql_socket }} {{ db_name }} --skip-lock-tables --quick --ignore-table={{ db_name }}.department --master-data=1 --skip-triggers") - name: state dump/import - file name should exist file: name={{ db_file_name }} state=file @@ -153,6 +166,9 @@ - name: test state=import to restore the database of type {{ format_type }} (expect changed=true) mysql_db: + login_user: '{{ db_user }}' + login_password: '{{ db_user_unsafe_password }}' + unsafe_login_password: yes name: '{{ db_name }}' state: import target: '{{ db_file_name }}'