Fix salt rabbitmq module incompatibility with rabbitmq-server 3.8+
It was an issue with check regex against newer RabbitMQ version in
check_password function in rabbitmq module.
Now it uses rabbitmq_custom module instead of rabbitmq for
check_password function.
PROD-35125
Change-Id: Icead56cbc0ae9f52e53f3978636a0d687dd92942
diff --git a/_modules/rabbitmq_custom.py b/_modules/rabbitmq_custom.py
new file mode 100644
index 0000000..182ca49
--- /dev/null
+++ b/_modules/rabbitmq_custom.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# This file is copy of original /usr/lib/python2.7/dist-packages/salt/modules/rabbitmq.py
+# with fix applied for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+# It fix native check_password function by override it here and call it from
+# _states/rabbitmq_user_common.py which also introduced in this patch.
+# Fix source: https://github.com/saltstack/salt/commit/d0e0ed6b60f4de6d2b2551cad2fc7a553efe0274
+
+from __future__ import absolute_import
+
+# Import python libs
+import json
+import re
+import logging
+import os
+import os.path
+import random
+import string
+
+# Import salt libs
+import salt.utils
+import salt.utils.itertools
+import salt.ext.six as six
+from salt.exceptions import SaltInvocationError
+from salt.ext.six.moves import range
+from salt.exceptions import CommandExecutionError
+
+log = logging.getLogger(__name__)
+
+RABBITMQCTL = None
+RABBITMQ_PLUGINS = None
+
+
+def __virtual__():
+ '''
+ Verify RabbitMQ is installed.
+ '''
+ global RABBITMQCTL
+ global RABBITMQ_PLUGINS
+
+ if salt.utils.is_windows():
+ from salt.ext.six.moves import winreg
+ key = None
+ try:
+ key = winreg.OpenKeyEx(
+ winreg.HKEY_LOCAL_MACHINE,
+ 'SOFTWARE\\VMware, Inc.\\RabbitMQ Server',
+ 0,
+ winreg.KEY_READ | winreg.KEY_WOW64_32KEY
+ )
+ (dir_path, value_type) = winreg.QueryValueEx(
+ key,
+ 'Install_Dir'
+ )
+ if value_type != winreg.REG_SZ:
+ raise TypeError('Invalid RabbitMQ Server directory type: {0}'.format(value_type))
+ if not os.path.isdir(dir_path):
+ raise IOError('RabbitMQ directory not found: {0}'.format(dir_path))
+ subdir_match = ''
+ for name in os.listdir(dir_path):
+ if name.startswith('rabbitmq_server-'):
+ subdir_path = os.path.join(dir_path, name)
+ # Get the matching entry that is last in ASCII order.
+ if os.path.isdir(subdir_path) and subdir_path > subdir_match:
+ subdir_match = subdir_path
+ if not subdir_match:
+ raise IOError('"rabbitmq_server-*" subdirectory not found in: {0}'.format(dir_path))
+ RABBITMQCTL = os.path.join(subdir_match, 'sbin', 'rabbitmqctl.bat')
+ RABBITMQ_PLUGINS = os.path.join(subdir_match, 'sbin', 'rabbitmq-plugins.bat')
+ except Exception:
+ pass
+ finally:
+ if key is not None:
+ winreg.CloseKey(key)
+ else:
+ RABBITMQCTL = salt.utils.which('rabbitmqctl')
+ RABBITMQ_PLUGINS = salt.utils.which('rabbitmq-plugins')
+
+ if not RABBITMQCTL:
+ return (False, 'Module rabbitmq: module only works when RabbitMQ is installed')
+ return True
+
+
+def _format_response(response, msg):
+ if isinstance(response, dict):
+ if response['retcode'] != 0 or response['stderr']:
+ raise CommandExecutionError(
+ 'RabbitMQ command failed: {0}'.format(response['stderr'])
+ )
+ else:
+ response = response['stdout']
+ else:
+ if 'Error' in response:
+ raise CommandExecutionError(
+ 'RabbitMQ command failed: {0}'.format(response)
+ )
+ return {
+ msg: response
+ }
+
+
+def check_password(name, password, runas=None):
+ '''
+ .. versionadded:: 2016.3.0
+
+ Checks if a user's password is valid.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' rabbitmq.check_password rabbit_user password
+ '''
+ # try to get the rabbitmq-version - adapted from _get_rabbitmq_plugin
+
+ if runas is None and not salt.utils.is_windows():
+ runas = salt.utils.get_user()
+
+ try:
+ res = __salt__['cmd.run']([RABBITMQCTL, 'status'], reset_system_locale=False, runas=runas, python_shell=False)
+
+ # Fix for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+ # https://github.com/saltstack/salt/commit/d0e0ed6b60f4de6d2b2551cad2fc7a553efe0274
+
+ # Check regex against older RabbitMQ version status output
+ old_server_version = re.search(r'\{rabbit,"RabbitMQ","(.+)"\}', res)
+ # Check regex against newer RabbitMQ version status output
+ server_version = re.search(r"RabbitMQ version:\s*(.+)", res)
+
+ if server_version is None and old_server_version is None:
+ raise ValueError
+
+ if old_server_version:
+ server_version = old_server_version
+ server_version = server_version.group(1).split("-")[0]
+ version = [int(i) for i in server_version.split(".")]
+
+ except ValueError:
+ version = (0, 0, 0)
+ if len(version) < 3:
+ version = (0, 0, 0)
+
+ # rabbitmq introduced a native api to check a username and password in version 3.5.7.
+ if tuple(version) >= (3, 5, 7):
+ if salt.utils.is_windows():
+ # On Windows, if the password contains a special character
+ # such as '|', normal execution will fail. For example:
+ # cmd: rabbitmq.add_user abc "asdf|def"
+ # stderr: 'def' is not recognized as an internal or external
+ # command,\r\noperable program or batch file.
+ # Work around this by using a shell and a quoted command.
+ python_shell = True
+ cmd = '"{0}" authenticate_user "{1}" "{2}"'.format(
+ RABBITMQCTL, name, password
+ )
+ else:
+ python_shell = False
+ cmd = [RABBITMQCTL, 'authenticate_user', name, password]
+
+ res = __salt__['cmd.run_all'](
+ cmd,
+ reset_system_locale=False,
+ runas=runas,
+ output_loglevel='quiet',
+ python_shell=python_shell)
+
+ if res['retcode'] != 0 or res['stderr']:
+ return False
+ return True
+
+ cmd = ('rabbit_auth_backend_internal:check_user_login'
+ '(<<"{0}">>, [{{password, <<"{1}">>}}]).').format(
+ name.replace('"', '\\"'),
+ password.replace('"', '\\"'))
+
+ res = __salt__['cmd.run_all'](
+ [RABBITMQCTL, 'eval', cmd],
+ reset_system_locale=False,
+ runas=runas,
+ output_loglevel='quiet',
+ python_shell=False)
+ msg = 'password-check'
+
+ _response = _format_response(res, msg)
+ _key = _response.keys()[0]
+
+ if 'invalid credentials' in _response[_key]:
+ return False
+
+ return True