Skip to content

Commit

Permalink
ACL plugin and documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
romancardenas committed Jan 4, 2019
1 parent 46f90a5 commit 9d81b1f
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 3 deletions.
17 changes: 17 additions & 0 deletions docs/references/broker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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 <https://pythonhosted.org/setuptools/setuptools.html#dynamic-discovery-of-services-and-plugins>`_.
* 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 <http://pyyaml.org/wiki/PyYAMLDocumentation>`_ for loading YAML files as Python dict.
47 changes: 47 additions & 0 deletions hbmqtt/plugins/topic_checking.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,50 @@ def topic_filtering(self, *args, **kwargs):
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
53 changes: 53 additions & 0 deletions samples/boker_acl.py
Original file line number Diff line number Diff line change
@@ -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()
4 changes: 3 additions & 1 deletion samples/broker_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
'plugins': [
'auth_file', 'auth_anonymous'
]

},
'topic-check': {
'enabled': False
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@
@asyncio.coroutine
def uptime_coro():
C = MQTTClient()
yield from C.connect('mqtt://test:[email protected]:1883')
# yield from C.connect('mqtt://test:[email protected]: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:
Expand Down
3 changes: 3 additions & 0 deletions scripts/broker_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@
'plugins': [
'auth_file', 'auth_anonymous'
]
},
'topic-check': {
'enabled': False
}
}

Expand Down
4 changes: 3 additions & 1 deletion scripts/default_broker.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ auth:
allow-anonymous: true
plugins:
- auth_file
- auth_anonymous
- auth_anonymous
topic-check:
enabled: False
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
'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': [
Expand Down

0 comments on commit 9d81b1f

Please sign in to comment.