Просмотр исходного кода

fix(authz_mnesia): add a soft limit in the API for the length of ACL rules

firest 1 год назад
Родитель
Сommit
5532c7b0a6

+ 74 - 24
apps/emqx_auth_mnesia/src/emqx_authz_api_mnesia.erl

@@ -469,8 +469,8 @@ users(get, #{query_string := QueryString}) ->
             {200, Result}
     end;
 users(post, #{body := Body}) when is_list(Body) ->
-    case ensure_all_not_exists(<<"username">>, username, Body) of
-        [] ->
+    case ensure_rules_is_valid(<<"username">>, username, Body) of
+        ok ->
             lists:foreach(
                 fun(#{<<"username">> := Username, <<"rules">> := Rules}) ->
                     emqx_authz_mnesia:store_rules({username, Username}, Rules)
@@ -478,10 +478,16 @@ users(post, #{body := Body}) when is_list(Body) ->
                 Body
             ),
             {204};
-        Exists ->
+        {error, rules_too_long} ->
+            {400, #{
+                code => <<"BAD_REQUEST">>,
+                message =>
+                    <<"The length of rules exceeds the maximum limit.">>
+            }};
+        {error, {already_exists, Exists}} ->
             {409, #{
                 code => <<"ALREADY_EXISTS">>,
-                message => binfmt("Users '~ts' already exist", [binjoin(Exists)])
+                message => binfmt("User '~ts' already exist", [binjoin(Exists)])
             }}
     end.
 
@@ -507,8 +513,8 @@ clients(get, #{query_string := QueryString}) ->
             {200, Result}
     end;
 clients(post, #{body := Body}) when is_list(Body) ->
-    case ensure_all_not_exists(<<"clientid">>, clientid, Body) of
-        [] ->
+    case ensure_rules_is_valid(<<"clientid">>, clientid, Body) of
+        ok ->
             lists:foreach(
                 fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) ->
                     emqx_authz_mnesia:store_rules({clientid, ClientID}, Rules)
@@ -516,10 +522,16 @@ clients(post, #{body := Body}) when is_list(Body) ->
                 Body
             ),
             {204};
-        Exists ->
+        {error, rules_too_long} ->
+            {400, #{
+                code => <<"BAD_REQUEST">>,
+                message =>
+                    <<"The length of rules exceeds the maximum limit.">>
+            }};
+        {error, {already_exists, Exists}} ->
             {409, #{
                 code => <<"ALREADY_EXISTS">>,
-                message => binfmt("Clients '~ts' already exist", [binjoin(Exists)])
+                message => binfmt("Client '~ts' already exist", [binjoin(Exists)])
             }}
     end.
 
@@ -583,8 +595,17 @@ all(get, _) ->
             }}
     end;
 all(post, #{body := #{<<"rules">> := Rules}}) ->
-    emqx_authz_mnesia:store_rules(all, Rules),
-    {204};
+    case ensure_rules_len(Rules) of
+        ok ->
+            emqx_authz_mnesia:store_rules(all, Rules),
+            {204};
+        _ ->
+            {400, #{
+                code => <<"BAD_REQUEST">>,
+                message =>
+                    <<"The length of rules exceeds the maximum limit.">>
+            }}
+    end;
 all(delete, _) ->
     emqx_authz_mnesia:store_rules(all, []),
     {204}.
@@ -700,24 +721,53 @@ rules_example({ExampleName, ExampleType}) ->
         }
     }.
 
