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.
- 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
substituting client id and username respectively, in publishClientSend and
publishClientReceive ACLs.
substituting client id and username respectively, in all ACLs except for
subscribeLiteral and unsubscribeLiteral.
- Add `mosquitto_topic_matches_sub_with_pattern()`, which can match against
subscriptions with `%c` and `%u` patterns for client id / username
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;
size_t len;
bool result;
const char *clientid, *username;
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_FIND(hh, rolelist->role->acls.subscribe_literal, ed->topic, len, 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){
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(acl->allow){
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;
size_t len;
bool result;
const char *clientid, *username;
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_FIND(hh, rolelist->role->acls.unsubscribe_literal, ed->topic, len, 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){
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(acl->allow){
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": "unsubscribeLiteral", "topic": "simple/topic", "allow": False },
{ "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" }
]}
@ -54,6 +58,8 @@ add_client_group_role_response = {'responses': [
{'command': 'addGroupRole'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'}, {'command': 'addRoleACL'},
{'command': 'addRoleACL'},
{'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/+/+", "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/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/%c/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/%c/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/u/%u/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/u/%u/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientSend", "topic": "pattern/c/%c/topic/#", "allow": True },
{ "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": "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/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/%c/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/%c/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/u/%u/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/u/%u/denied", "allow": False },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/c/%c/topic/#", "allow": True },
{ "command": "addRoleACL", "rolename": "myrole", "acltype": "publishClientReceive", "topic": "pattern/c/%c/denied", "allow": False },
]}
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)
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)
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
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)
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)
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
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)
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:
os.mkdir(str(port))
shutil.copyfile("dynamic-security-init.json", "%d/dynamic-security.json" % (port))
@ -297,20 +328,38 @@ try:
# Multi unsubscribe should be allowed
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)
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")
# Publish to "pattern/cid/topic" - this is allowed
# Publish to "pattern/c/cid/topic" - this is allowed
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")
# 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")
# 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
command_check(sock, delete_role_command, delete_role_response)
@ -361,8 +410,3 @@ finally:
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
The `publishClientSend` and `publishClientReceive` ACL types can make use of
pattern substitution. This means that the strings `%c` and `%u` will be
replaced with the client id and username of the client being checked,
respectively. The pattern strings must be the only item in that level of
hierarchy, so the ACL `topic/%count` will not be considered as a pattern.
The `publishClientSend`, `publishClientReceive`, `subscribePattern`, and
`unsubscribePattern` ACL types can make use of pattern substitution. This means
that the strings `%c` and `%u` will be replaced with the client id and username
of the client being checked, respectively. The pattern strings must be the only
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
client id `kitchen` would be allowed to use the topic

Loading…
Cancel
Save