Merge pull request #6314 from JimMoen/fix-re-escape

fix(authz): placehodler regular escape
This commit is contained in:
tigercl 2021-11-30 10:21:28 +08:00 committed by GitHub
commit 36cece7a49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 79 additions and 52 deletions

View File

@ -218,4 +218,3 @@ parse(TopicFilter = <<"$share/", Rest/binary>>, Options) ->
end; end;
parse(TopicFilter, Options) -> parse(TopicFilter, Options) ->
{TopicFilter, Options}. {TopicFilter, Options}.

View File

@ -20,6 +20,7 @@
-compile(nowarn_export_all). -compile(nowarn_export_all).
-include_lib("eunit/include/eunit.hrl"). -include_lib("eunit/include/eunit.hrl").
-include_lib("emqx/include/emqx_placeholder.hrl").
-import(emqx_topic, -import(emqx_topic,
[ wildcard/1 [ wildcard/1
@ -183,9 +184,11 @@ t_feed_var(_) ->
?assertEqual(<<"$queue/client/clientId">>, ?assertEqual(<<"$queue/client/clientId">>,
feed_var(<<"$c">>, <<"clientId">>, <<"$queue/client/$c">>)), feed_var(<<"$c">>, <<"clientId">>, <<"$queue/client/$c">>)),
?assertEqual(<<"username/test/client/x">>, ?assertEqual(<<"username/test/client/x">>,
feed_var(<<"%u">>, <<"test">>, <<"username/%u/client/x">>)), feed_var( ?PH_USERNAME, <<"test">>
, <<"username/", ?PH_USERNAME/binary, "/client/x">>)),
?assertEqual(<<"username/test/client/clientId">>, ?assertEqual(<<"username/test/client/clientId">>,
feed_var(<<"%c">>, <<"clientId">>, <<"username/test/client/%c">>)). feed_var( ?PH_CLIENTID, <<"clientId">>
, <<"username/test/client/", ?PH_CLIENTID/binary>>)).
long_topic() -> long_topic() ->
iolist_to_binary([[integer_to_list(I), "/"] || I <- lists:seq(0, 66666)]). iolist_to_binary([[integer_to_list(I), "/"] || I <- lists:seq(0, 66666)]).

View File

@ -23,7 +23,7 @@ authz:{
keyfile: "etc/certs/client-key.pem" keyfile: "etc/certs/client-key.pem"
} }
} }
sql: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = '%a' or username = '%u' or clientid = '%c'" sql: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = ${peerhost} or username = ${username} or clientid = ${clientid}"
}, },
{ {
type: postgresql type: postgresql
@ -36,7 +36,7 @@ authz:{
auto_reconnect: true auto_reconnect: true
ssl: {enable: false} ssl: {enable: false}
} }
sql: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'" sql: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = ${peerhost} or username = ${username} or username = '$all' or clientid = ${clientid}"
}, },
{ {
type: redis type: redis
@ -48,7 +48,7 @@ authz:{
auto_reconnect: true auto_reconnect: true
ssl: {enable: false} ssl: {enable: false}
} }
cmd: "HGETALL mqtt_authz:%u" cmd: "HGETALL mqtt_authz:${username}"
}, },
{ {
principal: {username: "^admin?"} principal: {username: "^admin?"}

View File

@ -22,7 +22,7 @@ authorization {
# certfile: "{{ platform_etc_dir }}/certs/client-cert.pem" # certfile: "{{ platform_etc_dir }}/certs/client-cert.pem"
# keyfile: "{{ platform_etc_dir }}/certs/client-key.pem" # keyfile: "{{ platform_etc_dir }}/certs/client-key.pem"
# } # }
# query: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = '%a' or username = '%u' or clientid = '%c'" # query: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = ${peerhost} or username = ${username} or clientid = ${clientid}"
# }, # },
# { # {
# type: postgresql # type: postgresql
@ -33,7 +33,7 @@ authorization {
# password: public # password: public
# auto_reconnect: true # auto_reconnect: true
# ssl: {enable: false} # ssl: {enable: false}
# query: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = '%a' or username = '%u' or username = '$all' or clientid = '%c'" # query: "select ipaddress, username, clientid, action, permission, topic from mqtt_authz where ipaddr = ${peerhost} or username = ${username} or username = '$all' or clientid = ${clientid}"
# }, # },
# { # {
# type: redis # type: redis
@ -43,7 +43,7 @@ authorization {
# password: public # password: public
# auto_reconnect: true # auto_reconnect: true
# ssl: {enable: false} # ssl: {enable: false}
# cmd: "HGETALL mqtt_authz:%u" # cmd: "HGETALL mqtt_authz:${username}"
# }, # },
# { # {
# type: mongodb # type: mongodb
@ -53,7 +53,7 @@ authorization {
# database: mqtt # database: mqtt
# ssl: {enable: false} # ssl: {enable: false}
# collection: mqtt_authz # collection: mqtt_authz
# selector: { "$or": [ { "username": "%u" }, { "clientid": "%c" } ] } # selector: { "$or": [ { "username": "${username}" }, { "clientid": "${clientid}" } ] }
# }, # },
{ {
type: built-in-database type: built-in-database

View File

@ -40,6 +40,8 @@
-export([acl_conf_file/0]). -export([acl_conf_file/0]).
-export([ph_to_re/1]).
-spec(register_metrics() -> ok). -spec(register_metrics() -> ok).
register_metrics() -> register_metrics() ->
lists:foreach(fun emqx_metrics:ensure/1, ?AUTHZ_METRICS). lists:foreach(fun emqx_metrics:ensure/1, ?AUTHZ_METRICS).
@ -64,11 +66,14 @@ move(Type, Cmd) ->
move(Type, Cmd, #{}). move(Type, Cmd, #{}).
move(Type, #{<<"before">> := Before}, Opts) -> move(Type, #{<<"before">> := Before}, Opts) ->
emqx:update_config(?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))}, Opts); emqx:update_config( ?CONF_KEY_PATH
, {?CMD_MOVE, type(Type), ?CMD_MOVE_BEFORE(type(Before))}, Opts);
move(Type, #{<<"after">> := After}, Opts) -> move(Type, #{<<"after">> := After}, Opts) ->
emqx:update_config(?CONF_KEY_PATH, {?CMD_MOVE, type(Type), ?CMD_MOVE_AFTER(type(After))}, Opts); emqx:update_config( ?CONF_KEY_PATH
, {?CMD_MOVE, type(Type), ?CMD_MOVE_AFTER(type(After))}, Opts);
move(Type, Position, Opts) -> move(Type, Position, Opts) ->
emqx:update_config(?CONF_KEY_PATH, {?CMD_MOVE, type(Type), Position}, Opts). emqx:update_config( ?CONF_KEY_PATH
, {?CMD_MOVE, type(Type), Position}, Opts).
update(Cmd, Sources) -> update(Cmd, Sources) ->
update(Cmd, Sources, #{}). update(Cmd, Sources, #{}).
@ -155,7 +160,8 @@ do_post_update({{?CMD_REPLACE, Type}, Source}, _NewSources) when is_map(Source)
{OldSource, Front, Rear} = take(Type, OldInitedSources), {OldSource, Front, Rear} = take(Type, OldInitedSources),
ok = ensure_resource_deleted(OldSource), ok = ensure_resource_deleted(OldSource),
InitedSources = init_sources(check_sources([Source])), InitedSources = init_sources(check_sources([Source])),
ok = emqx_hooks:put('client.authorize', {?MODULE, authorize, [Front ++ InitedSources ++ Rear]}, -1), ok = emqx_hooks:put( 'client.authorize'
, {?MODULE, authorize, [Front ++ InitedSources ++ Rear]}, -1),
ok = emqx_authz_cache:drain_cache(); ok = emqx_authz_cache:drain_cache();
do_post_update({{?CMD_DELETE, Type}, _Source}, _NewSources) -> do_post_update({{?CMD_DELETE, Type}, _Source}, _NewSources) ->
OldInitedSources = lookup(), OldInitedSources = lookup(),
@ -267,7 +273,7 @@ init_source(#{type := DB,
{error, Reason} -> error({load_config_error, Reason}); {error, Reason} -> error({load_config_error, Reason});
Id -> Source#{annotations => Id -> Source#{annotations =>
#{id => Id, #{id => Id,
query => Mod:parse_query(SQL) query => erlang:apply(Mod, parse_query, [SQL])
} }
} }
end. end.
@ -277,22 +283,36 @@ init_source(#{type := DB,
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% @doc Check AuthZ %% @doc Check AuthZ
-spec(authorize(emqx_types:clientinfo(), emqx_types:all(), emqx_types:topic(), allow | deny, sources()) -spec(authorize( emqx_types:clientinfo()
, emqx_types:all()
, emqx_types:topic()
, allow | deny
, sources())
-> {stop, allow} | {ok, deny}). -> {stop, allow} | {ok, deny}).
authorize(#{username := Username, authorize(#{username := Username,
peerhost := IpAddress peerhost := IpAddress
} = Client, PubSub, Topic, DefaultResult, Sources) -> } = Client, PubSub, Topic, DefaultResult, Sources) ->
case do_authorize(Client, PubSub, Topic, Sources) of case do_authorize(Client, PubSub, Topic, Sources) of
{matched, allow} -> {matched, allow} ->
?SLOG(info, #{msg => "authorization_permission_allowed", username => Username, ipaddr => IpAddress, topic => Topic}), ?SLOG(info, #{msg => "authorization_permission_allowed",
username => Username,
ipaddr => IpAddress,
topic => Topic}),
emqx_metrics:inc(?AUTHZ_METRICS(allow)), emqx_metrics:inc(?AUTHZ_METRICS(allow)),
{stop, allow}; {stop, allow};
{matched, deny} -> {matched, deny} ->
?SLOG(info, #{msg => "authorization_permission_denied", username => Username, ipaddr => IpAddress, topic => Topic}), ?SLOG(info, #{msg => "authorization_permission_denied",
username => Username,
ipaddr => IpAddress,
topic => Topic}),
emqx_metrics:inc(?AUTHZ_METRICS(deny)), emqx_metrics:inc(?AUTHZ_METRICS(deny)),
{stop, deny}; {stop, deny};
nomatch -> nomatch ->
?SLOG(info, #{msg => "authorization_failed_nomatch", username => Username, ipaddr => IpAddress, topic => Topic, reason => "no-match rule"}), ?SLOG(info, #{msg => "authorization_failed_nomatch",
username => Username,
ipaddr => IpAddress,
topic => Topic,
reason => "no-match rule"}),
{stop, DefaultResult} {stop, DefaultResult}
end. end.
@ -309,7 +329,7 @@ do_authorize(Client, PubSub, Topic, [#{type := file} = F | Tail]) ->
do_authorize(Client, PubSub, Topic, do_authorize(Client, PubSub, Topic,
[Connector = #{type := Type} | Tail] ) -> [Connector = #{type := Type} | Tail] ) ->
Mod = authz_module(Type), Mod = authz_module(Type),
case Mod:authorize(Client, PubSub, Topic, Connector) of case erlang:apply(Mod, authorize, [Client, PubSub, Topic, Connector]) of
nomatch -> do_authorize(Client, PubSub, Topic, Tail); nomatch -> do_authorize(Client, PubSub, Topic, Tail);
Matched -> Matched Matched -> Matched
end. end.
@ -381,8 +401,12 @@ type(postgresql) -> postgresql;
type(<<"postgresql">>) -> postgresql; type(<<"postgresql">>) -> postgresql;
type('built-in-database') -> 'built-in-database'; type('built-in-database') -> 'built-in-database';
type(<<"built-in-database">>) -> 'built-in-database'; type(<<"built-in-database">>) -> 'built-in-database';
type(Unknown) -> error({unknown_authz_source_type, Unknown}). % should never happend if the input is type-checked by hocon schema %% should never happend if the input is type-checked by hocon schema
type(Unknown) -> error({unknown_authz_source_type, Unknown}).
%% @doc where the acl.conf file is stored. %% @doc where the acl.conf file is stored.
acl_conf_file() -> acl_conf_file() ->
filename:join([emqx:data_dir(), "authz", "acl.conf"]). filename:join([emqx:data_dir(), "authz", "acl.conf"]).
ph_to_re(VarPH) ->
re:replace(VarPH, "[\\$\\{\\}]", "\\\\&", [global, {return, list}]).

View File

@ -87,19 +87,19 @@ replvar(Str0, PubSub, Topic,
}) when is_list(Str0); }) when is_list(Str0);
is_binary(Str0) -> is_binary(Str0) ->
NTopic = emqx_http_lib:uri_encode(Topic), NTopic = emqx_http_lib:uri_encode(Topic),
Str1 = re:replace( Str0, ?PH_S_CLIENTID Str1 = re:replace( Str0, emqx_authz:ph_to_re(?PH_S_CLIENTID)
, Clientid, [global, {return, binary}]), , Clientid, [global, {return, binary}]),
Str2 = re:replace( Str1, ?PH_S_USERNAME Str2 = re:replace( Str1, emqx_authz:ph_to_re(?PH_S_USERNAME)
, bin(Username), [global, {return, binary}]), , bin(Username), [global, {return, binary}]),
Str3 = re:replace( Str2, ?PH_S_HOST Str3 = re:replace( Str2, emqx_authz:ph_to_re(?PH_S_HOST)
, inet_parse:ntoa(IpAddress), [global, {return, binary}]), , inet_parse:ntoa(IpAddress), [global, {return, binary}]),
Str4 = re:replace( Str3, ?PH_S_PROTONAME Str4 = re:replace( Str3, emqx_authz:ph_to_re(?PH_S_PROTONAME)
, bin(Protocol), [global, {return, binary}]), , bin(Protocol), [global, {return, binary}]),
Str5 = re:replace( Str4, ?PH_S_MOUNTPOINT Str5 = re:replace( Str4, emqx_authz:ph_to_re(?PH_S_MOUNTPOINT)
, Mountpoint, [global, {return, binary}]), , Mountpoint, [global, {return, binary}]),
Str6 = re:replace( Str5, ?PH_S_TOPIC Str6 = re:replace( Str5, emqx_authz:ph_to_re(?PH_S_TOPIC)
, NTopic, [global, {return, binary}]), , NTopic, [global, {return, binary}]),
Str7 = re:replace( Str6, ?PH_S_ACTION Str7 = re:replace( Str6, emqx_authz:ph_to_re(?PH_S_ACTION)
, bin(PubSub), [global, {return, binary}]), , bin(PubSub), [global, {return, binary}]),
Str7. Str7.

View File

@ -76,11 +76,11 @@ replvar(Selector, #{clientid := Clientid,
end || M <- V], end || M <- V],
AccIn); AccIn);
InFun(K, V, AccIn) when is_binary(V) -> InFun(K, V, AccIn) when is_binary(V) ->
V1 = re:replace( V, ?PH_S_CLIENTID V1 = re:replace( V, emqx_authz:ph_to_re(?PH_S_CLIENTID)
, bin(Clientid), [global, {return, binary}]), , bin(Clientid), [global, {return, binary}]),
V2 = re:replace( V1, ?PH_S_USERNAME V2 = re:replace( V1, emqx_authz:ph_to_re(?PH_S_USERNAME)
, bin(Username), [global, {return, binary}]), , bin(Username), [global, {return, binary}]),
V3 = re:replace( V2, ?PH_S_HOST V3 = re:replace( V2, emqx_authz:ph_to_re(?PH_S_HOST)
, inet_parse:ntoa(IpAddress), [global, {return, binary}]), , inet_parse:ntoa(IpAddress), [global, {return, binary}]),
maps:put(K, V3, AccIn); maps:put(K, V3, AccIn);
InFun(K, V, AccIn) -> maps:put(K, V, AccIn) InFun(K, V, AccIn) -> maps:put(K, V, AccIn)

View File

@ -71,8 +71,9 @@ replvar(Cmd, Client = #{username := Username}) ->
replvar(Cmd, _) -> replvar(Cmd, _) ->
Cmd. Cmd.
repl(S, _Var, undefined) -> repl(S, _VarPH, undefined) ->
S; S;
repl(S, Var, Val) -> repl(S, VarPH, Val) ->
NVal = re:replace(Val, "&", "\\\\&", [global, {return, list}]), NVal = re:replace(Val, "&", "\\\\&", [global, {return, list}]),
re:replace(S, Var, NVal, [{return, list}]). NVarPH = emqx_authz:ph_to_re(VarPH),
re:replace(S, NVarPH, NVal, [{return, list}]).