-ensure_all_not_exists(Key, Type, Cfgs) ->
-    lists:foldl(
-        fun(#{Key := Id}, Acc) ->
-            case emqx_authz_mnesia:get_rules({Type, Id}) of
-                not_found ->
-                    Acc;
-                _ ->
-                    [Id | Acc]
-            end
-        end,
-        [],
-        Cfgs
+ensure_rules_len(Rules) ->
+    emqx_authz_api_sources:with_source(
+        ?AUTHZ_TYPE_BIN,
+        fun(#{<<"max_rules_len">> := MaxLen}) ->
+            ensure_rules_len(Rules, MaxLen)
+        end
     ).
 
+ensure_rules_len(Rules, MaxLen) ->
+    case erlang:length(Rules) =< MaxLen of
+        true ->
+            ok;
+        _ ->
+            {error, rules_too_long}
+    end.
+
+ensure_rules_is_valid(Key, Type, Cfgs) ->
+    MaxLen = emqx_authz_api_sources:with_source(
+        ?AUTHZ_TYPE_BIN,
+        fun(#{<<"max_rules_len">> := MaxLen}) ->
+            MaxLen
+        end
+    ),
+    ensure_rules_is_valid(Key, Type, MaxLen, Cfgs).
+
+ensure_rules_is_valid(Key, Type, MaxLen, [Cfg | Cfgs]) ->
+    #{Key := Id, <<"rules">> := Rules} = Cfg,
+    case emqx_authz_mnesia:get_rules({Type, Id}) of
+        not_found ->
+            case ensure_rules_len(Rules, MaxLen) of
+                ok ->
+                    ensure_rules_is_valid(Key, Type, MaxLen, Cfgs);
+                Error ->
+                    Error
+            end;
+        _ ->
+            {error, {already_exists, Id}}
+    end;
+ensure_rules_is_valid(_Key, _Type, _MaxLen, []) ->
+    ok.
+
 binjoin([Bin]) ->
     Bin;
-binjoin(Bins) ->
-    binjoin(Bins, <<>>).
+binjoin(Bins) when is_list(Bins) ->
+    binjoin(Bins, <<>>);
+binjoin(Bin) ->
+    Bin.
 
 binjoin([H | T], Acc) ->
     binjoin(T, <<H/binary, $,, Acc/binary>>);

+ 13 - 1
apps/emqx_auth_mnesia/src/emqx_authz_mnesia_schema.erl

@@ -30,12 +30,24 @@
     namespace/0
 ]).
 
+-define(MAX_RULES_LEN, 100).
+
 namespace() -> "authz".
 
 type() -> ?AUTHZ_TYPE.
 
 fields(builtin_db) ->
-    emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE).
+    emqx_authz_schema:authz_common_fields(?AUTHZ_TYPE) ++
+        [
+            {max_rules_len,
+                ?HOCON(
+                    pos_integer(),
+                    #{
+                        default => ?MAX_RULES_LEN,
+                        desc => ?DESC(max_rules_len)
+                    }
+                )}
+        ].
 
 source_refs() ->
     [?R_REF(builtin_db)].

+ 31 - 1
apps/emqx_auth_mnesia/test/emqx_authz_api_mnesia_SUITE.erl

@@ -36,7 +36,7 @@ init_per_suite(Config) ->
             {emqx_conf,
                 "authorization.cache { enable = false },"
                 "authorization.no_match = deny,"
-                "authorization.sources = [{type = built_in_database}]"},
+                "authorization.sources = [{type = built_in_database, max_rules_len = 5}]"},
             emqx,
             emqx_auth,
             emqx_auth_mnesia,
@@ -66,6 +66,14 @@ t_api(_) ->
             [?USERNAME_RULES_EXAMPLE]
         ),
 
+    %% check length limit
+    {ok, 400, _} =
+        request(
+            post,
+            uri(["authorization", "sources", "built_in_database", "rules", "users"]),
+            [dup_rules_example(?USERNAME_RULES_EXAMPLE)]
+        ),
+
     {ok, 409, _} =
         request(
             post,
@@ -171,6 +179,13 @@ t_api(_) ->
             [?CLIENTID_RULES_EXAMPLE]
         ),
 
+    {ok, 400, _} =
+        request(
+            post,
+            uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
+            [dup_rules_example(?CLIENTID_RULES_EXAMPLE)]
+        ),
+
     {ok, 409, _} =
         request(
             post,
@@ -238,6 +253,14 @@ t_api(_) ->
             uri(["authorization", "sources", "built_in_database", "rules", "all"]),
             ?ALL_RULES_EXAMPLE
         ),
+
+    {ok, 400, _} =
+        request(
+            post,
+            uri(["authorization", "sources", "built_in_database", "rules", "all"]),
+            dup_rules_example(?ALL_RULES_EXAMPLE)
+        ),
+
     {ok, 200, Request7} =
         request(
             get,
@@ -491,3 +514,10 @@ replace_parts(Parts, Replacements) ->
         end,
         Parts
     ).
+
+dup_rules_example(#{username := _, rules := Rules}) ->
+    #{username => user2, rules => Rules ++ Rules};
+dup_rules_example(#{clientid := _, rules := Rules}) ->
+    #{clientid => client2, rules => Rules ++ Rules};
+dup_rules_example(#{rules := Rules}) ->
+    #{rules => Rules ++ Rules}.

+ 3 - 0
rel/i18n/emqx_authz_mnesia_schema.hocon

@@ -6,4 +6,7 @@ builtin_db.desc:
 builtin_db.label:
 """Builtin Database"""
 
+max_rules_len.desc:
+"""Maximum rule length per client/user. Note that performance may decrease as rule length increases."""
+
 }