Merge branch 'feature/add-deny-option-for-acl' of git://github.com/BrandtHill/mosquitto into BrandtHill-feature/add-deny-option-for-acl

This commit is contained in:
Roger A. Light 2020-10-14 11:30:16 +01:00
commit f18f1a08a9
3 changed files with 46 additions and 16 deletions

View File

@ -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">

View File

@ -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;
@ -505,6 +525,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);
rc = MOSQ_ERR_INVAL; rc = MOSQ_ERR_INVAL;

View File

@ -14,12 +14,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')
@ -77,12 +80,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: