fix(authz_mnesia): add a soft limit in the API for the length of ACL rules
This commit is contained in:
parent
07a3fbaf1a
commit
5532c7b0a6
|
@ -469,8 +469,8 @@ users(get, #{query_string := QueryString}) ->
|
||||||
{200, Result}
|
{200, Result}
|
||||||
end;
|
end;
|
||||||
users(post, #{body := Body}) when is_list(Body) ->
|
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(
|
lists:foreach(
|
||||||
fun(#{<<"username">> := Username, <<"rules">> := Rules}) ->
|
fun(#{<<"username">> := Username, <<"rules">> := Rules}) ->
|
||||||
emqx_authz_mnesia:store_rules({username, Username}, Rules)
|
emqx_authz_mnesia:store_rules({username, Username}, Rules)
|
||||||
|
@ -478,10 +478,16 @@ users(post, #{body := Body}) when is_list(Body) ->
|
||||||
Body
|
Body
|
||||||
),
|
),
|
||||||
{204};
|
{204};
|
||||||
Exists ->
|
{error, rules_too_long} ->
|
||||||
|
{400, #{
|
||||||
|
code => <<"BAD_REQUEST">>,
|
||||||
|
message =>
|
||||||
|
<<"The length of rules exceeds the maximum limit.">>
|
||||||
|
}};
|
||||||
|
{error, {already_exists, Exists}} ->
|
||||||
{409, #{
|
{409, #{
|
||||||
code => <<"ALREADY_EXISTS">>,
|
code => <<"ALREADY_EXISTS">>,
|
||||||
message => binfmt("Users '~ts' already exist", [binjoin(Exists)])
|
message => binfmt("User '~ts' already exist", [binjoin(Exists)])
|
||||||
}}
|
}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -507,8 +513,8 @@ clients(get, #{query_string := QueryString}) ->
|
||||||
{200, Result}
|
{200, Result}
|
||||||
end;
|
end;
|
||||||
clients(post, #{body := Body}) when is_list(Body) ->
|
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(
|
lists:foreach(
|
||||||
fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) ->
|
fun(#{<<"clientid">> := ClientID, <<"rules">> := Rules}) ->
|
||||||
emqx_authz_mnesia:store_rules({clientid, ClientID}, Rules)
|
emqx_authz_mnesia:store_rules({clientid, ClientID}, Rules)
|
||||||
|
@ -516,10 +522,16 @@ clients(post, #{body := Body}) when is_list(Body) ->
|
||||||
Body
|
Body
|
||||||
),
|
),
|
||||||
{204};
|
{204};
|
||||||
Exists ->
|
{error, rules_too_long} ->
|
||||||
|
{400, #{
|
||||||
|
code => <<"BAD_REQUEST">>,
|
||||||
|
message =>
|
||||||
|
<<"The length of rules exceeds the maximum limit.">>
|
||||||
|
}};
|
||||||
|
{error, {already_exists, Exists}} ->
|
||||||
{409, #{
|
{409, #{
|
||||||
code => <<"ALREADY_EXISTS">>,
|
code => <<"ALREADY_EXISTS">>,
|
||||||
message => binfmt("Clients '~ts' already exist", [binjoin(Exists)])
|
message => binfmt("Client '~ts' already exist", [binjoin(Exists)])
|
||||||
}}
|
}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -583,8 +595,17 @@ all(get, _) ->
|
||||||
}}
|
}}
|
||||||
end;
|
end;
|
||||||
all(post, #{body := #{<<"rules">> := Rules}}) ->
|
all(post, #{body := #{<<"rules">> := Rules}}) ->
|
||||||
emqx_authz_mnesia:store_rules(all, Rules),
|
case ensure_rules_len(Rules) of
|
||||||
{204};
|
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, _) ->
|
all(delete, _) ->
|
||||||
emqx_authz_mnesia:store_rules(all, []),
|
emqx_authz_mnesia:store_rules(all, []),
|
||||||
{204}.
|
{204}.
|
||||||
|
@ -700,24 +721,53 @@ rules_example({ExampleName, ExampleType}) ->
|
||||||
}
|
}
|
||||||
}.
|
}.
|
||||||
|
|
||||||
ensure_all_not_exists(Key, Type, Cfgs) ->
|
ensure_rules_len(Rules) ->
|
||||||
lists:foldl(
|
emqx_authz_api_sources:with_source(
|
||||||
fun(#{Key := Id}, Acc) ->
|
?AUTHZ_TYPE_BIN,
|
||||||
case emqx_authz_mnesia:get_rules({Type, Id}) of
|
fun(#{<<"max_rules_len">> := MaxLen}) ->
|
||||||
not_found ->
|
ensure_rules_len(Rules, MaxLen)
|
||||||
Acc;
|
end
|
||||||
_ ->
|
|
||||||
[Id | Acc]
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
[],
|
|
||||||
Cfgs
|
|
||||||
).
|
).
|
||||||
|
|
||||||
|
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]) ->
|
binjoin([Bin]) ->
|
||||||
Bin;
|
Bin;
|
||||||
binjoin(Bins) ->
|
binjoin(Bins) when is_list(Bins) ->
|
||||||
binjoin(Bins, <<>>).
|
binjoin(Bins, <<>>);
|
||||||
|
binjoin(Bin) ->
|
||||||
|
Bin.
|
||||||
|
|
||||||
binjoin([H | T], Acc) ->
|
binjoin([H | T], Acc) ->
|
||||||
binjoin(T, <<H/binary, $,, Acc/binary>>);
|
binjoin(T, <<H/binary, $,, Acc/binary>>);
|
||||||
|
|
|
@ -30,12 +30,24 @@
|
||||||
namespace/0
|
namespace/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(MAX_RULES_LEN, 100).
|
||||||
|
|
||||||
namespace() -> "authz".
|
namespace() -> "authz".
|
||||||
|
|
||||||
type() -> ?AUTHZ_TYPE.
|
type() -> ?AUTHZ_TYPE.
|
||||||
|
|
||||||
fields(builtin_db) ->
|
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() ->
|
source_refs() ->
|
||||||
[?R_REF(builtin_db)].
|
[?R_REF(builtin_db)].
|
||||||
|
|
|
@ -36,7 +36,7 @@ init_per_suite(Config) ->
|
||||||
{emqx_conf,
|
{emqx_conf,
|
||||||
"authorization.cache { enable = false },"
|
"authorization.cache { enable = false },"
|
||||||
"authorization.no_match = deny,"
|
"authorization.no_match = deny,"
|
||||||
"authorization.sources = [{type = built_in_database}]"},
|
"authorization.sources = [{type = built_in_database, max_rules_len = 5}]"},
|
||||||
emqx,
|
emqx,
|
||||||
emqx_auth,
|
emqx_auth,
|
||||||
emqx_auth_mnesia,
|
emqx_auth_mnesia,
|
||||||
|
@ -66,6 +66,14 @@ t_api(_) ->
|
||||||
[?USERNAME_RULES_EXAMPLE]
|
[?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, _} =
|
{ok, 409, _} =
|
||||||
request(
|
request(
|
||||||
post,
|
post,
|
||||||
|
@ -171,6 +179,13 @@ t_api(_) ->
|
||||||
[?CLIENTID_RULES_EXAMPLE]
|
[?CLIENTID_RULES_EXAMPLE]
|
||||||
),
|
),
|
||||||
|
|
||||||
|
{ok, 400, _} =
|
||||||
|
request(
|
||||||
|
post,
|
||||||
|
uri(["authorization", "sources", "built_in_database", "rules", "clients"]),
|
||||||
|
[dup_rules_example(?CLIENTID_RULES_EXAMPLE)]
|
||||||
|
),
|
||||||
|
|
||||||
{ok, 409, _} =
|
{ok, 409, _} =
|
||||||
request(
|
request(
|
||||||
post,
|
post,
|
||||||
|
@ -238,6 +253,14 @@ t_api(_) ->
|
||||||
uri(["authorization", "sources", "built_in_database", "rules", "all"]),
|
uri(["authorization", "sources", "built_in_database", "rules", "all"]),
|
||||||
?ALL_RULES_EXAMPLE
|
?ALL_RULES_EXAMPLE
|
||||||
),
|
),
|
||||||
|
|
||||||
|
{ok, 400, _} =
|
||||||
|
request(
|
||||||
|
post,
|
||||||
|
uri(["authorization", "sources", "built_in_database", "rules", "all"]),
|
||||||
|
dup_rules_example(?ALL_RULES_EXAMPLE)
|
||||||
|
),
|
||||||
|
|
||||||
{ok, 200, Request7} =
|
{ok, 200, Request7} =
|
||||||
request(
|
request(
|
||||||
get,
|
get,
|
||||||
|
@ -491,3 +514,10 @@ replace_parts(Parts, Replacements) ->
|
||||||
end,
|
end,
|
||||||
Parts
|
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}.
|
||||||
|
|
|
@ -6,4 +6,7 @@ builtin_db.desc:
|
||||||
builtin_db.label:
|
builtin_db.label:
|
||||||
"""Builtin Database"""
|
"""Builtin Database"""
|
||||||
|
|
||||||
|
max_rules_len.desc:
|
||||||
|
"""Maximum rule length per client/user. Note that performance may decrease as rule length increases."""
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue