Add support for pattern ACLs to dynsec un/subscription ACLs.

pull/2263/head
Roger A. Light 4 years ago
parent a02aad0a9c
commit 192a092d50

@ -25,8 +25,8 @@ Broker:
corresponding PUBACK/PUBREC. corresponding PUBACK/PUBREC.
- MOSQ_EVT_TICK is now passed to plugins when `per_listener_settings` is true. - MOSQ_EVT_TICK is now passed to plugins when `per_listener_settings` is true.
- The dynamic security plugin now supports `%c` and `%u` patterns for - The dynamic security plugin now supports `%c` and `%u` patterns for
substituting client id and username respectively, in publishClientSend and substituting client id and username respectively, in all ACLs except for
publishClientReceive ACLs. subscribeLiteral and unsubscribeLiteral.
- Add `mosquitto_topic_matches_sub_with_pattern()`, which can match against - Add `mosquitto_topic_matches_sub_with_pattern()`, which can match against
subscriptions with `%c` and `%u` patterns for client id / username subscriptions with `%c` and `%u` patterns for client id / username
substitution. substitution.

@ -103,9 +103,13 @@ static int acl_check_subscribe(struct mosquitto_evt_acl_check *ed, struct dynsec
struct dynsec__acl *acl, *acl_tmp = NULL; struct dynsec__acl *acl, *acl_tmp = NULL;
size_t len; size_t len;
bool result; bool result;
const char *clientid, *username;
len = strlen(ed->topic); len = strlen(ed->topic);
clientid = mosquitto_client_id(ed->client);
username = mosquitto_client_username(ed->client);
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){ HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
HASH_FIND(hh, rolelist->role->acls.subscribe_literal, ed->topic, len, acl); HASH_FIND(hh, rolelist->role->acls.subscribe_literal, ed->topic, len, acl);
if(acl){ if(acl){
@ -116,7 +120,7 @@ static int acl_check_subscribe(struct mosquitto_evt_acl_check *ed, struct dynsec
} }
} }
HASH_ITER(hh, rolelist->role->acls.subscribe_pattern, acl, acl_tmp){ HASH_ITER(hh, rolelist->role->acls.subscribe_pattern, acl, acl_tmp){
mosquitto_sub_matches_acl(acl->topic, ed->topic, &result); mosquitto_sub_matches_acl_with_pattern(acl->topic, ed->topic, clientid, username, &result);
if(result){ if(result){
if(acl->allow){ if(acl->allow){
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;
@ -142,9 +146,13 @@ static int acl_check_unsubscribe(struct mosquitto_evt_acl_check *ed, struct dyns
struct dynsec__acl *acl, *acl_tmp = NULL; struct dynsec__acl *acl, *acl_tmp = NULL;
size_t len; size_t len;
bool result; bool result;
const char *clientid, *username;
len = strlen(ed->topic); len = strlen(ed->topic);
clientid = mosquitto_client_id(ed->client);
username = mosquitto_client_username(ed->client);
HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){ HASH_ITER(hh, base_rolelist, rolelist, rolelist_tmp){
HASH_FIND(hh, rolelist->role->acls.unsubscribe_literal, ed->topic, len, acl); HASH_FIND(hh, rolelist->role->acls.unsubscribe_literal, ed->topic, len, acl);
if(acl){ if(acl){
@ -155,7 +163,7 @@ static int acl_check_unsubscribe(struct mosquitto_evt_acl_check *ed, struct dyns
} }
} }
HASH_ITER(hh, rolelist->role->acls.unsubscribe_pattern, acl, acl_tmp){ HASH_ITER(hh, rolelist->role->acls.unsubscribe_pattern, acl, acl_tmp){
mosquitto_sub_matches_acl(acl->topic, ed->topic, &result); mosquitto_sub_matches_acl_with_pattern(acl->topic, ed->topic, clientid, username, &result);
if(result){ if(result){
if(acl->allow){ if(acl->allow){
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;

@ -45,6 +45,10 @@ add_client_group_role_command = {"commands":[
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "multilevel-wildcard/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "multilevel-wildcard/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "unsubscribeLiteral", "topic": "simple/topic", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "unsubscribeLiteral", "topic": "simple/topic", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "pattern/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "pattern/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "subscribe/pattern/c/%c/allowed", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "subscribe/pattern/c/%c/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "subscribe/pattern/u/%u/allowed", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "subscribePattern", "topic": "subscribe/pattern/u/%u/denied", "allow": False },
{ "command": "addGroupClient", "groupname": "mygroup", "username": "user_one" } { "command": "addGroupClient", "groupname": "mygroup", "username": "user_one" }
]} ]}
@ -54,6 +58,8 @@ add_client_group_role_response = {'responses': [
{'command': 'addGroupRole'}, {'command': 'addGroupRole'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'}, {'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'}, {'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addGroupClient'} {'command': 'addGroupClient'}
]} ]}
@ -63,16 +69,16 @@ add_publish_acl_command = {"commands":[
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "single-wildcard/deny/deny", "priority":10, "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "single-wildcard/deny/deny", "priority":10, "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "single-wildcard/+/+", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "single-wildcard/+/+", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "multilevel-wildcard/topic/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "multilevel-wildcard/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/%u/topic/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/u/%u/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/%u/denied", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/u/%u/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/%c/topic/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/c/%c/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/%c/denied", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/c/%c/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "single-wildcard/bob/bob", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "single-wildcard/bob/bob", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "multilevel-wildcard/topic/topic/denied", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "multilevel-wildcard/topic/topic/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/%u/topic/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/u/%u/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/%u/denied", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/u/%u/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/%c/topic/#", "allow": True }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/c/%c/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/%c/denied", "allow": False }, { "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/c/%c/denied", "allow": False },
]} ]}
add_publish_acl_response = {'responses': [ add_publish_acl_response = {'responses': [
@ -167,23 +173,48 @@ suback_pattern_packet = mosq_test.gen_suback(mid, 0, proto_ver=5)
disconnect_kick_packet = mosq_test.gen_disconnect(reason_code=mqtt5_rc.MQTT_RC_ADMINISTRATIVE_ACTION, proto_ver=5) disconnect_kick_packet = mosq_test.gen_disconnect(reason_code=mqtt5_rc.MQTT_RC_ADMINISTRATIVE_ACTION, proto_ver=5)
mid = 15 mid = 15
publish_u_pattern_packet = mosq_test.gen_publish(mid=mid, topic="pattern/user_one/topic", qos=1, proto_ver=5, payload="test") publish_u_pattern_packet = mosq_test.gen_publish(mid=mid, topic="pattern/u/user_one/topic", qos=1, proto_ver=5, payload="test")
puback_u_packet_success = mosq_test.gen_puback(mid=mid, proto_ver=5) puback_u_packet_success = mosq_test.gen_puback(mid=mid, proto_ver=5)
publish_u_pattern_packet_r = mosq_test.gen_publish(topic="pattern/user_one/topic", qos=0, proto_ver=5, payload="test") publish_u_pattern_packet_r = mosq_test.gen_publish(topic="pattern/u/user_one/topic", qos=0, proto_ver=5, payload="test")
mid = 16 mid = 16
publish_u_pattern_packet_denied = mosq_test.gen_publish(mid=mid, topic="pattern/user_one/denied", qos=1, proto_ver=5, payload="test") publish_u_pattern_packet_denied = mosq_test.gen_publish(mid=mid, topic="pattern/u/user_one/denied", qos=1, proto_ver=5, payload="test")
puback_u_packet_denied = mosq_test.gen_puback(mid=mid, reason_code=mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5) puback_u_packet_denied = mosq_test.gen_puback(mid=mid, reason_code=mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5)
mid = 17 mid = 17
publish_c_pattern_packet = mosq_test.gen_publish(mid=mid, topic="pattern/user_one/topic", qos=1, proto_ver=5, payload="test") publish_c_pattern_packet = mosq_test.gen_publish(mid=mid, topic="pattern/c/cid/topic", qos=1, proto_ver=5, payload="test")
puback_c_packet_success = mosq_test.gen_puback(mid=mid, proto_ver=5) puback_c_packet_success = mosq_test.gen_puback(mid=mid, proto_ver=5)
publish_c_pattern_packet_r = mosq_test.gen_publish(topic="pattern/user_one/topic", qos=0, proto_ver=5, payload="test") publish_c_pattern_packet_r = mosq_test.gen_publish(topic="pattern/c/cid/topic", qos=0, proto_ver=5, payload="test")
mid = 18 mid = 18
publish_c_pattern_packet_denied = mosq_test.gen_publish(mid=mid, topic="pattern/user_one/denied", qos=1, proto_ver=5, payload="test") publish_c_pattern_packet_denied = mosq_test.gen_publish(mid=mid, topic="pattern/c/cid/denied", qos=1, proto_ver=5, payload="test")
puback_c_packet_denied = mosq_test.gen_puback(mid=mid, reason_code=mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5) puback_c_packet_denied = mosq_test.gen_puback(mid=mid, reason_code=mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5)
mid = 19
subscribe_u_pattern_packet_allowed_success = mosq_test.gen_subscribe(mid, "subscribe/pattern/u/user_one/allowed", qos=1, proto_ver=5)
suback_u_pattern_packet_allowed_success = mosq_test.gen_suback(mid, 1, proto_ver=5)
mid = 20
subscribe_u_pattern_packet_allowed_fail = mosq_test.gen_subscribe(mid, "subscribe/pattern/u/bad/allowed", qos=1, proto_ver=5)
suback_u_pattern_packet_allowed_fail = mosq_test.gen_suback(mid, mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5)
mid = 21
subscribe_u_pattern_packet_denied_success = mosq_test.gen_subscribe(mid, "subscribe/pattern/u/user_one/denied", qos=1, proto_ver=5)
suback_u_pattern_packet_denied_success = mosq_test.gen_suback(mid, mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5)
mid = 22
subscribe_c_pattern_packet_allowed_success = mosq_test.gen_subscribe(mid, "subscribe/pattern/c/cid/allowed", qos=1, proto_ver=5)
suback_c_pattern_packet_allowed_success = mosq_test.gen_suback(mid, 1, proto_ver=5)
mid = 23
subscribe_c_pattern_packet_allowed_fail = mosq_test.gen_subscribe(mid, "subscribe/pattern/c/bad/allowed", qos=1, proto_ver=5)
suback_c_pattern_packet_allowed_fail = mosq_test.gen_suback(mid, mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5)
mid = 24
subscribe_c_pattern_packet_denied_success = mosq_test.gen_subscribe(mid, "subscribe/pattern/c/cid/denied", qos=1, proto_ver=5)
suback_c_pattern_packet_denied_success = mosq_test.gen_suback(mid, mqtt5_rc.MQTT_RC_NOT_AUTHORIZED, proto_ver=5)
try: try:
os.mkdir(str(port)) os.mkdir(str(port))
shutil.copyfile("dynamic-security-init.json", "%d/dynamic-security.json" % (port)) shutil.copyfile("dynamic-security-init.json", "%d/dynamic-security.json" % (port))
@ -297,20 +328,38 @@ try:
# Multi unsubscribe should be allowed # Multi unsubscribe should be allowed
mosq_test.do_send_receive(csock, unsubscribe_multi_packet, unsuback_multi_packet_success, "unsuback multi 1") mosq_test.do_send_receive(csock, unsubscribe_multi_packet, unsuback_multi_packet_success, "unsuback multi 1")
# Publish to "pattern/user_one/topic" - this is allowed # Publish to "pattern/u/user_one/topic" - this is allowed
csock.send(publish_u_pattern_packet) csock.send(publish_u_pattern_packet)
mosq_test.receive_unordered(csock, publish_u_pattern_packet_r, puback_u_packet_success, "puback pattern 1 / publish r") mosq_test.receive_unordered(csock, publish_u_pattern_packet_r, puback_u_packet_success, "puback pattern 1 / publish r")
# Publish to "pattern/user_one/denied" - this is not allowed # Publish to "pattern/u/user_one/denied" - this is not allowed
mosq_test.do_send_receive(csock, publish_u_pattern_packet_denied, puback_u_packet_denied, "puback pattern 2") mosq_test.do_send_receive(csock, publish_u_pattern_packet_denied, puback_u_packet_denied, "puback pattern 2")
# Publish to "pattern/cid/topic" - this is allowed # Publish to "pattern/c/cid/topic" - this is allowed
csock.send(publish_c_pattern_packet) csock.send(publish_c_pattern_packet)
mosq_test.receive_unordered(csock, publish_c_pattern_packet_r, puback_c_packet_success, "puback pattern 3 / publish r") mosq_test.receive_unordered(csock, publish_c_pattern_packet_r, puback_c_packet_success, "puback pattern 3 / publish r")
# Publish to "pattern/cid/denied" - this is not allowed # Publish to "pattern/c/cid/denied" - this is not allowed
mosq_test.do_send_receive(csock, publish_c_pattern_packet_denied, puback_c_packet_denied, "puback pattern 4") mosq_test.do_send_receive(csock, publish_c_pattern_packet_denied, puback_c_packet_denied, "puback pattern 4")
# Subscribe to "subscribe/pattern/u/user_one/allowed" - this is allowed
mosq_test.do_send_receive(csock, subscribe_u_pattern_packet_allowed_success, suback_u_pattern_packet_allowed_success, "suback pattern 1")
# Subscribe to "subscribe/pattern/u/bad/allowed" - this is not allowed
mosq_test.do_send_receive(csock, subscribe_u_pattern_packet_allowed_fail, suback_u_pattern_packet_allowed_fail, "suback pattern 2")
# Subscribe to "subscribe/pattern/u/user_one/denied" - this is not allowed
mosq_test.do_send_receive(csock, subscribe_u_pattern_packet_denied_success, suback_u_pattern_packet_denied_success, "suback pattern 3")
# Subscribe to "subscribe/pattern/c/cid/allowed" - this is allowed
mosq_test.do_send_receive(csock, subscribe_c_pattern_packet_allowed_success, suback_c_pattern_packet_allowed_success, "suback pattern 4")
# Subscribe to "subscribe/pattern/c/bad/allowed" - this is not allowed
mosq_test.do_send_receive(csock, subscribe_c_pattern_packet_allowed_fail, suback_c_pattern_packet_allowed_fail, "suback pattern 5")
# Subscribe to "subscribe/pattern/c/cid/denied" - this is not allowed
mosq_test.do_send_receive(csock, subscribe_c_pattern_packet_denied_success, suback_c_pattern_packet_denied_success, "suback pattern 6")
# Delete the role, client should be kicked # Delete the role, client should be kicked
command_check(sock, delete_role_command, delete_role_response) command_check(sock, delete_role_command, delete_role_response)
@ -361,8 +410,3 @@ finally:
exit(rc) exit(rc)
publishClientSend
publishClientReceive
subscribeLiteral
subscribePattern

@ -192,11 +192,12 @@ to deny would prevent matching devices from subscribing to any topic at all.
#### ACL pattern substitution #### ACL pattern substitution
The `publishClientSend` and `publishClientReceive` ACL types can make use of The `publishClientSend`, `publishClientReceive`, `subscribePattern`, and
pattern substitution. This means that the strings `%c` and `%u` will be `unsubscribePattern` ACL types can make use of pattern substitution. This means
replaced with the client id and username of the client being checked, that the strings `%c` and `%u` will be replaced with the client id and username
respectively. The pattern strings must be the only item in that level of of the client being checked, respectively. The pattern strings must be the only
hierarchy, so the ACL `topic/%count` will not be considered as a pattern. item in that level of hierarchy, so the ACL `topic/%count` will not be
considered as a pattern.
For example, with an ACL of `room/%c/temperature`, a client connecting with For example, with an ACL of `room/%c/temperature`, a client connecting with
client id `kitchen` would be allowed to use the topic client id `kitchen` would be allowed to use the topic

Loading…
Cancel
Save