Merge "[AMQP 1.0] Add default SASL realm setting"
This commit is contained in:
commit
eef025d619
oslo_messaging
@ -763,13 +763,14 @@ class NotificationServer(Server):
|
|||||||
|
|
||||||
|
|
||||||
class Hosts(object):
|
class Hosts(object):
|
||||||
"""An order list of TransportHost addresses. Connection failover
|
"""An order list of TransportHost addresses. Connection failover progresses
|
||||||
progresses from one host to the next. username and password come from the
|
from one host to the next. username, password , and realm come from the
|
||||||
configuration and are used only if no username/password was given in the
|
configuration and are used only if no username/password/realm is present in
|
||||||
URL.
|
the URL.
|
||||||
"""
|
"""
|
||||||
def __init__(self, entries=None, default_username=None,
|
def __init__(self, entries=None, default_username=None,
|
||||||
default_password=None):
|
default_password=None,
|
||||||
|
default_realm=None):
|
||||||
if entries:
|
if entries:
|
||||||
self._entries = entries[:]
|
self._entries = entries[:]
|
||||||
else:
|
else:
|
||||||
@ -779,6 +780,8 @@ class Hosts(object):
|
|||||||
entry.port = entry.port or 5672
|
entry.port = entry.port or 5672
|
||||||
entry.username = entry.username or default_username
|
entry.username = entry.username or default_username
|
||||||
entry.password = entry.password or default_password
|
entry.password = entry.password or default_password
|
||||||
|
if default_realm and entry.username and '@' not in entry.username:
|
||||||
|
entry.username = entry.username + '@' + default_realm
|
||||||
self._current = random.randint(0, len(self._entries) - 1)
|
self._current = random.randint(0, len(self._entries) - 1)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -840,7 +843,8 @@ class Controller(pyngus.ConnectionEventHandler):
|
|||||||
self.sasl_config_dir = config.oslo_messaging_amqp.sasl_config_dir
|
self.sasl_config_dir = config.oslo_messaging_amqp.sasl_config_dir
|
||||||
self.sasl_config_name = config.oslo_messaging_amqp.sasl_config_name
|
self.sasl_config_name = config.oslo_messaging_amqp.sasl_config_name
|
||||||
self.hosts = Hosts(hosts, config.oslo_messaging_amqp.username,
|
self.hosts = Hosts(hosts, config.oslo_messaging_amqp.username,
|
||||||
config.oslo_messaging_amqp.password)
|
config.oslo_messaging_amqp.password,
|
||||||
|
config.oslo_messaging_amqp.sasl_default_realm)
|
||||||
self.conn_retry_interval = \
|
self.conn_retry_interval = \
|
||||||
config.oslo_messaging_amqp.connection_retry_interval
|
config.oslo_messaging_amqp.connection_retry_interval
|
||||||
self.conn_retry_backoff = \
|
self.conn_retry_backoff = \
|
||||||
|
@ -83,6 +83,10 @@ amqp1_opts = [
|
|||||||
deprecated_group='amqp1',
|
deprecated_group='amqp1',
|
||||||
help='Name of configuration file (without .conf suffix)'),
|
help='Name of configuration file (without .conf suffix)'),
|
||||||
|
|
||||||
|
cfg.StrOpt('sasl_default_realm',
|
||||||
|
default='',
|
||||||
|
help='SASL realm to use if no realm present in username'),
|
||||||
|
|
||||||
cfg.StrOpt('username',
|
cfg.StrOpt('username',
|
||||||
default='',
|
default='',
|
||||||
deprecated_group='amqp1',
|
deprecated_group='amqp1',
|
||||||
|
@ -711,7 +711,7 @@ class TestCyrusAuthentication(test_utils.BaseTestCase):
|
|||||||
# the temp dir after the first test is run
|
# the temp dir after the first test is run
|
||||||
os.makedirs(cls._conf_dir)
|
os.makedirs(cls._conf_dir)
|
||||||
db = os.path.join(cls._conf_dir, 'openstack.sasldb')
|
db = os.path.join(cls._conf_dir, 'openstack.sasldb')
|
||||||
_t = "echo secret | saslpasswd2 -c -p -f ${db} joe"
|
_t = "echo secret | saslpasswd2 -c -p -f ${db} -u myrealm joe"
|
||||||
cmd = Template(_t).substitute(db=db)
|
cmd = Template(_t).substitute(db=db)
|
||||||
try:
|
try:
|
||||||
subprocess.check_call(args=cmd, shell=True)
|
subprocess.check_call(args=cmd, shell=True)
|
||||||
@ -744,7 +744,7 @@ mech_list: ${mechs}
|
|||||||
_dir = TestCyrusAuthentication._conf_dir
|
_dir = TestCyrusAuthentication._conf_dir
|
||||||
self._broker = FakeBroker(self.conf.oslo_messaging_amqp,
|
self._broker = FakeBroker(self.conf.oslo_messaging_amqp,
|
||||||
sasl_mechanisms=_mechs,
|
sasl_mechanisms=_mechs,
|
||||||
user_credentials=["\0joe\0secret"],
|
user_credentials=["\0joe@myrealm\0secret"],
|
||||||
sasl_config_dir=_dir,
|
sasl_config_dir=_dir,
|
||||||
sasl_config_name="openstack")
|
sasl_config_name="openstack")
|
||||||
self._broker.start()
|
self._broker.start()
|
||||||
@ -757,45 +757,44 @@ mech_list: ${mechs}
|
|||||||
self._broker = None
|
self._broker = None
|
||||||
super(TestCyrusAuthentication, self).tearDown()
|
super(TestCyrusAuthentication, self).tearDown()
|
||||||
|
|
||||||
def test_authentication_ok(self):
|
def _authentication_test(self, addr, retry=None):
|
||||||
"""Verify that username and password given in TransportHost are
|
|
||||||
accepted by the broker.
|
|
||||||
"""
|
|
||||||
addr = "amqp://joe:secret@%s:%d" % (self._broker.host,
|
|
||||||
self._broker.port)
|
|
||||||
url = oslo_messaging.TransportURL.parse(self.conf, addr)
|
url = oslo_messaging.TransportURL.parse(self.conf, addr)
|
||||||
driver = amqp_driver.ProtonDriver(self.conf, url)
|
driver = amqp_driver.ProtonDriver(self.conf, url)
|
||||||
target = oslo_messaging.Target(topic="test-topic")
|
target = oslo_messaging.Target(topic="test-topic")
|
||||||
listener = _ListenerThread(
|
listener = _ListenerThread(
|
||||||
driver.listen(target, None, None)._poll_style_listener, 1)
|
driver.listen(target, None, None)._poll_style_listener, 1)
|
||||||
rc = driver.send(target, {"context": True},
|
try:
|
||||||
{"method": "echo"}, wait_for_reply=True)
|
rc = driver.send(target, {"context": True},
|
||||||
self.assertIsNotNone(rc)
|
{"method": "echo"}, wait_for_reply=True,
|
||||||
listener.join(timeout=30)
|
retry=retry)
|
||||||
self.assertFalse(listener.isAlive())
|
self.assertIsNotNone(rc)
|
||||||
driver.cleanup()
|
listener.join(timeout=30)
|
||||||
|
self.assertFalse(listener.isAlive())
|
||||||
|
finally:
|
||||||
|
driver.cleanup()
|
||||||
|
|
||||||
|
def test_authentication_ok(self):
|
||||||
|
"""Verify that username and password given in TransportHost are
|
||||||
|
accepted by the broker.
|
||||||
|
"""
|
||||||
|
addr = "amqp://joe@myrealm:secret@%s:%d" % (self._broker.host,
|
||||||
|
self._broker.port)
|
||||||
|
self._authentication_test(addr)
|
||||||
|
|
||||||
def test_authentication_failure(self):
|
def test_authentication_failure(self):
|
||||||
"""Verify that a bad password given in TransportHost is
|
"""Verify that a bad password given in TransportHost is
|
||||||
rejected by the broker.
|
rejected by the broker.
|
||||||
"""
|
"""
|
||||||
addr = "amqp://joe:badpass@%s:%d" % (self._broker.host,
|
addr = "amqp://joe@myrealm:badpass@%s:%d" % (self._broker.host,
|
||||||
self._broker.port)
|
self._broker.port)
|
||||||
url = oslo_messaging.TransportURL.parse(self.conf, addr)
|
|
||||||
driver = amqp_driver.ProtonDriver(self.conf, url)
|
|
||||||
target = oslo_messaging.Target(topic="test-topic")
|
|
||||||
_ListenerThread(
|
|
||||||
driver.listen(target, None, None)._poll_style_listener, 1)
|
|
||||||
try:
|
try:
|
||||||
driver.send(target, {"context": True}, {"method": "echo"},
|
self._authentication_test(addr, retry=2)
|
||||||
wait_for_reply=True, retry=2)
|
|
||||||
except oslo_messaging.MessageDeliveryFailure as e:
|
except oslo_messaging.MessageDeliveryFailure as e:
|
||||||
# verify the exception indicates the failure was an authentication
|
# verify the exception indicates the failure was an authentication
|
||||||
# error
|
# error
|
||||||
self.assertTrue('amqp:unauthorized-access' in str(e))
|
self.assertTrue('amqp:unauthorized-access' in str(e))
|
||||||
else:
|
else:
|
||||||
self.assertIsNone("Expected authentication failure")
|
self.assertIsNone("Expected authentication failure")
|
||||||
driver.cleanup()
|
|
||||||
|
|
||||||
def test_authentication_bad_mechs(self):
|
def test_authentication_bad_mechs(self):
|
||||||
"""Verify that the connection fails if the client's SASL mechanisms do
|
"""Verify that the connection fails if the client's SASL mechanisms do
|
||||||
@ -803,40 +802,41 @@ mech_list: ${mechs}
|
|||||||
"""
|
"""
|
||||||
self.config(sasl_mechanisms="EXTERNAL ANONYMOUS",
|
self.config(sasl_mechanisms="EXTERNAL ANONYMOUS",
|
||||||
group="oslo_messaging_amqp")
|
group="oslo_messaging_amqp")
|
||||||
addr = "amqp://joe:secret@%s:%d" % (self._broker.host,
|
addr = "amqp://joe@myrealm:secret@%s:%d" % (self._broker.host,
|
||||||
self._broker.port)
|
self._broker.port)
|
||||||
url = oslo_messaging.TransportURL.parse(self.conf, addr)
|
|
||||||
driver = amqp_driver.ProtonDriver(self.conf, url)
|
|
||||||
target = oslo_messaging.Target(topic="test-topic")
|
|
||||||
_ListenerThread(
|
|
||||||
driver.listen(target, None, None)._poll_style_listener, 1)
|
|
||||||
self.assertRaises(oslo_messaging.MessageDeliveryFailure,
|
self.assertRaises(oslo_messaging.MessageDeliveryFailure,
|
||||||
driver.send,
|
self._authentication_test,
|
||||||
target, {"context": True},
|
addr,
|
||||||
{"method": "echo"},
|
|
||||||
wait_for_reply=True,
|
|
||||||
retry=0)
|
retry=0)
|
||||||
driver.cleanup()
|
|
||||||
|
|
||||||
def test_authentication_default_username(self):
|
def test_authentication_default_username(self):
|
||||||
"""Verify that a configured username/password is used if none appears
|
"""Verify that a configured username/password is used if none appears
|
||||||
in the URL.
|
in the URL.
|
||||||
|
Deprecated: username password deprecated in favor of transport_url
|
||||||
"""
|
"""
|
||||||
addr = "amqp://%s:%d" % (self._broker.host, self._broker.port)
|
addr = "amqp://%s:%d" % (self._broker.host, self._broker.port)
|
||||||
self.config(username="joe",
|
self.config(username="joe@myrealm",
|
||||||
password="secret",
|
password="secret",
|
||||||
group="oslo_messaging_amqp")
|
group="oslo_messaging_amqp")
|
||||||
url = oslo_messaging.TransportURL.parse(self.conf, addr)
|
self._authentication_test(addr)
|
||||||
driver = amqp_driver.ProtonDriver(self.conf, url)
|
|
||||||
target = oslo_messaging.Target(topic="test-topic")
|
def test_authentication_default_realm(self):
|
||||||
listener = _ListenerThread(
|
"""Verify that default realm is used if none present in username"""
|
||||||
driver.listen(target, None, None)._poll_style_listener, 1)
|
addr = "amqp://joe:secret@%s:%d" % (self._broker.host,
|
||||||
rc = driver.send(target, {"context": True},
|
self._broker.port)
|
||||||
{"method": "echo"}, wait_for_reply=True)
|
self.config(sasl_default_realm="myrealm",
|
||||||
self.assertIsNotNone(rc)
|
group="oslo_messaging_amqp")
|
||||||
listener.join(timeout=30)
|
self._authentication_test(addr)
|
||||||
self.assertFalse(listener.isAlive())
|
|
||||||
driver.cleanup()
|
def test_authentication_ignore_default_realm(self):
|
||||||
|
"""Verify that default realm is not used if realm present in
|
||||||
|
username
|
||||||
|
"""
|
||||||
|
addr = "amqp://joe@myrealm:secret@%s:%d" % (self._broker.host,
|
||||||
|
self._broker.port)
|
||||||
|
self.config(sasl_default_realm="bad-realm",
|
||||||
|
group="oslo_messaging_amqp")
|
||||||
|
self._authentication_test(addr)
|
||||||
|
|
||||||
|
|
||||||
@testtools.skipUnless(pyngus, "proton modules not present")
|
@testtools.skipUnless(pyngus, "proton modules not present")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user