Add 'deny' as an option for topics/patterns in acl file to allow certain topics to be explicitly denied when they might otherwise be allowed through a more open read/write/readwrite option. Example: 'topic readwrite test/#' and 'topic deny test/hello/#' may be added so that a user can read/write to all test/# topics, except for test/hello/#.

Signed-off-by: Brandt Hill <brandtlarsonhill@gmail.com>

Change variable name for clarity. Remember to initialize bool (I'm bad at C).

Signed-off-by: Brandt Hill <brandtlarsonhill@gmail.com>

Add documentation to config man page

Signed-off-by: Brandt Hill <brandtlarsonhill@gmail.com>

Add test case for deny option

Signed-off-by: Brandt Hill <brandtlarsonhill@gmail.com>

Add deny acls to top of the list to preserve early exit

Signed-off-by: Brandt Hill <brandtlarsonhill@gmail.com>

change comments

Signed-off-by: Brandt Hill <brandtlarsonhill@gmail.com>
pull/1611/head
Brandt Hill 6 years ago
parent 019d4214b8
commit 16eecfcbc5

@ -107,14 +107,16 @@
listed will have access. Topic access is added with listed will have access. Topic access is added with
lines of the format:</para> lines of the format:</para>
<para><code>topic [read|write|readwrite] &lt;topic&gt;</code></para> <para><code>topic [read|write|readwrite|deny] &lt;topic&gt;</code></para>
<para>The access type is controlled using "read", "write" or <para>The access type is controlled using "read", "write",
"readwrite". This parameter is optional (unless "readwrite" or "deny". This parameter is optional (unless
&lt;topic&gt; includes a space character) - if not &lt;topic&gt; includes a space character) - if not
given then the access is read/write. &lt;topic&gt; can given then the access is read/write. &lt;topic&gt; can
contain the + or # wildcards as in contain the + or # wildcards as in
subscriptions.</para> subscriptions. The "deny" option can used to explicity
deny access to a topic that would otherwise be granted
by a broader read/write/readwrite statement.</para>
<para>The first set of topics are applied to anonymous <para>The first set of topics are applied to anonymous
clients, assuming <option>allow_anonymous</option> is clients, assuming <option>allow_anonymous</option> is
@ -131,7 +133,7 @@
substitution within the topic. The form is the same as substitution within the topic. The form is the same as
for the topic keyword, but using pattern as the for the topic keyword, but using pattern as the
keyword.</para> keyword.</para>
<para><code>pattern [read|write|readwrite] &lt;topic&gt;</code></para> <para><code>pattern [read|write|readwrite|deny] &lt;topic&gt;</code></para>
<para>The patterns available for substition are:</para> <para>The patterns available for substition are:</para>
<itemizedlist mark="circle"> <itemizedlist mark="circle">

@ -217,10 +217,16 @@ int add__acl(struct mosquitto__security_options *security_opts, const char *user
/* Add acl to user acl list */ /* Add acl to user acl list */
if(acl_user->acl){ if(acl_user->acl){
acl_tail = acl_user->acl; acl_tail = acl_user->acl;
while(acl_tail->next){ if(access == MOSQ_ACL_NONE){
acl_tail = acl_tail->next; /* Put "deny" acls at front of the list */
acl->next = acl_tail;
acl_user->acl = acl;
}else{
while(acl_tail->next){
acl_tail = acl_tail->next;
}
acl_tail->next = acl;
} }
acl_tail->next = acl;
}else{ }else{
acl_user->acl = acl; acl_user->acl = acl;
} }
@ -291,10 +297,16 @@ int add__acl_pattern(struct mosquitto__security_options *security_opts, const ch
if(security_opts->acl_patterns){ if(security_opts->acl_patterns){
acl_tail = security_opts->acl_patterns; acl_tail = security_opts->acl_patterns;
while(acl_tail->next){ if(access == MOSQ_ACL_NONE){
acl_tail = acl_tail->next; /* Put "deny" acls at front of the list */
acl->next = acl_tail;
security_opts->acl_patterns = acl;
}else{
while(acl_tail->next){
acl_tail = acl_tail->next;
}
acl_tail->next = acl;
} }
acl_tail->next = acl;
}else{ }else{
security_opts->acl_patterns = acl; security_opts->acl_patterns = acl;
} }
@ -334,7 +346,7 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte
acl_root = NULL; acl_root = NULL;
} }
/* Loop through all ACLs for this client. */ /* Loop through all ACLs for this client. ACL denials are iterated over first. */
while(acl_root){ while(acl_root){
/* Loop through the topic looking for matches to this ACL. */ /* Loop through the topic looking for matches to this ACL. */
@ -345,6 +357,10 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte
} }
mosquitto_topic_matches_sub(acl_root->topic, topic, &result); mosquitto_topic_matches_sub(acl_root->topic, topic, &result);
if(result){ if(result){
if(acl_root->access == MOSQ_ACL_NONE){
/* Access was explicitly denied for this topic. */
return MOSQ_ERR_ACL_DENIED;
}
if(access & acl_root->access){ if(access & acl_root->access){
/* And access is allowed. */ /* And access is allowed. */
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;
@ -374,7 +390,7 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte
} }
} }
/* Loop through all pattern ACLs. */ /* Loop through all pattern ACLs. ACL denial patterns are iterated over first. */
if(!context->id) return MOSQ_ERR_ACL_DENIED; if(!context->id) return MOSQ_ERR_ACL_DENIED;
clen = strlen(context->id); clen = strlen(context->id);
@ -418,6 +434,10 @@ int mosquitto_acl_check_default(struct mosquitto_db *db, struct mosquitto *conte
mosquitto_topic_matches_sub(local_acl, topic, &result); mosquitto_topic_matches_sub(local_acl, topic, &result);
mosquitto__free(local_acl); mosquitto__free(local_acl);
if(result){ if(result){
if(acl_root->access == MOSQ_ACL_NONE){
/* Access was explicitly denied for this topic pattern. */
return MOSQ_ERR_ACL_DENIED;
}
if(access & acl_root->access){ if(access & acl_root->access){
/* And access is allowed. */ /* And access is allowed. */
return MOSQ_ERR_SUCCESS; return MOSQ_ERR_SUCCESS;
@ -501,6 +521,8 @@ static int aclfile__parse(struct mosquitto_db *db, struct mosquitto__security_op
access = MOSQ_ACL_WRITE; access = MOSQ_ACL_WRITE;
}else if(!strcmp(access_s, "readwrite")){ }else if(!strcmp(access_s, "readwrite")){
access = MOSQ_ACL_READ | MOSQ_ACL_WRITE; access = MOSQ_ACL_READ | MOSQ_ACL_WRITE;
}else if(!strcmp(access_s, "deny")){
access = MOSQ_ACL_NONE;
}else{ }else{
log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid topic access type \"%s\" in acl_file \"%s\".", access_s, security_opts->acl_file); log__printf(NULL, MOSQ_LOG_ERR, "Error: Invalid topic access type \"%s\" in acl_file \"%s\".", access_s, security_opts->acl_file);
mosquitto__free(user); mosquitto__free(user);

@ -13,12 +13,15 @@ def write_config(filename, port, per_listener):
def write_acl(filename, global_en, user_en, pattern_en): def write_acl(filename, global_en, user_en, pattern_en):
with open(filename, 'w') as f: with open(filename, 'w') as f:
if global_en: if global_en:
f.write('topic readwrite topic/global\n') f.write('topic readwrite topic/global/#\n')
f.write('topic deny topic/global/except\n')
if user_en: if user_en:
f.write('user username\n') f.write('user username\n')
f.write('topic readwrite topic/username\n') f.write('topic readwrite topic/username/#\n')
f.write('topic deny topic/username/except\n')
if pattern_en: if pattern_en:
f.write('pattern readwrite pattern/%u\n') f.write('pattern readwrite pattern/%u/#\n')
f.write('pattern deny pattern/%u/except\n')
@ -73,12 +76,15 @@ def acl_test(port, per_listener, global_en, user_en, pattern_en):
if global_en: if global_en:
single_test(port, per_listener, username=None, topic="topic/global", expect_deny=False) single_test(port, per_listener, username=None, topic="topic/global", expect_deny=False)
single_test(port, per_listener, username="username", topic="topic/global", expect_deny=True) single_test(port, per_listener, username="username", topic="topic/global", expect_deny=True)
single_test(port, per_listener, username=None, topic="topic/global/except", expect_deny=True)
if user_en: if user_en:
single_test(port, per_listener, username=None, topic="topic/username", expect_deny=True) single_test(port, per_listener, username=None, topic="topic/username", expect_deny=True)
single_test(port, per_listener, username="username", topic="topic/username", expect_deny=False) single_test(port, per_listener, username="username", topic="topic/username", expect_deny=False)
single_test(port, per_listener, username="username", topic="topic/username/except", expect_deny=True)
if pattern_en: if pattern_en:
single_test(port, per_listener, username=None, topic="pattern/username", expect_deny=True) single_test(port, per_listener, username=None, topic="pattern/username", expect_deny=True)
single_test(port, per_listener, username="username", topic="pattern/username", expect_deny=False) single_test(port, per_listener, username="username", topic="pattern/username", expect_deny=False)
single_test(port, per_listener, username="username", topic="pattern/username/except", expect_deny=True)
def do_test(port, per_listener): def do_test(port, per_listener):
try: try:

Loading…
Cancel
Save