Extend check_rabbitmq.py to honor ssl=only

This patch enables checks of rabbitmq when SSL is enabled, when ssl
config option is set to 'on' both ports (5672 and 5671) will be
checked.

Change-Id: Ia0bab1dca65112cd06ae382f6ebc1cc280d7b130
Closes-Bug: 1687916
This commit is contained in:
Felipe Reyes 2018-03-06 18:20:50 -03:00
parent a649fb15de
commit 6ede4d38b3
5 changed files with 166 additions and 30 deletions

View File

@ -48,9 +48,9 @@ except ImportError:
import psutil
ssl_key_file = "/etc/rabbitmq/rabbit-server-privkey.pem"
ssl_cert_file = "/etc/rabbitmq/rabbit-server-cert.pem"
ssl_ca_file = "/etc/rabbitmq/rabbit-server-ca.pem"
SSL_KEY_FILE = "/etc/rabbitmq/rabbit-server-privkey.pem"
SSL_CERT_FILE = "/etc/rabbitmq/rabbit-server-cert.pem"
SSL_CA_FILE = "/etc/rabbitmq/rabbit-server-ca.pem"
RABBITMQ_CTL = '/usr/sbin/rabbitmqctl'
ENV_CONF = '/etc/rabbitmq/rabbitmq-env.conf'
@ -90,9 +90,9 @@ class RabbitMQSSLContext(object):
gid = grp.getgrnam("rabbitmq").gr_gid
for contents, path in (
(ssl_key, ssl_key_file),
(ssl_cert, ssl_cert_file),
(ssl_ca, ssl_ca_file)):
(ssl_key, SSL_KEY_FILE),
(ssl_cert, SSL_CERT_FILE),
(ssl_ca, SSL_CA_FILE)):
if not contents:
continue
@ -100,20 +100,26 @@ class RabbitMQSSLContext(object):
with open(path, 'w') as fh:
fh.write(contents)
if path == SSL_CA_FILE:
# the CA can be world readable and it will allow clients to
# verify the certificate offered by rabbit.
os.chmod(path, 0o644)
else:
os.chmod(path, 0o640)
os.chown(path, uid, gid)
data = {
"ssl_port": ssl_port,
"ssl_cert_file": ssl_cert_file,
"ssl_key_file": ssl_key_file,
"ssl_cert_file": SSL_CERT_FILE,
"ssl_key_file": SSL_KEY_FILE,
"ssl_client": ssl_client,
"ssl_ca_file": "",
"ssl_only": ssl_only
}
if ssl_ca:
data["ssl_ca_file"] = ssl_ca_file
data["ssl_ca_file"] = SSL_CA_FILE
return data

View File

