From 4791c64b73d520cc4630139be8d9bace7fa3292f Mon Sep 17 00:00:00 2001 From: EMQ-YangM Date: Mon, 28 Mar 2022 16:12:19 +0800 Subject: [PATCH] feat: add rule event 'client.check_authz_complete' --- apps/emqx/src/emqx_access_control.erl | 2 + apps/emqx_authz/src/emqx_authz.erl | 12 +++- .../emqx_rule_engine/src/emqx_rule_events.erl | 56 +++++++++++++++++++ .../test/emqx_rule_engine_SUITE.erl | 35 +++++++++++- 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/apps/emqx/src/emqx_access_control.erl b/apps/emqx/src/emqx_access_control.erl index 565644cf8..081fe5329 100644 --- a/apps/emqx/src/emqx_access_control.erl +++ b/apps/emqx/src/emqx_access_control.erl @@ -60,6 +60,8 @@ check_authorization_cache(ClientInfo, PubSub, Topic) -> emqx_authz_cache:put_authz_cache(PubSub, Topic, AuthzResult), AuthzResult; AuthzResult -> + emqx:run_hook('client.check_authz_complete', + [ClientInfo, PubSub, Topic, AuthzResult, cache]), inc_acl_metrics(cache_hit), AuthzResult end. diff --git a/apps/emqx_authz/src/emqx_authz.erl b/apps/emqx_authz/src/emqx_authz.erl index 00309d560..916b08632 100644 --- a/apps/emqx_authz/src/emqx_authz.erl +++ b/apps/emqx_authz/src/emqx_authz.erl @@ -283,14 +283,18 @@ authorize(#{username := Username, peerhost := IpAddress } = Client, PubSub, Topic, DefaultResult, Sources) -> case do_authorize(Client, PubSub, Topic, Sources) of - {matched, allow} -> + {{matched, allow}, AuthzSource}-> + emqx:run_hook('client.check_authz_complete', + [Client, PubSub, Topic, allow, AuthzSource]), ?SLOG(info, #{msg => "authorization_permission_allowed", username => Username, ipaddr => IpAddress, topic => Topic}), emqx_metrics:inc(?METRIC_ALLOW), {stop, allow}; - {matched, deny} -> + {{matched, deny}, AuthzSource}-> + emqx:run_hook('client.check_authz_complete', + [Client, PubSub, Topic, deny, AuthzSource]), ?SLOG(info, #{msg => "authorization_permission_denied", username => Username, ipaddr => IpAddress, @@ -298,6 +302,8 @@ authorize(#{username := Username, emqx_metrics:inc(?METRIC_DENY), {stop, deny}; nomatch -> + emqx:run_hook('client.check_authz_complete', + [Client, PubSub, Topic, DefaultResult, default]), ?SLOG(info, #{msg => "authorization_failed_nomatch", username => Username, ipaddr => IpAddress, @@ -316,7 +322,7 @@ do_authorize(Client, PubSub, Topic, Module = authz_module(Type), case Module:authorize(Client, PubSub, Topic, Connector) of nomatch -> do_authorize(Client, PubSub, Topic, Tail); - Matched -> Matched + Matched -> {Matched, Type} end. %%-------------------------------------------------------------------- diff --git a/apps/emqx_rule_engine/src/emqx_rule_events.erl b/apps/emqx_rule_engine/src/emqx_rule_events.erl index d85ee5cda..197b03f28 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_events.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_events.erl @@ -34,6 +34,7 @@ -export([ on_client_connected/3 , on_client_disconnected/4 , on_client_connack/4 + , on_client_check_authz_complete/6 , on_session_subscribed/4 , on_session_unsubscribed/4 , on_message_publish/2 @@ -62,6 +63,7 @@ event_names() -> [ 'client.connected' , 'client.disconnected' , 'client.connack' + , 'client.check_authz_complete' , 'session.subscribed' , 'session.unsubscribed' , 'message.publish' @@ -114,6 +116,14 @@ on_client_connack(ConnInfo, Reason, _, Env) -> apply_event('client.connack', fun() -> eventmsg_connack(ConnInfo, Reason) end, Env). +on_client_check_authz_complete(ClientInfo, PubSub, Topic, Result, AuthzSource, Env) -> + apply_event('client.check_authz_complete', + fun() -> eventmsg_check_authz_complete(ClientInfo, + PubSub, + Topic, + Result, + AuthzSource) end, Env). + on_client_disconnected(ClientInfo, Reason, ConnInfo, Env) -> apply_event('client.disconnected', fun() -> eventmsg_disconnected(ClientInfo, ConnInfo, Reason) end, Env). @@ -269,6 +279,21 @@ eventmsg_connack(_ConnInfo = #{ conn_props => printable_maps(ConnProps) }). +eventmsg_check_authz_complete(_ClientInfo = #{ + clientid := ClientId, + username := Username, + peerhost := PeerHost + }, PubSub, Topic, Result, AuthzSource) -> + with_basic_columns('client.check_authz_complete', + #{clientid => ClientId, + username => Username, + peerhost => ntoa(PeerHost), + topic => Topic, + action => PubSub, + authz_source => AuthzSource, + result => Result + }). + eventmsg_sub_or_unsub(Event, _ClientInfo = #{ clientid := ClientId, username := Username, @@ -413,6 +438,7 @@ event_info() -> , event_info_client_connected() , event_info_client_disconnected() , event_info_client_connack() + , event_info_client_check_authz_complete() , event_info_session_subscribed() , event_info_session_unsubscribed() , event_info_delivery_dropped() @@ -477,6 +503,13 @@ event_info_client_connack() -> {<<"client connack">>, <<"连接确认"/utf8>>}, <<"SELECT * FROM \"$events/client_connack\"">> ). +event_info_client_check_authz_complete() -> + event_info_common( + 'client.check_authz_complete', + {<<"client check authz complete">>, <<"鉴权结果"/utf8>>}, + {<<"client check authz complete">>, <<"鉴权结果"/utf8>>}, + <<"SELECT * FROM \"$events/client_check_authz_complete\"">> + ). event_info_session_subscribed() -> event_info_common( 'session.subscribed', @@ -547,6 +580,13 @@ test_columns('client.connack') -> , {<<"username">>, <<"u_emqx">>} , {<<"reason_code">>, <<"sucess">>} ]; +test_columns('client.check_authz_complete') -> + [ {<<"clientid">>, <<"c_emqx">>} + , {<<"username">>, <<"u_emqx">>} + , {<<"topic">>, <<"t/1">>} + , {<<"action">>, <<"publish">>} + , {<<"result">>, <<"allow">>} + ]; test_columns('session.unsubscribed') -> test_columns('session.subscribed'); test_columns('session.subscribed') -> @@ -664,6 +704,18 @@ columns_with_exam('client.connack') -> , {<<"timestamp">>, erlang:system_time(millisecond)} , {<<"node">>, node()} ]; +columns_with_exam('client.check_authz_complete') -> + [ {<<"event">>, 'client.check_authz_complete'} + , {<<"clientid">>, <<"c_emqx">>} + , {<<"username">>, <<"u_emqx">>} + , {<<"peerhost">>, <<"192.168.0.10">>} + , {<<"topic">>, <<"t/a">>} + , {<<"action">>, <<"publish">>} + , {<<"authz_source">>, <<"cache">>} + , {<<"result">>, <<"allow">>} + , {<<"timestamp">>, erlang:system_time(millisecond)} + , {<<"node">>, node()} + ]; columns_with_exam('session.subscribed') -> [ columns_example_props(sub_props) ] ++ columns_message_sub_unsub('session.subscribed'); @@ -775,6 +827,8 @@ ntoa(IpAddr) -> event_name(<<"$events/client_connected", _/binary>>) -> 'client.connected'; event_name(<<"$events/client_disconnected", _/binary>>) -> 'client.disconnected'; event_name(<<"$events/client_connack", _/binary>>) -> 'client.connack'; +event_name(<<"$events/client_check_authz_complete", _/binary>>) -> + 'client.check_authz_complete'; event_name(<<"$events/session_subscribed", _/binary>>) -> 'session.subscribed'; event_name(<<"$events/session_unsubscribed", _/binary>>) -> 'session.unsubscribed'; @@ -788,6 +842,8 @@ event_name(_) -> 'message.publish'. event_topic('client.connected') -> <<"$events/client_connected">>; event_topic('client.disconnected') -> <<"$events/client_disconnected">>; event_topic('client.connack') -> <<"$events/client_connack">>; +event_topic('client.check_authz_complete') -> + <<"$events/client_check_authz_complete">>; event_topic('session.subscribed') -> <<"$events/session_subscribed">>; event_topic('session.unsubscribed') -> <<"$events/session_unsubscribed">>; event_topic('message.delivered') -> <<"$events/message_delivered">>; diff --git a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl index 1b74b3652..d765cc0c2 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl @@ -105,13 +105,24 @@ groups() -> init_per_suite(Config) -> application:load(emqx_conf), - ok = emqx_common_test_helpers:start_apps([emqx_conf, emqx_rule_engine]), + ok = emqx_common_test_helpers:start_apps( + [emqx_conf, emqx_rule_engine, emqx_authz], + fun set_special_configs/1), Config. end_per_suite(_Config) -> emqx_common_test_helpers:stop_apps([emqx_conf, emqx_rule_engine]), ok. +set_special_configs(emqx_authz) -> + {ok, _} = emqx:update_config( + [authorization], + #{<<"no_match">> => atom_to_binary(allow), + <<"cache">> => #{<<"enable">> => atom_to_binary(true)}, + <<"sources">> => []}), + ok; +set_special_configs(_) -> + ok. on_resource_create(_id, _) -> #{}. on_resource_destroy(_id, _) -> ok. on_get_resource_status(_id, _) -> #{}. @@ -140,6 +151,7 @@ init_per_testcase(t_events, Config) -> SQL = "SELECT * FROM \"$events/client_connected\", " "\"$events/client_disconnected\", " "\"$events/client_connack\", " + "\"$events/client_check_authz_complete\", " "\"$events/session_subscribed\", " "\"$events/session_unsubscribed\", " "\"$events/message_acked\", " @@ -361,6 +373,7 @@ client_disconnected(Client, Client2) -> session_subscribed(Client2) -> {ok, _, _} = emqtt:subscribe(Client2, #{'User-Property' => {<<"topic_name">>, <<"t1">>}}, <<"t1">>, 1), verify_event('session.subscribed'), + verify_event('client.check_authz_complete'), ok. session_unsubscribed(Client2) -> {ok, _, _} = emqtt:unsubscribe(Client2, #{'User-Property' => {<<"topic_name">>, <<"t1">>}}, <<"t1">>), @@ -1671,7 +1684,25 @@ verify_event_fields('client.connack', Fields) -> ?assertMatch(#{'Session-Expiry-Interval' := 60}, Properties), ?assert(0 =< TimestampElapse andalso TimestampElapse =< 60*1000), ?assert(0 =< RcvdAtElapse andalso RcvdAtElapse =< 60*1000), - ?assert(EventAt =< Timestamp). + ?assert(EventAt =< Timestamp); + +verify_event_fields('client.check_authz_complete', Fields) -> + #{clientid := ClientId, + action := Action, + result := Result, + topic := Topic, + authz_source := AuthzSource, + username := Username + } = Fields, + ?assertEqual(<<"t1">>, Topic), + ?assert(lists:member(Action, [subscribe, publish])), + ?assert(lists:member(Result, [allow, deny])), + ?assert(lists:member(AuthzSource, [cache, default, file, + http, mongodb, mysql, redis, + postgresql, built_in_database])), + ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])), + ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])). + verify_peername(PeerName) -> case string:split(PeerName, ":") of