feat(events): add new hook && event `client.check_authn_complete`

This commit is contained in:
firest 2024-05-07 16:24:31 +08:00
parent 93d5e77698
commit e4e53844d5
8 changed files with 147 additions and 8 deletions

View File

@ -56,21 +56,21 @@ authenticate(Credential) ->
NotSuperUser = #{is_superuser => false}, NotSuperUser = #{is_superuser => false},
case pre_hook_authenticate(Credential) of case pre_hook_authenticate(Credential) of
ok -> ok ->
inc_authn_metrics(anonymous), on_authentication_complete(Credential, NotSuperUser, anonymous),
{ok, NotSuperUser}; {ok, NotSuperUser};
continue -> continue ->
case run_hooks('client.authenticate', [Credential], ignore) of case run_hooks('client.authenticate', [Credential], ignore) of
ignore -> ignore ->
inc_authn_metrics(anonymous), on_authentication_complete(Credential, NotSuperUser, anonymous),
{ok, NotSuperUser}; {ok, NotSuperUser};
ok -> ok ->
inc_authn_metrics(ok), on_authentication_complete(Credential, NotSuperUser, ok),
{ok, NotSuperUser}; {ok, NotSuperUser};
{ok, _AuthResult} = OkResult -> {ok, AuthResult} = OkResult ->
inc_authn_metrics(ok), on_authentication_complete(Credential, AuthResult, ok),
OkResult; OkResult;
{ok, _AuthResult, _AuthData} = OkResult -> {ok, AuthResult, _AuthData} = OkResult ->
inc_authn_metrics(ok), on_authentication_complete(Credential, AuthResult, ok),
OkResult; OkResult;
{error, _Reason} = Error -> {error, _Reason} = Error ->
inc_authn_metrics(error), inc_authn_metrics(error),
@ -240,3 +240,10 @@ inc_authn_metrics(ok) ->
inc_authn_metrics(anonymous) -> inc_authn_metrics(anonymous) ->
emqx_metrics:inc('authentication.success.anonymous'), emqx_metrics:inc('authentication.success.anonymous'),
emqx_metrics:inc('authentication.success'). emqx_metrics:inc('authentication.success').
on_authentication_complete(Credential, Result, Type) ->
emqx_hooks:run(
'client.check_authn_complete',
[Credential, Result#{is_anonymous => (Type =:= anonymous)}]
),
inc_authn_metrics(Type).

View File

@ -44,6 +44,7 @@
'client.disconnected', 'client.disconnected',
'client.authorize', 'client.authorize',
'client.check_authz_complete', 'client.check_authz_complete',
'client.check_authn_complete',
'client.authenticate', 'client.authenticate',
'client.subscribe', 'client.subscribe',
'client.unsubscribe', 'client.unsubscribe',

View File

@ -282,6 +282,17 @@ fields("ctx_check_authz_complete") ->
{"authz_source", sc(binary(), #{desc => ?DESC("event_authz_source")})}, {"authz_source", sc(binary(), #{desc => ?DESC("event_authz_source")})},
{"result", sc(binary(), #{desc => ?DESC("event_result")})} {"result", sc(binary(), #{desc => ?DESC("event_result")})}
]; ];
fields("ctx_check_authn_complete") ->
Event = 'client.check_authn_complete',
[
{"event_type", event_type_sc(Event)},
{"event", event_sc(Event)},
{"clientid", sc(binary(), #{desc => ?DESC("event_clientid")})},
{"username", sc(binary(), #{desc => ?DESC("event_username")})},
{"peerhost", sc(binary(), #{desc => ?DESC("event_peerhost")})},
{"is_anonymous", sc(boolean(), #{desc => ?DESC("event_is_anonymous")})},
{"is_superuser", sc(boolean(), #{desc => ?DESC("event_is_superuser")})}
];
fields("ctx_bridge_mqtt") -> fields("ctx_bridge_mqtt") ->
Event = '$bridges/mqtt:*', Event = '$bridges/mqtt:*',
EventBin = atom_to_binary(Event), EventBin = atom_to_binary(Event),
@ -330,6 +341,7 @@ rule_input_message_context() ->
ref("ctx_disconnected"), ref("ctx_disconnected"),
ref("ctx_connack"), ref("ctx_connack"),
ref("ctx_check_authz_complete"), ref("ctx_check_authz_complete"),
ref("ctx_check_authn_complete"),
ref("ctx_bridge_mqtt"), ref("ctx_bridge_mqtt"),
ref("ctx_delivery_dropped"), ref("ctx_delivery_dropped"),
ref("ctx_schema_validation_failed") ref("ctx_schema_validation_failed")

View File

@ -40,6 +40,7 @@
on_client_disconnected/4, on_client_disconnected/4,
on_client_connack/4, on_client_connack/4,
on_client_check_authz_complete/6, on_client_check_authz_complete/6,
on_client_check_authn_complete/3,
on_session_subscribed/4, on_session_subscribed/4,
on_session_unsubscribed/4, on_session_unsubscribed/4,
on_message_publish/2, on_message_publish/2,
@ -182,6 +183,18 @@ on_client_check_authz_complete(
Conf Conf
). ).
on_client_check_authn_complete(ClientInfo, Result, Conf) ->
apply_event(
'client.check_authn_complete',
fun() ->
eventmsg_check_authn_complete(
ClientInfo,
Result
)
end,
Conf
).
on_client_disconnected(ClientInfo, Reason, ConnInfo, Conf) -> on_client_disconnected(ClientInfo, Reason, ConnInfo, Conf) ->
apply_event( apply_event(
'client.disconnected', 'client.disconnected',
@ -438,6 +451,27 @@ eventmsg_check_authz_complete(
#{} #{}
). ).
eventmsg_check_authn_complete(
_ClientInfo = #{
clientid := ClientId,
username := Username,
peerhost := PeerHost
},
#{is_anonymous := IsAnonymous} = Result
) ->
IsSuperuser = maps:get(is_superuser, Result, false),
with_basic_columns(
'client.check_authn_complete',
#{
clientid => ClientId,
username => Username,
peerhost => ntoa(PeerHost),
is_anonymous => IsAnonymous,
is_superuser => IsSuperuser
},
#{}
).
eventmsg_sub_or_unsub( eventmsg_sub_or_unsub(
Event, Event,
_ClientInfo = #{ _ClientInfo = #{
@ -679,6 +713,7 @@ event_info() ->
event_info_client_disconnected(), event_info_client_disconnected(),
event_info_client_connack(), event_info_client_connack(),
event_info_client_check_authz_complete(), event_info_client_check_authz_complete(),
event_info_client_check_authn_complete(),
event_info_session_subscribed(), event_info_session_subscribed(),
event_info_session_unsubscribed(), event_info_session_unsubscribed(),
event_info_delivery_dropped(), event_info_delivery_dropped(),
@ -770,6 +805,13 @@ event_info_client_check_authz_complete() ->
{<<"client check authz complete">>, <<"授权结果"/utf8>>}, {<<"client check authz complete">>, <<"授权结果"/utf8>>},
<<"SELECT * FROM \"$events/client_check_authz_complete\"">> <<"SELECT * FROM \"$events/client_check_authz_complete\"">>
). ).
event_info_client_check_authn_complete() ->
event_info_common(
'client.check_authn_complete',
{<<"client check authn complete">>, <<"认证结果"/utf8>>},
{<<"client check authn complete">>, <<"认证结果"/utf8>>},
<<"SELECT * FROM \"$events/client_check_authn_complete\"">>
).
event_info_session_subscribed() -> event_info_session_subscribed() ->
event_info_common( event_info_common(
'session.subscribed', 'session.subscribed',
@ -854,6 +896,13 @@ test_columns('client.check_authz_complete') ->
{<<"action">>, [<<"publish">>, <<"the action of publish or subscribe">>]}, {<<"action">>, [<<"publish">>, <<"the action of publish or subscribe">>]},
{<<"result">>, [<<"allow">>, <<"the authz check complete result">>]} {<<"result">>, [<<"allow">>, <<"the authz check complete result">>]}
]; ];
test_columns('client.check_authn_complete') ->
[
{<<"clientid">>, [<<"c_emqx">>, <<"the clientid if the client">>]},
{<<"username">>, [<<"u_emqx">>, <<"the username if the client">>]},
{<<"is_superuser">>, [true, <<"Whether this is a superuser">>]},
{<<"is_anonymous">>, [false, <<"Whether this is a superuser">>]}
];
test_columns('session.unsubscribed') -> test_columns('session.unsubscribed') ->
test_columns('session.subscribed'); test_columns('session.subscribed');
test_columns('session.subscribed') -> test_columns('session.subscribed') ->
@ -1023,6 +1072,17 @@ columns_with_exam('client.check_authz_complete') ->
{<<"timestamp">>, erlang:system_time(millisecond)}, {<<"timestamp">>, erlang:system_time(millisecond)},
{<<"node">>, node()} {<<"node">>, node()}
]; ];
columns_with_exam('client.check_authn_complete') ->
[
{<<"event">>, 'client.check_authz_complete'},
{<<"clientid">>, <<"c_emqx">>},
{<<"username">>, <<"u_emqx">>},
{<<"peerhost">>, <<"192.168.0.10">>},
{<<"is_superuser">>, true},
{<<"is_anonymous">>, false},
{<<"timestamp">>, erlang:system_time(millisecond)},
{<<"node">>, node()}
];
columns_with_exam('session.subscribed') -> columns_with_exam('session.subscribed') ->
[columns_example_props(sub_props)] ++ columns_message_sub_unsub('session.subscribed'); [columns_example_props(sub_props)] ++ columns_message_sub_unsub('session.subscribed');
columns_with_exam('session.unsubscribed') -> columns_with_exam('session.unsubscribed') ->
@ -1124,6 +1184,7 @@ hook_fun('client.connected') -> fun ?MODULE:on_client_connected/3;
hook_fun('client.disconnected') -> fun ?MODULE:on_client_disconnected/4; hook_fun('client.disconnected') -> fun ?MODULE:on_client_disconnected/4;
hook_fun('client.connack') -> fun ?MODULE:on_client_connack/4; hook_fun('client.connack') -> fun ?MODULE:on_client_connack/4;
hook_fun('client.check_authz_complete') -> fun ?MODULE:on_client_check_authz_complete/6; hook_fun('client.check_authz_complete') -> fun ?MODULE:on_client_check_authz_complete/6;
hook_fun('client.check_authn_complete') -> fun ?MODULE:on_client_check_authn_complete/3;
hook_fun('session.subscribed') -> fun ?MODULE:on_session_subscribed/4; hook_fun('session.subscribed') -> fun ?MODULE:on_session_subscribed/4;
hook_fun('session.unsubscribed') -> fun ?MODULE:on_session_unsubscribed/4; hook_fun('session.unsubscribed') -> fun ?MODULE:on_session_unsubscribed/4;
hook_fun('message.delivered') -> fun ?MODULE:on_message_delivered/3; hook_fun('message.delivered') -> fun ?MODULE:on_message_delivered/3;
@ -1149,6 +1210,7 @@ event_name(<<"$events/client_connected">>) -> 'client.connected';
event_name(<<"$events/client_disconnected">>) -> 'client.disconnected'; event_name(<<"$events/client_disconnected">>) -> 'client.disconnected';
event_name(<<"$events/client_connack">>) -> 'client.connack'; event_name(<<"$events/client_connack">>) -> 'client.connack';
event_name(<<"$events/client_check_authz_complete">>) -> 'client.check_authz_complete'; event_name(<<"$events/client_check_authz_complete">>) -> 'client.check_authz_complete';
event_name(<<"$events/client_check_authn_complete">>) -> 'client.check_authn_complete';
event_name(<<"$events/session_subscribed">>) -> 'session.subscribed'; event_name(<<"$events/session_subscribed">>) -> 'session.subscribed';
event_name(<<"$events/session_unsubscribed">>) -> 'session.unsubscribed'; event_name(<<"$events/session_unsubscribed">>) -> 'session.unsubscribed';
event_name(<<"$events/message_delivered">>) -> 'message.delivered'; event_name(<<"$events/message_delivered">>) -> 'message.delivered';
@ -1163,6 +1225,7 @@ event_topic('client.connected') -> <<"$events/client_connected">>;
event_topic('client.disconnected') -> <<"$events/client_disconnected">>; event_topic('client.disconnected') -> <<"$events/client_disconnected">>;
event_topic('client.connack') -> <<"$events/client_connack">>; event_topic('client.connack') -> <<"$events/client_connack">>;
event_topic('client.check_authz_complete') -> <<"$events/client_check_authz_complete">>; event_topic('client.check_authz_complete') -> <<"$events/client_check_authz_complete">>;
event_topic('client.check_authn_complete') -> <<"$events/client_check_authn_complete">>;
event_topic('session.subscribed') -> <<"$events/session_subscribed">>; event_topic('session.subscribed') -> <<"$events/session_subscribed">>;
event_topic('session.unsubscribed') -> <<"$events/session_unsubscribed">>; event_topic('session.unsubscribed') -> <<"$events/session_unsubscribed">>;
event_topic('message.delivered') -> <<"$events/message_delivered">>; event_topic('message.delivered') -> <<"$events/message_delivered">>;

View File

@ -237,6 +237,7 @@ init_per_testcase(t_events, Config) ->
"\"$events/client_disconnected\", " "\"$events/client_disconnected\", "
"\"$events/client_connack\", " "\"$events/client_connack\", "
"\"$events/client_check_authz_complete\", " "\"$events/client_check_authz_complete\", "
"\"$events/client_check_authn_complete\", "
"\"$events/session_subscribed\", " "\"$events/session_subscribed\", "
"\"$events/session_unsubscribed\", " "\"$events/session_unsubscribed\", "
"\"$events/message_acked\", " "\"$events/message_acked\", "
@ -1084,6 +1085,7 @@ client_connected(Client, Client2) ->
{ok, _} = emqtt:connect(Client2), {ok, _} = emqtt:connect(Client2),
verify_event('client.connack'), verify_event('client.connack'),
verify_event('client.connected'), verify_event('client.connected'),
verify_event('client.check_authn_complete'),
ok. ok.
client_disconnected(Client, Client2) -> client_disconnected(Client, Client2) ->
ok = emqtt:disconnect(Client, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}), ok = emqtt:disconnect(Client, 0, #{'User-Property' => {<<"reason">>, <<"normal">>}}),
@ -4196,7 +4198,18 @@ verify_event_fields('client.check_authz_complete', Fields) ->
]) ])
), ),
?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])), ?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])). ?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>]));
verify_event_fields('client.check_authn_complete', Fields) ->
#{
clientid := ClientId,
username := Username,
is_anonymous := IsAnonymous,
is_superuser := IsSuperuser
} = Fields,
?assert(lists:member(ClientId, [<<"c_event">>, <<"c_event2">>])),
?assert(lists:member(Username, [<<"u_event">>, <<"u_event2">>])),
?assert(erlang:is_boolean(IsAnonymous)),
?assert(erlang:is_boolean(IsSuperuser)).
verify_peername(PeerName) -> verify_peername(PeerName) ->
case string:split(PeerName, ":") of case string:split(PeerName, ":") of

