From 9d81b1f4a9132e919295d1f3fdc6b1510eb68773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 4 Jan 2019 18:29:48 +0100 Subject: [PATCH 1/5] ACL plugin and documentation --- docs/references/broker.rst | 17 ++++++ hbmqtt/plugins/topic_checking.py | 47 ++++++++++++++++ samples/boker_acl.py | 53 +++++++++++++++++++ samples/broker_start.py | 4 +- ...publish_taboo.py => client_publish_acl.py} | 3 ++ ...cribe_taboo.py => client_subscribe_acl.py} | 6 ++- scripts/broker_script.py | 3 ++ scripts/default_broker.yaml | 4 +- setup.py | 1 + 9 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 samples/boker_acl.py rename samples/{client_publish_taboo.py => client_publish_acl.py} (78%) rename samples/{client_subscribe_taboo.py => client_subscribe_acl.py} (81%) diff --git a/docs/references/broker.rst b/docs/references/broker.rst index 3c62da5..377ca87 100644 --- a/docs/references/broker.rst +++ b/docs/references/broker.rst @@ -77,6 +77,14 @@ The :class:`~hbmqtt.broker.Broker` ``__init__`` method accepts a ``config`` para plugins: ['auth.anonymous'] #List of plugins to activate for authentication among all registered plugins allow-anonymous: true / false password-file: /some/passwd_file + topic-check: + enabled: true / false # Set to False if topic filtering is not needed + plugins: ['topic_acl'] #List of plugins to activate for topic filtering among all registered plugins + acl: + username1: ['repositories/+/master', 'calendar/#', 'data/memes'] # List of topics on which client1 can publish and subscribe + username2: ... + anonymous: [] # List of topics on which an anonymous client can publish and subscribe + The ``listeners`` section allows to define network listeners which must be started by the :class:`~hbmqtt.broker.Broker`. Several listeners can be setup. ``default`` subsection defines common attributes for all listeners. Each listener can have the following settings: @@ -92,5 +100,14 @@ The ``auth`` section setup authentication behaviour: * ``allow-anonymous`` : used by the internal :class:`hbmqtt.plugins.authentication.AnonymousAuthPlugin` plugin. This parameter enables (``on``) or disable anonymous connection, ie. connection without username. * ``password-file`` : used by the internal :class:`hbmqtt.plugins.authentication.FileAuthPlugin` plugin. This parameter gives to path of the password file to load for authenticating users. +The ``topic-check`` section setup access control policies for publishing and subscribing to topics: + +* ``enabled``: set to true if you want to impose an access control policy. Otherwise, set it to false. +* ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``hbmqtt.broker.plugins`` `entry point `_. +* aditional parameters: depending on the plugin used for access control, additional parameters should be added. + * In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``. + * For each of the usernames, a list with the allowed topics must be defined. + * If the client logs in anonymously, the ``anonymous`` entry within the ACL is used in order to grant/deny subscriptions. + .. [1] See `PyYAML `_ for loading YAML files as Python dict. diff --git a/hbmqtt/plugins/topic_checking.py b/hbmqtt/plugins/topic_checking.py index ce80816..a98a29e 100644 --- a/hbmqtt/plugins/topic_checking.py +++ b/hbmqtt/plugins/topic_checking.py @@ -35,3 +35,50 @@ class TopicTabooPlugin(BaseTopicPlugin): else: return False return filter_result + + +class TopicAccessControlListPlugin(BaseTopicPlugin): + def __init__(self, context): + super().__init__(context) + + @staticmethod + def topic_ac(topic_requested, topic_allowed): + req_split = topic_requested.split('/') + allowed_split = topic_allowed.split('/') + ret = True + for i in range(max(len(req_split), len(allowed_split))): + try: + a_aux = req_split[i] + b_aux = allowed_split[i] + except IndexError: + ret = False + break + if b_aux == '#': + break + elif (b_aux == '+') or (b_aux == a_aux): + continue + else: + ret = False + break + return ret + + @asyncio.coroutine + def topic_filtering(self, *args, **kwargs): + filter_result = super().topic_filtering(*args, **kwargs) + if filter_result: + session = kwargs.get('session', None) + req_topic = kwargs.get('topic', None) + if req_topic: + username = session.username + if username is None: + username = 'anonymous' + allowed_topics = self.topic_config['acl'].get(username, None) + if allowed_topics: + for allowed_topic in allowed_topics: + if self.topic_ac(req_topic, allowed_topic): + return True + return False + else: + return False + else: + return False diff --git a/samples/boker_acl.py b/samples/boker_acl.py new file mode 100644 index 0000000..83fda2f --- /dev/null +++ b/samples/boker_acl.py @@ -0,0 +1,53 @@ +import logging +import asyncio +import os +from hbmqtt.broker import Broker + +logger = logging.getLogger(__name__) + +config = { + 'listeners': { + 'default': { + 'type': 'tcp', + 'bind': '0.0.0.0:1883', + }, + 'ws-mqtt': { + 'bind': '127.0.0.1:8080', + 'type': 'ws', + 'max_connections': 10, + }, + }, + 'sys_interval': 10, + 'auth': { + 'allow-anonymous': True, + 'password-file': os.path.join(os.path.dirname(os.path.realpath(__file__)), "passwd"), + 'plugins': [ + 'auth_file', 'auth_anonymous' + ] + + }, + 'topic-check': { + 'enabled': True, + 'plugins': [ + 'topic_acl' + ], + 'acl': { + 'test': ['repositories/+/master', 'calendar/#', 'data/memes'], + 'anonymous': [] + } + } +} + +broker = Broker(config) + + +@asyncio.coroutine +def test_coro(): + yield from broker.start() + + +if __name__ == '__main__': + formatter = "[%(asctime)s] :: %(levelname)s :: %(name)s :: %(message)s" + logging.basicConfig(level=logging.INFO, format=formatter) + asyncio.get_event_loop().run_until_complete(test_coro()) + asyncio.get_event_loop().run_forever() diff --git a/samples/broker_start.py b/samples/broker_start.py index 356f4c1..f56ef6a 100644 --- a/samples/broker_start.py +++ b/samples/broker_start.py @@ -24,7 +24,9 @@ config = { 'plugins': [ 'auth_file', 'auth_anonymous' ] - + }, + 'topic-check': { + 'enabled': False } } diff --git a/samples/client_publish_taboo.py b/samples/client_publish_acl.py similarity index 78% rename from samples/client_publish_taboo.py rename to samples/client_publish_acl.py index 40e9a4f..60a8dc9 100644 --- a/samples/client_publish_taboo.py +++ b/samples/client_publish_acl.py @@ -19,6 +19,9 @@ def test_coro(): yield from C.connect('mqtt://0.0.0.0:1883') yield from C.publish('data/classified', b'TOP SECRET', qos=0x01) yield from C.publish('data/memes', b'REAL FUN', qos=0x01) + yield from C.publish('repositories/hbmqtt/master', b'NEW STABLE RELEASE', qos=0x01) + yield from C.publish('repositories/hbmqtt/devel', b'THIS NEEDS TO BE CHECKED', qos=0x01) + yield from C.publish('calendar/hbmqtt/releases', b'NEW RELEASE', qos=0x01) logger.info("messages published") yield from C.disconnect() except ConnectException as ce: diff --git a/samples/client_subscribe_taboo.py b/samples/client_subscribe_acl.py similarity index 81% rename from samples/client_subscribe_taboo.py rename to samples/client_subscribe_acl.py index ff6b15a..b0c77b8 100644 --- a/samples/client_subscribe_taboo.py +++ b/samples/client_subscribe_acl.py @@ -17,11 +17,15 @@ logger = logging.getLogger(__name__) @asyncio.coroutine def uptime_coro(): C = MQTTClient() - yield from C.connect('mqtt://test:test@0.0.0.0:1883') + # yield from C.connect('mqtt://test:test@0.0.0.0:1883') + yield from C.connect('mqtt://0.0.0.0:1883') # Subscribe to '$SYS/broker/uptime' with QOS=1 yield from C.subscribe([ ('data/memes', QOS_1), # Topic allowed ('data/classified', QOS_1), # Topic forbidden + ('repositories/hbmqtt/master', QOS_1), # Topic allowed + ('repositories/hbmqtt/devel', QOS_1), # Topic forbidden + ('calendar/hbmqtt/releases', QOS_1), # Topic allowed ]) logger.info("Subscribed") try: diff --git a/scripts/broker_script.py b/scripts/broker_script.py index 9c4a39d..d5f615f 100644 --- a/scripts/broker_script.py +++ b/scripts/broker_script.py @@ -40,6 +40,9 @@ default_config = { 'plugins': [ 'auth_file', 'auth_anonymous' ] + }, + 'topic-check': { + 'enabled': False } } diff --git a/scripts/default_broker.yaml b/scripts/default_broker.yaml index bc0e532..0a54ec1 100644 --- a/scripts/default_broker.yaml +++ b/scripts/default_broker.yaml @@ -7,4 +7,6 @@ auth: allow-anonymous: true plugins: - auth_file - - auth_anonymous \ No newline at end of file + - auth_anonymous +topic-check: + enabled: False \ No newline at end of file diff --git a/setup.py b/setup.py index 7b5c483..3aa8294 100644 --- a/setup.py +++ b/setup.py @@ -48,6 +48,7 @@ setup( 'auth_anonymous = hbmqtt.plugins.authentication:AnonymousAuthPlugin', 'auth_file = hbmqtt.plugins.authentication:FileAuthPlugin', 'topic_taboo = hbmqtt.plugins.topic_checking:TopicTabooPlugin', + 'topic_acl = hbmqtt.plugins.topic_checking:TopicAccessControlListPlugin', 'broker_sys = hbmqtt.plugins.sys.broker:BrokerSysPlugin', ], 'hbmqtt.client.plugins': [ From 8a46ff8963d18d61a46a536976d3aeb31e9d0dba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 4 Jan 2019 18:31:42 +0100 Subject: [PATCH 2/5] minor changes --- docs/references/broker.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/references/broker.rst b/docs/references/broker.rst index 377ca87..f66c7d9 100644 --- a/docs/references/broker.rst +++ b/docs/references/broker.rst @@ -105,9 +105,9 @@ The ``topic-check`` section setup access control policies for publishing and sub * ``enabled``: set to true if you want to impose an access control policy. Otherwise, set it to false. * ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``hbmqtt.broker.plugins`` `entry point `_. * aditional parameters: depending on the plugin used for access control, additional parameters should be added. - * In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``. - * For each of the usernames, a list with the allowed topics must be defined. - * If the client logs in anonymously, the ``anonymous`` entry within the ACL is used in order to grant/deny subscriptions. +** In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``. +*** For each of the usernames, a list with the allowed topics must be defined. +*** If the client logs in anonymously, the ``anonymous`` entry within the ACL is used in order to grant/deny subscriptions. .. [1] See `PyYAML `_ for loading YAML files as Python dict. From c9f47e399647fef5410e2b3fe9c399007600ec34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Fri, 4 Jan 2019 18:33:10 +0100 Subject: [PATCH 3/5] minor changes --- docs/references/broker.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/references/broker.rst b/docs/references/broker.rst index f66c7d9..377ca87 100644 --- a/docs/references/broker.rst +++ b/docs/references/broker.rst @@ -105,9 +105,9 @@ The ``topic-check`` section setup access control policies for publishing and sub * ``enabled``: set to true if you want to impose an access control policy. Otherwise, set it to false. * ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``hbmqtt.broker.plugins`` `entry point `_. * aditional parameters: depending on the plugin used for access control, additional parameters should be added. -** In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``. -*** For each of the usernames, a list with the allowed topics must be defined. -*** If the client logs in anonymously, the ``anonymous`` entry within the ACL is used in order to grant/deny subscriptions. + * In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``. + * For each of the usernames, a list with the allowed topics must be defined. + * If the client logs in anonymously, the ``anonymous`` entry within the ACL is used in order to grant/deny subscriptions. .. [1] See `PyYAML `_ for loading YAML files as Python dict. From cece85a75e2cf003e37290efc921344ea00edc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Sat, 5 Jan 2019 19:49:00 +0100 Subject: [PATCH 4/5] documentation --- docs/references/broker.rst | 5 +++-- samples/{boker_acl.py => broker_acl.py} | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) rename samples/{boker_acl.py => broker_acl.py} (96%) diff --git a/docs/references/broker.rst b/docs/references/broker.rst index 377ca87..7a9a334 100644 --- a/docs/references/broker.rst +++ b/docs/references/broker.rst @@ -81,6 +81,7 @@ The :class:`~hbmqtt.broker.Broker` ``__init__`` method accepts a ``config`` para enabled: true / false # Set to False if topic filtering is not needed plugins: ['topic_acl'] #List of plugins to activate for topic filtering among all registered plugins acl: + # username: [list of allowed topics] username1: ['repositories/+/master', 'calendar/#', 'data/memes'] # List of topics on which client1 can publish and subscribe username2: ... anonymous: [] # List of topics on which an anonymous client can publish and subscribe @@ -104,9 +105,9 @@ The ``topic-check`` section setup access control policies for publishing and sub * ``enabled``: set to true if you want to impose an access control policy. Otherwise, set it to false. * ``plugins``: defines the list of activated plugins. Note the plugins must be defined in the ``hbmqtt.broker.plugins`` `entry point `_. -* aditional parameters: depending on the plugin used for access control, additional parameters should be added. +* additional parameters: depending on the plugin used for access control, additional parameters should be added. * In case of ``topic_acl`` plugin, the Access Control List (ACL) must be defined in the parameter ``acl``. - * For each of the usernames, a list with the allowed topics must be defined. + * For each username, a list with the allowed topics must be defined. * If the client logs in anonymously, the ``anonymous`` entry within the ACL is used in order to grant/deny subscriptions. diff --git a/samples/boker_acl.py b/samples/broker_acl.py similarity index 96% rename from samples/boker_acl.py rename to samples/broker_acl.py index 83fda2f..165e6f3 100644 --- a/samples/boker_acl.py +++ b/samples/broker_acl.py @@ -32,6 +32,7 @@ config = { 'topic_acl' ], 'acl': { + # username: [list of allowed topics] 'test': ['repositories/+/master', 'calendar/#', 'data/memes'], 'anonymous': [] } From 3df8b4066e8b42adea00bd06550e6c15cd065efc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C3=A1n=20C=C3=A1rdenas?= Date: Sat, 5 Jan 2019 19:52:54 +0100 Subject: [PATCH 5/5] changes in ACL sample --- samples/client_subscribe_acl.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/samples/client_subscribe_acl.py b/samples/client_subscribe_acl.py index b0c77b8..906813f 100644 --- a/samples/client_subscribe_acl.py +++ b/samples/client_subscribe_acl.py @@ -17,8 +17,8 @@ logger = logging.getLogger(__name__) @asyncio.coroutine def uptime_coro(): C = MQTTClient() - # yield from C.connect('mqtt://test:test@0.0.0.0:1883') - yield from C.connect('mqtt://0.0.0.0:1883') + yield from C.connect('mqtt://test:test@0.0.0.0:1883') + # yield from C.connect('mqtt://0.0.0.0:1883') # Subscribe to '$SYS/broker/uptime' with QOS=1 yield from C.subscribe([ ('data/memes', QOS_1), # Topic allowed