@ -31,6 +31,7 @@ except ImportError:
import rabbit_utils as rabbit
import ssl_utils
from rabbitmq_context import SSL_CA_FILE
from lib.utils import (
chown, chmod,
@ -616,12 +617,33 @@ def update_nrpe_checks():
rabbit.grant_permissions(user, vhost)
nrpe_compat = nrpe.NRPE(hostname=hostname)
if config('ssl') in ['off', 'on']:
cmd = ('{plugins_dir}/check_rabbitmq.py --user {user} '
'--password {password} --vhost {vhost}')
cmd = cmd.format(plugins_dir=NAGIOS_PLUGINS, user=user,
password=password, vhost=vhost)
nrpe_compat.add_check(
shortname=rabbit.RABBIT_USER,
description='Check RabbitMQ {%s}' % myunit,
check_cmd='{}/check_rabbitmq.py --user {} --password {} --vhost {}'
''.format(NAGIOS_PLUGINS, user, password, vhost)
check_cmd=cmd
)
if config('ssl') in ['only', 'on']:
log('Adding rabbitmq SSL check', level=DEBUG)
cmd = ('{plugins_dir}/check_rabbitmq.py --user {user} '
'--password {password} --vhost {vhost} '
'--ssl --ssl-ca {ssl_ca} --port {port}')
cmd = cmd.format(plugins_dir=NAGIOS_PLUGINS,
user=user,
password=password,
port=int(config('ssl_port')),
vhost=vhost,
ssl_ca=SSL_CA_FILE)
nrpe_compat.add_check(
shortname=rabbit.RABBIT_USER + "_ssl",
description='Check RabbitMQ (SSL) {%s}' % myunit,
check_cmd=cmd
)
if config('queue_thresholds'):
cmd = ""
# If value of queue_thresholds is incorrect we want the hook to fail
@ -695,7 +717,17 @@ def config_changed():
if rabbit.archive_upgrade_available():
rabbit.install_or_upgrade_packages()
if config('ssl') == 'off':
open_port(5672)
close_port(int(config('ssl_port')))
elif config('ssl') == 'on':
open_port(5672)
open_port(int(config('ssl_port')))
elif config('ssl') == 'only':
close_port(5672)
open_port(int(config('ssl_port')))
else:
log("Unknown ssl config value: '%s'" % config('ssl'), level=ERROR)
chown(RABBIT_DIR, rabbit.RABBIT_USER, rabbit.RABBIT_USER)
chmod(RABBIT_DIR, 0o775)

View File

@ -38,14 +38,18 @@ def alarm_handler(signum, frame):
os._exit(1)
def get_connection(host_port, user, password, vhost):
def get_connection(host_port, user, password, vhost, ssl, ssl_ca):
""" connect to the amqp service """
if options.verbose:
print "Connection to %s requested" % host_port
try:
ret = amqp.Connection(host=host_port, userid=user,
password=password, virtual_host=vhost,
insist=False)
params = {'host': host_port, 'userid': user, 'password': password,
'virtual_host': vhost, 'insist': False}
if ssl:
params['ssl'] = {'ca_certs': ssl_ca}
ret = amqp.Connection(**params)
except (socket.error, TypeError), e:
print "ERROR: Could not connect to RabbitMQ server %s:%d" % (
options.host, options.port)
@ -53,9 +57,9 @@ def get_connection(host_port, user, password, vhost):
print e
raise
sys.exit(2)
except:
print "ERROR: Unknown error connecting to RabbitMQ server %s:%d" % (
options.host, options.port)
except Exception as ex:
print("ERROR: Unknown error connecting to RabbitMQ server %s:%d: %s"
% (options.host, options.port, ex))
if options.verbose:
raise
sys.exit(3)
@ -174,12 +178,12 @@ def main_loop(conn, exname):
return consumer.loop(timeout=options.timeout)
def main(host, port, exname, extype, user, password, vhost):
def main(host, port, exname, extype, user, password, vhost, ssl, ssl_ca):
""" setup the connection and the communication channel """
sys.stdout = os.fdopen(os.dup(1), "w", 0)
host_port = "%s:%s" % (host, port)
conn = get_connection(host_port, user, password, vhost)
chan = conn.channel()
conn = get_connection(host_port, user, password, vhost, ssl, ssl_ca)
if setup_exchange(conn, exname, extype):
if options.verbose:
print "Created %s exchange of type %s" % (exname, extype)
@ -187,8 +191,13 @@ def main(host, port, exname, extype, user, password, vhost):
if options.verbose:
print "Reusing existing exchange %s of type %s" % (exname, extype)
ret = main_loop(conn, exname)
chan.close()
try:
conn.close()
except socket.error:
# when using SSL socket.shutdown() fails inside amqplib.
pass
return ret
if __name__ == '__main__':
@ -222,6 +231,10 @@ if __name__ == '__main__':
parser.add_option("--vhost", dest="vhost", default="/",
help="RabbitMQ vhost [default=%default]",
metavar="VHOST")
parser.add_option("--ssl", dest="ssl", default=False, action="store_true",
help="Connect using SSL")
parser.add_option("--ssl-ca", metavar="FILE", dest="ssl_ca",
help="SSL CA certificate path")
(options, args) = parser.parse_args()
if options.verbose:
@ -229,7 +242,8 @@ if __name__ == '__main__':
Using AMQP setup: host:port=%s:%d exchange_name=%s exchange_type=%s
""" % (options.host, options.port, options.exchange, options.type)
ret = main(options.host, options.port, options.exchange, options.type,
options.user, options.password, options.vhost)
options.user, options.password, options.vhost, options.ssl,
options.ssl_ca)
if ret:
print "Ok: sent and received %d test messages" % options.messages
sys.exit(0)

View File

@ -14,6 +14,7 @@
import os
import shutil
import subprocess
import sys
import tempfile
@ -34,6 +35,7 @@ with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
lambda *args, **kwargs: f(*args, **kwargs))
import rabbitmq_server_relations
import rabbit_utils
TO_PATCH = [
# charmhelpers.core.hookenv
@ -48,6 +50,11 @@ class RelationUtil(CharmTestCase):
self.fake_repo = {}
super(RelationUtil, self).setUp(rabbitmq_server_relations,
TO_PATCH)
self.tmp_dir = tempfile.mkdtemp()
def tearDown(self):
shutil.rmtree(self.tmp_dir)
super(RelationUtil, self).tearDown()
@patch('rabbitmq_server_relations.rabbit.leader_node_is_ready')
@patch('rabbitmq_server_relations.peer_store_and_set')
@ -278,3 +285,71 @@ class RelationUtil(CharmTestCase):
finally:
if os.path.exists(tmpdir):
shutil.rmtree(tmpdir)
@patch('rabbitmq_server_relations.local_unit')
@patch('charmhelpers.contrib.charmsupport.nrpe.NRPE.add_check')
@patch('subprocess.check_call')
@patch('rabbit_utils.get_rabbit_password_on_disk')
@patch('charmhelpers.contrib.charmsupport.nrpe.relation_ids')
@patch('charmhelpers.contrib.charmsupport.nrpe.config')
@patch('charmhelpers.contrib.charmsupport.nrpe.get_nagios_unit_name')
@patch('charmhelpers.contrib.charmsupport.nrpe.get_nagios_hostname')
@patch('os.fchown')
@patch('rabbitmq_server_relations.charm_dir')
@patch('subprocess.check_output')
@patch('rabbitmq_server_relations.config')
def test_update_nrpe_checks(self, mock_config, mock_check_output,
mock_charm_dir, mock_fchown,
mock_get_nagios_hostname,
mock_get_nagios_unit_name, mock_config2,
mock_nrpe_relation_ids,
mock_get_rabbit_password_on_disk,
mock_check_call, mock_add_check,
mock_local_unit):
self.test_config.set('ssl', 'on')
mock_charm_dir.side_effect = lambda: self.tmp_dir
mock_config.side_effect = self.test_config
mock_config2.side_effect = self.test_config
rabbitmq_server_relations.STATS_CRONFILE = os.path.join(
self.tmp_dir, "rabbitmq-stats")
mock_get_nagios_hostname.return_value = "foo-0"
mock_get_nagios_unit_name.return_value = "bar-0"
mock_get_rabbit_password_on_disk.return_value = "qwerty"
mock_nrpe_relation_ids.side_effect = lambda x: [
'nrpe-external-master:1']
mock_local_unit.return_value = 'unit/0'
rabbitmq_server_relations.update_nrpe_checks()
mock_check_output.assert_any_call(
['/usr/bin/rsync', '-r', '--delete', '--executability',
'%s/scripts/collect_rabbitmq_stats.sh' % self.tmp_dir,
'/usr/local/bin/collect_rabbitmq_stats.sh'],
stderr=subprocess.STDOUT)
# regular check on 5672
cmd = ('{plugins_dir}/check_rabbitmq.py --user {user} '
'--password {password} --vhost {vhost}').format(
plugins_dir=rabbitmq_server_relations.NAGIOS_PLUGINS,
user='nagios-unit-0', vhost='nagios-unit-0',
password='qwerty')
mock_add_check.assert_any_call(
shortname=rabbit_utils.RABBIT_USER,
description='Check RabbitMQ {%s}' % 'bar-0', check_cmd=cmd)
# check on ssl port 5671
cmd = ('{plugins_dir}/check_rabbitmq.py --user {user} '
'--password {password} --vhost {vhost} '
'--ssl --ssl-ca {ssl_ca} --port {port}').format(
plugins_dir=rabbitmq_server_relations.NAGIOS_PLUGINS,
user='nagios-unit-0',
password='qwerty',
port=int(self.test_config['ssl_port']),
vhost='nagios-unit-0',
ssl_ca=rabbitmq_server_relations.SSL_CA_FILE)
mock_add_check.assert_any_call(
shortname=rabbit_utils.RABBIT_USER + "_ssl",
description='Check RabbitMQ (SSL) {%s}' % 'bar-0', check_cmd=cmd)

View File

@ -117,6 +117,15 @@ class TestConfig(object):
def __getitem__(self, key):
return self.get(key)
def __contains__(self, key):
return key in self.config
def __call__(self, key=None):
if key:
return self.get(key)
else:
return self
class TestRelation(object):