View File

@ -265,6 +265,21 @@ t_rule_test_smoke(_Config) ->
<<"sql">> => <<"SELECT\n *\nFROM\n \"t/#\"">> <<"sql">> => <<"SELECT\n *\nFROM\n \"t/#\"">>
} }
}, },
#{
expected => #{code => 412},
input =>
#{
<<"context">> =>
#{
<<"clientid">> => <<"c_emqx">>,
<<"event_type">> => <<"client_check_authn_complete">>,
<<"is_superuser">> => true,
<<"is_anonymous">> => false,
<<"username">> => <<"u_emqx">>
},
<<"sql">> => <<"SELECT\n *\nFROM\n \"t/#\"">>
}
},
#{ #{
expected => #{code => 412}, expected => #{code => 412},
input => input =>

View File

@ -195,6 +195,28 @@ t_ctx_check_authz_complete(_) ->
do_test(SQL, Context, Expected). do_test(SQL, Context, Expected).
t_ctx_check_authn_complete(_) ->
SQL =
<<
"SELECT clientid, username, is_superuser, is_anonymous\n"
"FROM \"$events/client_check_authn_complete\""
>>,
Context =
#{
clientid => <<"c_emqx">>,
event_type => client_check_authn_complete,
is_superuser => true,
is_anonymous => false
},
Expected = check_result(
[clientid, username, is_superuser, is_anonymous],
[],
Context
),
do_test(SQL, Context, Expected).
t_ctx_delivery_dropped(_) -> t_ctx_delivery_dropped(_) ->
SQL = SQL =
<<"SELECT from_clientid, from_username, reason, topic, qos FROM \"$events/delivery_dropped\"">>, <<"SELECT from_clientid, from_username, reason, topic, qos FROM \"$events/delivery_dropped\"">>,

View File

@ -390,4 +390,10 @@ event_ctx_disconnected_reason.desc:
event_ctx_disconnected_reason.label: event_ctx_disconnected_reason.label:
"""Disconnect Reason""" """Disconnect Reason"""
event_is_anonymous.desc:
"""True if this user is anonymous."""
event_is_superuser.desc:
"""True if this is a super user."""
} }