From 6e040e14964626842dca0429190841942c2ab60c Mon Sep 17 00:00:00 2001 From: "Jorge Rodriguez (A.K.A. Tiriel)" Date: Mon, 21 Sep 2020 07:39:08 +0300 Subject: [PATCH] Add test to verify TLS requirements are removed (#26) * Add test to verify TLS requirements are removed * Fix cursor parsing * small fixes * Refactor TLS tests into their own file * Fix TLS requirements parsing --- plugins/modules/mysql_user.py | 20 +- .../targets/test_mysql_user/tasks/main.yml | 162 +-------------- .../tasks/tls_requirements.yml | 195 ++++++++++++++++++ 3 files changed, 213 insertions(+), 164 deletions(-) create mode 100644 tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml diff --git a/plugins/modules/mysql_user.py b/plugins/modules/mysql_user.py index 9b1aca8..8d6c36a 100644 --- a/plugins/modules/mysql_user.py +++ b/plugins/modules/mysql_user.py @@ -210,6 +210,11 @@ EXAMPLES = r''' subject: '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' cipher: 'ECDHE-ECDSA-AES256-SHA384' +- name: Modify user to no longer require SSL. + community.mysql.mysql_user: + name: bob + tls_requires: + - name: Ensure no user named 'sally'@'localhost' exists, also passing in the auth credentials. community.mysql.mysql_user: login_user: root @@ -424,12 +429,16 @@ def get_tls_requires(cursor, user, host): query = "SHOW GRANTS for '%s'@'%s'" % (user, host) cursor.execute(query) - require_list = list(filter(lambda x: "REQUIRE" in x, cursor.fetchall())) + require_list = [tuple[0] for tuple in filter(lambda x: "REQUIRE" in x[0], cursor.fetchall())] require_line = require_list[0] if require_list else "" pattern = r"(?<=\bREQUIRE\b)(.*?)(?=(?:\bPASSWORD\b|$))" requires_match = re.search(pattern, require_line) requires = requires_match.group().strip() if requires_match else "" - if len(requires.split()) > 1: + if any((requires.startswith(req) for req in ('SSL', 'X509', 'NONE'))): + requires = requires.split()[0] + if requires == 'NONE': + requires = None + else: import shlex items = iter(shlex.split(requires)) @@ -514,6 +523,9 @@ def user_mod(cursor, user, host, host_all, password, encrypted, msg = "User unchanged" grant_option = False + # Determine what user management method server uses + old_user_mgmt = use_old_user_mgmt(cursor) + if host_all: hostnames = user_get_hostnames(cursor, [user]) else: @@ -522,8 +534,6 @@ def user_mod(cursor, user, host, host_all, password, encrypted, for host in hostnames: # Handle clear text and hashed passwords. if bool(password): - # Determine what user management method server uses - old_user_mgmt = use_old_user_mgmt(cursor) # Get a list of valid columns in mysql.user table to check if Password and/or authentication_string exist cursor.execute(""" @@ -675,7 +685,7 @@ def user_mod(cursor, user, host, host_all, password, encrypted, query = " ".join((pre_query, "%s@%s")) cursor.execute(*mogrify_requires(query, (user, host), tls_requires)) else: - query = " ".join(pre_query, "%s@%s REQUIRE NONE") + query = " ".join((pre_query, "%s@%s REQUIRE NONE")) cursor.execute(query, (user, host)) changed = True diff --git a/tests/integration/targets/test_mysql_user/tasks/main.yml b/tests/integration/targets/test_mysql_user/tasks/main.yml index 0b06ace..a9ca8a5 100644 --- a/tests/integration/targets/test_mysql_user/tasks/main.yml +++ b/tests/integration/targets/test_mysql_user/tasks/main.yml @@ -129,165 +129,6 @@ - include: assert_no_user.yml user_name={{user_name_2}} - # ============================================================ - # Create users with TLS requirements and verify requirements are assigned - # - - name: find out the database version - mysql_info: - <<: *mysql_params - filter: version - register: db_version - - - name: create user with TLS requirements in check mode (expect changed=true) - mysql_user: - <<: *mysql_params - name: "{{ user_name_1 }}" - password: "{{ user_password_1 }}" - tls_requires: - SSL: - check_mode: yes - register: result - - - name: Assert check mode user create reports changed state - assert: - that: - - result is changed - - - include: assert_no_user.yml user_name={{user_name_1}} - - - name: create user with TLS requirements state=present (expect changed=true) - mysql_user: - <<: *mysql_params - name: '{{ item[0] }}' - password: '{{ user_password_1 }}' - tls_requires: '{{ item[1] }}' - with_together: - - [ '{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] - - - - SSL: - - X509: - - subject: '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - cipher: 'ECDHE-ECDSA-AES256-SHA384' - issuer: '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' - - - block: - - name: retrieve TLS requiremets for users in old database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ item }}'@'localhost'\"" - register: old_result - with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] - - - name: set old database separator - set_fact: - separator: '\n' - # Semantically: when mysql version <= 5.6 or MariaDB version <= 10.1 - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - - - block: - - name: retrieve TLS requiremets for users in new database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ item }}'@'localhost'\"" - register: new_result - with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] - - - name: set new database separator - set_fact: - separator: 'PASSWORD' - # Semantically: when mysql version >= 5.7 or MariaDB version >= 10.2 - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 - - - block: - - name: assert user1 TLS requirements - assert: - that: - - "'SSL' in reqs" - vars: - - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_1) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - - name: assert user2 TLS requirements - assert: - that: - - "'X509' in reqs" - vars: - - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_2) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - - name: assert user3 TLS requirements - assert: - that: - - "'/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'SUBJECT') | first)" - - "'/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'ISSUER') | first)" - - "'ECDHE-ECDSA-AES256-SHA384' in (reqs | select('contains', 'CIPHER') | first)" - vars: - - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_3) | first).stdout.split('REQUIRE')[1].split(separator)[0].replace(\"' \", \"':\").split(\":\")}}" - # CentOS 6 uses an older version of jinja that does not provide the selectattr filter. - when: ansible_distribution != 'CentOS' or ansible_distribution_major_version != '6' - - - name: modify user with TLS requirements state=present in check mode (expect changed=true) - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' - tls_requires: - X509: - check_mode: yes - register: result - - - name: Assert check mode user update reports changed state - assert: - that: - - result is changed - - - name: retrieve TLS requiremets for users in old database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" - register: old_result - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - - - name: retrieve TLS requiremets for users in new database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" - register: new_result - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 - - - name: assert user1 TLS requirements was not changed - assert: - that: "'SSL' in reqs" - vars: - - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - - name: modify user with TLS requirements state=present (expect changed=true) - mysql_user: - <<: *mysql_params - name: '{{ user_name_1 }}' - password: '{{ user_password_1 }}' - tls_requires: - X509: - - - name: retrieve TLS requiremets for users in old database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" - register: old_result - when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 - - - name: retrieve TLS requiremets for users in new database version - command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" - register: new_result - when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 - - - name: assert user1 TLS requirements - assert: - that: "'X509' in reqs" - vars: - - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" - - - - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} - - - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }} - - - include: remove_user.yml user_name={{user_name_3}} user_password={{ user_password_1 }} - - - include: assert_no_user.yml user_name={{user_name_1}} - - - include: assert_no_user.yml user_name={{user_name_2}} - - - include: assert_no_user.yml user_name={{user_name_3}} - # ============================================================ # Assert user has access to multiple databases # @@ -393,6 +234,9 @@ # Tests for the priv parameter with dict value (https://github.com/ansible/ansible/issues/57533) - include: test_priv_dict.yml + # Tests for the TLS requires dictionary + - include: tls_requirements.yml + - import_tasks: issue-29511.yaml tags: - issue-29511 diff --git a/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml b/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml new file mode 100644 index 0000000..8de9401 --- /dev/null +++ b/tests/integration/targets/test_mysql_user/tasks/tls_requirements.yml @@ -0,0 +1,195 @@ +--- +- vars: + mysql_parameters: &mysql_params + login_user: '{{ mysql_user }}' + login_password: '{{ mysql_password }}' + login_host: 127.0.0.1 + login_port: '{{ mysql_primary_port }}' + + block: + + # ============================================================ + - name: find out the database version + mysql_info: + <<: *mysql_params + filter: version + register: db_version + + - name: Drop mysql user {{ item }} if exists + mysql_user: + <<: *mysql_params + name: '{{ item }}' + state: absent + with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + + - name: create user with TLS requirements in check mode (expect changed=true) + mysql_user: + <<: *mysql_params + name: "{{ user_name_1 }}" + password: "{{ user_password_1 }}" + tls_requires: + SSL: + check_mode: yes + register: result + + - name: Assert check mode user create reports changed state + assert: + that: + - result is changed + + - include: assert_no_user.yml user_name={{user_name_1}} + + - name: create user with TLS requirements state=present (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ item[0] }}' + password: '{{ user_password_1 }}' + tls_requires: '{{ item[1] }}' + with_together: + - [ '{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + - + - SSL: + - X509: + - subject: '/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' + cipher: 'ECDHE-ECDSA-AES256-SHA384' + issuer: '/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' + + - block: + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ item }}'@'localhost'\"" + register: old_result + with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + + - name: set old database separator + set_fact: + separator: '\n' + # Semantically: when mysql version <= 5.6 or MariaDB version <= 10.1 + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - block: + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ item }}'@'localhost'\"" + register: new_result + with_items: ['{{ user_name_1 }}', '{{ user_name_2 }}', '{{ user_name_3 }}'] + + - name: set new database separator + set_fact: + separator: 'PASSWORD' + # Semantically: when mysql version >= 5.7 or MariaDB version >= 10.2 + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - block: + - name: assert user1 TLS requirements + assert: + that: + - "'SSL' in reqs" + vars: + - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_1) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: assert user2 TLS requirements + assert: + that: + - "'X509' in reqs" + vars: + - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_2) | first).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: assert user3 TLS requirements + assert: + that: + - "'/CN=alice/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'SUBJECT') | first)" + - "'/CN=org/O=MyDom, Inc./C=US/ST=Oregon/L=Portland' in (reqs | select('contains', 'ISSUER') | first)" + - "'ECDHE-ECDSA-AES256-SHA384' in (reqs | select('contains', 'CIPHER') | first)" + vars: + - reqs: "{{((old_result.results[0] is skipped | ternary(new_result, old_result)).results | selectattr('item', 'contains', user_name_3) | first).stdout.split('REQUIRE')[1].split(separator)[0].replace(\"' \", \"':\").split(\":\")}}" + # CentOS 6 uses an older version of jinja that does not provide the selectattr filter. + when: ansible_distribution != 'CentOS' or ansible_distribution_major_version != '6' + + - name: modify user with TLS requirements state=present in check mode (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + tls_requires: + X509: + check_mode: yes + register: result + + - name: Assert check mode user update reports changed state + assert: + that: + - result is changed + + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" + register: old_result + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" + register: new_result + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - name: assert user1 TLS requirements was not changed + assert: + that: "'SSL' in reqs" + vars: + - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: modify user with TLS requirements state=present (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + tls_requires: + X509: + + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" + register: old_result + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" + register: new_result + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - name: assert user1 TLS requirements + assert: + that: "'X509' in reqs" + vars: + - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip()}}" + + - name: remove TLS requiremets from user (expect changed=true) + mysql_user: + <<: *mysql_params + name: '{{ user_name_1 }}' + password: '{{ user_password_1 }}' + tls_requires: + + - name: retrieve TLS requiremets for users in old database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW GRANTS for '{{ user_name_1 }}'@'localhost'\"" + register: old_result + when: db_version.version.major <= 5 and db_version.version.minor <= 6 or db_version.version.major == 10 and db_version.version.minor < 2 + + - name: retrieve TLS requiremets for users in new database version + command: "{{ mysql_command }} -L -N -s -e \"SHOW CREATE USER '{{ user_name_1 }}'@'localhost'\"" + register: new_result + when: db_version.version.major == 5 and db_version.version.minor >= 7 or db_version.version.major > 5 and db_version.version.major < 10 or db_version.version.major == 10 and db_version.version.minor >= 2 + + - name: assert user1 TLS requirements + assert: + that: "'NONE' in reqs" + vars: + - reqs: "{{(old_result is skipped | ternary(new_result, old_result)).stdout.split('REQUIRE')[1].split(separator)[0].strip() | default('NONE') }}" + + - include: remove_user.yml user_name={{user_name_1}} user_password={{ user_password_1 }} + + - include: remove_user.yml user_name={{user_name_2}} user_password={{ user_password_1 }} + + - include: remove_user.yml user_name={{user_name_3}} user_password={{ user_password_1 }} + + - include: assert_no_user.yml user_name={{user_name_1}} + + - include: assert_no_user.yml user_name={{user_name_2}} + + - include: assert_no_user.yml user_name={{user_name_3}}